miércoles, 24 de marzo de 2010

Configuración de Pruebas JUnit

Durante la realización de pruebas unitarias existen variables comunes en todas las pruebas por ejemplo directorios de salida, directorio de datos etc.

En vez de crear estas variables en cada prueba unitaria utilizo la clase TestConfiguration.

TestConfiguration es Singleton que permite accesar el outputPath (directorio de salida) y el testDataPath (directorio con datos de prueba). Además inicializa el Logger de Log4J.

En el constructor de cada prueba unitaria solo hay que insertar el siguiente código y Log4J queda configurado segun el log4jprops.xml (ver código).


TestConfiguration.getInstance();


El siguiente es el código de TestConfiguration


package org.gissolution.photodb;

import java.io.File;
import java.net.URL;

import org.apache.log4j.xml.DOMConfigurator;

public class TestConfiguration {
private static org.apache.log4j.Logger logger = org.apache.log4j.Logger
.getLogger(TestConfiguration.class);
private static TestConfiguration testConfig = null;
private final String outputPath;
private final String testDataPath;
protected TestConfiguration() {
File out = new File("output");
File data = new File("test_data");
this.outputPath = out.getAbsolutePath();
this.testDataPath = data.getAbsolutePath();
String configfile = "/log4jprops.xml";
URL url = this.getClass().getResource(configfile);
DOMConfigurator.configure(url);
logger.info("Log4j configured with " + url.getFile());

}

public static TestConfiguration getInstance() {
if (testConfig == null) {
testConfig = new TestConfiguration();
}
return testConfig;
}

public String getOutputPath() {
return outputPath;
}

public String getTestDataPath() {
return testDataPath;
}

public static String getExistingTestData(String relativePath) {
String tpath = TestConfiguration.getInstance().getTestDataPath();
File f = new File(tpath + File.separator + relativePath);
if(f.exists()) {
return f.getAbsolutePath();
}else {
throw new IllegalArgumentException(f.getAbsolutePath() + " no existe");
}
}

public static void main(String[] args) {
System.out.println("Output path: " + TestConfiguration.getInstance().getOutputPath());
System.out.println("Test Data path: " + TestConfiguration.getInstance().getTestDataPath());
}

martes, 23 de marzo de 2010

Guardando Imágenes en la Base de Datos con Symfony 1.4

Aunque existen varias escuelas de pensamiento sobre si las imágenes se deben o no guardar en la base de datos. Para mis necesidades especificas es un requerimiento.

Symfony es un framework muy poderoso que apenas estoy empezando a aprender a utilizar. Sin embargo dentro de la documentación no encontré un buen ejemplo de como guardad una imagen en la base de datos como un BLOB y como recuperarla para despliegue.

Después de algo de investigación encontré un excelente posting en un forum del Symfony titulado How to upload an Image to a DB under Symfony 1.4 & display image to an end user por René Kabis donde explicaban como cargar y desplegar las imágenes en un BLOB.

El problema que tuve no era con el posting si no con mi falta de experiencia con Symfony. El posting asume que el usuario tiene al menos experiencia básica con Symfony, asi que decidí crear un guía un poco más detallada.

Creando el Proyecto sfMapGallery





  1. Abra Netbeans y vaya a Menu>New Project...



  2. Seleccione PHP Application y haga clic en Next


  3. Tecle el como Project Name sfMapGallery y haga clic en Next


  4. Cambie el Project URL a http://localhost:9091/ y haga clic en Next este paso asume que su configuración en Apache es en esta dirección


  5. Seleccione Symfony PHP Framework y haga clic en Finish





Creando la Base de Datos


Por defecto se crea el archivo Sources Files\config\databases.yml el mismo debe verse como este:

all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: mysql:host=localhost;dbname=sfMapGallery
username: root
password:

A continuación crearemos una base de de datos con el nombre mapgallery.



  1. Edite el archivo Sources Files\config\databases.yml y cambie el dbname por mapgallery

    dsn: mysql:host=localhost;dbname=mapgallery
  2. Abra phpMyAdmin (con WAMP el url es http://localhost/phpmyadmin/) y cree una nueva base de datos con el nombre mapgallery



  3. Copie el siguiente contenido en el archivo Sources Files\config\doctrine\schema.yml

    Map:
    tableName: maps
    connection: doctrine
    actAs: [Timestampable]
    options:
    type: INNODB
    collate: utf8_unicode_ci
    charset: utf8
    columns:
    id:
    type: integer(4)
    primary: true
    autoincrement: true
    filename:
    type: string(50)
    image:
    type: blob
    tags:
    type: string(150)

  4. Corra el comando symfony doctrine:build --all --no-confirmation . Este comando debe crear la tabla en la base de datos y el modelos, las formas y los filtros en Sources Files\lib

Creando el Validador de Imágenes




  1. Cree el directorio Sources Files\lib\validator

  2. Copy el archivo <Directorio Instalación Symfony>\lib\validator\sfValidatorFile.class.php

  3. Renombre el archivo Sources Files\lib\validator\sfValidatorFile.class.php a sfValidatorImgToDB.class.php

  4. Abra el archivo sfValidatorImgToDB.class.php y :

    1. Cambie el nombre de la clase a sfValidatorImgToDB
      ...
      class sfValidatorImgToDB extends sfValidatorBase {
      ...
    2. En el método configure adicione la opcion resolution (aprox. linea 75) y comente las optiones validated_file_class y path.
      ...
      $this->addOption('mime_categories', array(
      'web_images' => array(
      'image/jpeg',
      'image/pjpeg',
      'image/png',
      'image/x-png',
      'image/gif',
      )));
      $this->addOption('resolution');
      //$this->addOption('validated_file_class', 'sfValidatedFile');
      //$this->addOption('path', null);

      $this->addMessage('max_size', 'File is too large (maximum is %max_size% bytes).');
      ...

    3. En el método doClean comente las dos últimas lineas e inserte el código mostrado
      ...
      if ($this->hasOption('mime_types')) {
      $mimeTypes = is_array($this->getOption('mime_types')) ? $this->getOption('mime_types') : $this->getMimeTypesFromCategory($this->getOption('mime_types'));
      if (!in_array($mimeType, array_map('strtolower', $mimeTypes))) {
      throw new sfValidatorError($this, 'mime_types', array('mime_types' => $mimeTypes, 'mime_type' => $mimeType));
      }
      }

      //$class = $this->getOption('validated_file_class');
      //return new $class($value['name'], $mimeType, $value['tmp_name'], $value['size'], $this->getOption('path'));

      // check resolution

      if ($this->hasOption('resolution')) {
      $resolution = (is_array($this->getOption('resolution')) && count($this->getOption('resolution'))==2) ? $this->getOption('resolution') : null;
      if (is_int($resolution[0]) && is_int($resolution[1]) && ($resolution[0]/$resolution[1] == 4/3)) {
      $width = $resolution[0];
      $height = $resolution [1];
      }else {
      $width = 1024;
      $width = 768;
      $ratio = 4/3;
      }
      }

      return base64_encode($this->catchImage($value['tmp_name'], $width, $height));
      }

      ...

    4. Después del metodo doClean agregue los metodos catchImage y resizeImage
      ...
      public function catchImage($image, $width, $height) {
      if($image) {
      $data=fread(fopen($image, "r"), filesize($image));
      unlink($image);
      return $this->resizeImage(imagecreatefromstring($data), $width, $height);
      }
      }

      public function resizeImage($image, $width, $height) {
      $old_width = imagesx($image);
      $old_height = imagesy($image);
      $ratio=$old_width/$old_height;
      $resized_img = imagecreatetruecolor($width,$height);
      $white = imagecolorallocate($resized_img, 255, 255, 255);
      imagefill($resized_img, 0, 0, $white);
      if($ratio>(4/3)) {
      $new_width=$width;
      $new_height=($width*($old_height/$old_width));
      $x_offset=0;
      $y_offset=(($height-$new_height)/2);
      }elseif($ratio<(4/3)) { $new_width=($height*($old_width/$old_height)); $new_height=$height; $x_offset=(($width-$new_width)/2); $y_offset=0; }else { $new_width=$width; $new_height=$height; $x_offset=0; $y_offset=0; } imagecopyresampled($resized_img, $image, $x_offset, $y_offset, 0, 0, $new_width, $new_height, $old_width, $old_height); ob_start(); imageJPEG($resized_img); $new_image = ob_get_contents(); ob_end_clean(); return $new_image; }

      ...

    5. Guarde el archivo



  5. Abra el archivo Sources Files\lib\form\doctrine\MapForm.class.php y agreue el siguiente código en el método configure.

    class MapForm extends BaseMapForm
    {
    public function configure()
    {
    $this->setWidgets(array(
    'id' => new sfWidgetFormInputHidden(),
    'filename' => new sfWidgetFormInputText(),
    'image' => new sfWidgetFormInputFileEditable(array('label'=>'Map', 'file_src'=>'', 'is_image'=>true, 'edit_mode'=>!$this->isNew(),'template'=>'%input%')),
    'tags' => new sfWidgetFormInputText(),
    'created_at' => new sfWidgetFormDateTime(),
    'updated_at' => new sfWidgetFormDateTime(),
    ));

    $this->setValidators(array(
    'id' => new sfValidatorDoctrineChoice(array('model' => $this->getModelName(), 'column' => 'id', 'required' => false)),
    'filename' => new sfValidatorString(array('max_length' => 50, 'required' => false)),
    'image' => new sfValidatorImgToDB(array('resolution'=>array(1024, 768), 'mime_types'=>array('image/jpeg', 'image/jpg', 'image/pjpeg'), 'required'=>true)),
    'tags' => new sfValidatorString(array('max_length' => 150, 'required' => false)),
    'created_at' => new sfValidatorDateTime(),
    'updated_at' => new sfValidatorDateTime(),
    ));

    $this->widgetSchema->setNameFormat('map[%s]');

    $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
    }
    }

Probando el Validador


  1. Corra el commando symfony doctrine:generate-module --with-show frontend maps Map

  2. Abra la dirección http://localhost:9091/frontend_dev.php/maps


Desplegando la Imagen





  1. Corra el comando symfony generate:module frontend image. Esto creará la carpeta image en Source Files\apps\frontend\modules.


  2. Abra el archivo Source Files\apps\frontend\modules\templates\indexSuccess.php agregue el siguiente código
    <?php echo $image; ?>


  3. Abra el archivo Source Files\apps\frontend\modules\image\actions\actions.class.php reemplaze el código del método executeIndex por el siquiente:
    ...

    public function executeIndex(sfWebRequest $request) {
    $idreq = $request->getParameter('id');
    $data = Doctrine::getTable('Map')->find($idreq)->getData();
    $this->image = base64_decode($data["image"]);
    sfConfig::set('sf_web_debug', false);
    sfConfig::set('sf_escaping_strategy', false);
    }
    ...
  4. Cree el directorio Source Files\apps\frontend\modules\image\config

  5. Cree el archivo Source Files\apps\frontend\modules\image\config\view.yml, agregue el siguiente contenido al archivo:
    indexSuccess:
    has_layout: false
    http_metas:
    content-type: image/jpeg
    downloadSuccess:
    has_layout: false
    http_metas:
    content-type: image/jpeg
  6. Abra el archivo Source Files\apps\frontend\config\routing.yml , y agregue el siguiente código antes del registro default:
    ...
    default_index:
    url: /:module
    param: { action: index }
    image_handler:
    url: /image/:id/*
    class: sfRoute
    options:
    model: Photo
    type: object
    param: { module: image, action: index }
    requirements:
    id: \d+
    sf_method: [GET]

    default:
    url: /:module/:action/*
  7. Abra el archivo Source Files\apps\frontend\modules\photo\templates\indexSuccess.php y cambie el codigo que despliega la imagen:
    ...
    <td><?php echo $map->getFilename() ?></td>
    <td><img src="<?php echo url_for('@default?module=image&action=index&id='.$map->getId()) ?>" height="100px"/> </td>
    <td><?php echo $map->getTags() ?></td>
    ...

viernes, 19 de marzo de 2010

Como cambiar las contraseñas de ArcGIS Server

Recientemente tuvimos que hacer la actualización de ArcGIS Server 9.3 a la versión 9.3.1. Al momento de leer las intrucciones de instalaciión nos percatamos que se requería dar las mismas contraseñas de las cuentas ArcGISSOC y ArcGISSOM. Desafortunadamente habíamos extraviado las contraseñas.

La instalación de la versión 9.3.1 permite recrear las cuentas pero esto implica recrear los permisos NTFS (es una instalación en Windows con .NET) lo cual para nosotros hubiese sido un proceso largo.

No econtramos una clara documentación de como cambiar las contraseñas sin dañar las intalación existente y sin tener que recrear los permisos.

Los siguientes pasos nos funcionaron:



  1. Abra Services y detener el Servicio ArcGIS Server Object Manager.


  2. Cambie las contraseñas de las cuentas ArcGISSOC, ArcGISSOM y ArcGISWebServices.


  3. En la ventana Services camie la contraseña de servicio ArcGIS Server Object Manager y reinicie el servicio


  4. Corra el Programs\ArcGIS\ArcGIS Server for the Microsoft . NET Framework\GIS Server Post Install


  5. Coloque las misma contraseñas que cambio en el paso 2 en las respectivas cuentas


  6. Reinicie el servidor


Pruebe entrando al ArcGIS Server Manager con la cuenta ArcGISWebServices y debe poder ver los servicios existentes.

miércoles, 17 de marzo de 2010

Servcio de Image Server colgado en Starting

Por razones que aún no he podido determinar la instalación de ESRI Image Server 9.3 que tenemos en la oficina se cuelga periódicamente. La ventana de Services muestra el Status como Starting.

Las soluciones en el pasado había sido reinciar el servidor. Esta vez por motivos de operación no podiamos.

Ya habíamos tratado de iniciar el servicio desde la ventana Services y desde la linea de comando (con net stop y net start) pero no subía.

Para subsanar el problema siga los siguientes pasos:
1. Seleccione el Image Name ESRIImageServiceProvider.exe en el Windows Task Manager en la pestaña de Processes y haga clic en el botón End Process.

2. Abra la ventana Services.

3. Detenga el servicio ESRIImageServerReporter si esta iniciado.

4. Inicie el servicio ESRI Image ServiceProvider : XXXX (las X representan el número de puerto en que se instalo el Image Server). Esto debe reiniciar el servicio ESRIImageServerReporter automáticamente.



martes, 16 de marzo de 2010

"Incorrect Function" utilizando Isapi_redirect.dll y Tomcat 6.0.x

Al instalar el isapi_redirect.dll según las instrucciones de ESRI en el articulo 35014 obtenía el error "Incorrect Function" al correr la prueba en el paso 8.B.

El comportamiento era el siguiente la primera vez que se accedía http://[your_machine_name]/examples/servlets/servlet/HelloWorldExample funcionaba. Si hacia refresh de la pagina obtenía Incorrect function y si despues de eso abría otra pestaña del navegador obtenía el error 500.

Sospeché que el problema teníaque ver con permisos NTFS, pero a pesar de dar permisos a "Everyone" en todo el directorio de Tomcat y en el directorio del JDK (una pésima práctica pero estaba desesperado y nada funcionaba) no logré resolver el problema.

Hice varias instalaciones utilizando Tomcat 6.0.26 con isapi redirect 1.2.28 y Tomcat 6.0.16 con 1.2.27 pero ninguno de las instalaciones funcionó.

Lo que por fin funcionó fue desinstalar IIS y volver a instalarlo.

La configuración que funcionó fue Windows XP Pro SP2, IIS 5.1, Tomcat 6.0.16 e isapi redirect 1.2.27.

domingo, 14 de marzo de 2010

Configurar Symfony con XAMPP en Mac

La configuracion del servidor web mostrada en la sección Web Server Configuration requiere algunos adiciones para que funcione.

Estoy usando Symfony 1.4.3 con XAMPP for Mac OS X 1.7.2a corriendo sobre Snow Leopard.

Configuración de Apache


Cuando seguí las instrucciones de la sección Web Server Configuration Apache inciaba y los segundos se apagaba solo. Al verificar el error_log encontré:

[Sun Mar 14 08:09:31 2010] [emerg] (13)Permission denied: couldn't grab the accept mutex
[Sun Mar 14 08:09:31 2010] [emerg] (13)Permission denied: couldn't grab the accept mutex
[Sun Mar 14 08:09:32 2010] [alert] Child 329 returned a Fatal error... Apache is exiting!

La solución que funcionó para mi fue agregar "AcceptMutex flock" en el httpd.conf.
Mi configuración quedó así:
AcceptMutex flock
# Be sure to only have this line once in your configuration
NameVirtualHost 127.0.0.1:8080

# This is the configuration for your project
Listen 127.0.0.1:8080

<VirtualHost 127.0.0.1:8080>
DocumentRoot "/Users/tg/NetBeansProjects/sfRemates/web"
DirectoryIndex index.php
<Directory "/Users/tg/NetBeansProjects/sfRemates/web">
AllowOverride All
Allow from All
</Directory>

Alias /sf /Users/tg/Documents/symfony-1.4.3/data/web/sf
<Directory "/Users/tg/Documents/symfony-1.4.3/data/web/sf">
AllowOverride All
Allow from All
</Directory>
</VirtualHost>

Con esto logré que Apache arrancara.

Problemas con Permisos


Luego de lograr correr Apache y tener la aplicación creada. Empece a obtener el siguiente error al conectarme a http://127.0.0.1:8080 :

Warning: require_once(/Users/luisberrocal/Documents/symfony-1.4.3/lib/autoload/sfCoreAutoload.class.php) [function.require-once]: failed to open stream: Permission denied in /Users/luisberrocal/NetBeansProjects/sfRemates/config/ProjectConfiguration.class.php on line 3

Fatal error: require_once() [function.require]: Failed opening required '/Users/luisberrocal/Documents/symfony-1.4.3/lib/autoload/sfCoreAutoload.class.php' (include_path='.:/Applications/xampp/xamppfiles/lib/php:/Applications/xampp/xamppfiles/lib/php/pear:/Applications/xampp/xamppfiles/Smarty-2.6.26/libs') in /Users/luisberrocal/NetBeansProjects/sfRemates/config/ProjectConfiguration.class.php on line 3

Despues de investigar mucho y hacer varios cambios en los permisos opte por instalar Symfony desde Pear. Originalmente lo había instalado en /Users/luisberrocal/Documents/symfony-1.4.3 a partir de un zip.

A continución los pasos que me funcionaron, los puntos 3 a 5 son para usuarios de Netbeans, si no esta utilizando Netbeans salte al punto 6:

1. Corra pear channel-discover pear.symfony-project.com
2. Corra pear install symfony/symfony-1.4.3
3. En Netbeans 6.8 abra la ventana Options desde el menu Neatbeans\Preferences.
4. Seleccion la pestaña PHP y luego la pestaña Symfony.
5. Cambie la ubicación del Symfony script a /usr/bin/symfony.
6. Genere un proyecto.

Tuve que editar el httpd.conf para lograr que los gráficos se vieran:

Alias /sf /usr/lib/php/data/symfony/web/sf
<Directory "/usr/lib/php/data/symfony/web/sf">
AllowOverride All
Allow from All
</Directory>

viernes, 12 de marzo de 2010

Oracle, Doctrine y Symfony

Después de años de resistirme a PHP, por considerlo un lenguage de scripting, recientemente me vi forzado a involucrarme con el. Después de años de programar en Java, C# y Visual Basic el cambio fue un poco traumático. Sin embargo mi experiencia con Python me ayudo un poco.
En mi busqueda de frameworks que hicieran lo que ya conocia en Java como Hibernate, Struts, etc. Me tropece con Symfony.

Symfony es un Framework basado en el paradigma Model-View-Controller (MVC), es elegante y muy versatil. Sin embargo su curva de apredizaje es mayor que la otros frameworks como por ejempo CodeIgniter. Symfony soporta dos ORMs Doctrine y Propel, decidí trabaja con Doctrine porque encontre la documentación más clara.

La mayoría de los ejemplos que encontré eran usando como RDBMS MySQL, sin embargo no encontré mucho para Oracle y Doctrine. También tuve problemas con las conexiones a Oracle al principio asi que decidi escribir este artículo para tenerlo de referencia en futuras conexiones.

Las tareas de Doctrine no corren desde NetBeans


El primer problema que tuve fue que las tareas de symfony no encontraban el conector oci a pesar que habia editado el php.ini. El problema se debía a que WAMP utiliza dos php.ini uno para Apache (<directorio instalación WAMP>\bin\apache\Apache2.2.11\bin\php.ini) y otro que está en el raiz de la la instalación de php (<directorio instalación WAMP>/bin/php/php5.3.0/php.ini). Cuando trataba de correr las tareas de Doctrine desde Netbeans 6.8 mandaba un error. Una vez active el active el php_oci8.dll en php.ini que se encontraba en el raíz de php el problema se solucionó.

Definición del DSN de Oracle en Symfony

La versión de Oracle con la que estoy trabajando es 10gR2 asi que utilice el conector php_pdo_oci.dll. Aparentemente el php_oci8.dll no es requerido para conectarse con Doctrine.
La sintaxis que me funcionó en el databases.yml fue la siguiente:

all:
doctrine:
class: sfDoctrineDatabase
param:
dsn: oci://[user]:[password]@[server_url]/(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=[server_url])(PORT=1521)))(CONNECT_DATA=(SID=[namespace])))


La información del TNS se encuentra en el archivo tnsame.ora (en mi caso se ecuentra en C:/Oracle/product/10.1.0/Client_1/network/ADMIN/tnsnames.ora).