sábado, agosto 09, 2008

Extendiendo la memoria virtual en el Nokia N800

La máxima memoria virtual del N800 es de 128KB, quiero agregar por lo menos 1GB que tomaré de la tarjeta interna.

La SD interna es de 8GB y la tengo ocupada sólo con respaldos de contactos, correos etc.

Entro como root desde mi desktop.
hopper800:~# sfdisk -l /dev/mmcblk0

Disk /dev/mmcblk0: 250112 cylinders, 4 heads, 16 sectors/track
Warning: The partition table looks like it was made
for C/H/S=*/103/2 (instead of 250112/4/16).
For this listing I'll assume that geometry.
Units = cylinders of 105472 bytes, blocks of 1024 bytes, counting from 0

Device Boot Start End #cyls #blocks Id System
/dev/mmcblk0p1 39+ 77704- 77665- 7999488 b W95 FAT32
start: (c,h,s) expected (39,79,1) found (0,130,3)
end: (c,h,s) expected (1023,102,2) found (996,102,2)
/dev/mmcblk0p2 0 - 0 0 0 Empty
/dev/mmcblk0p3 0 - 0 0 0 Empty
/dev/mmcblk0p4 0 - 0 0 0 Empty


El formato original de la tarjeta ocupa los 8GB en una partición FAT32.
hopper800:~# sfdisk /dev/mmcblk0 -uM
Checking that no-one is using this disk right now ...
OK

Disk /dev/mmcblk0: 250112 cylinders, 4 heads, 16 sectors/track
Old situation:
Units = mebibytes of 1048576 bytes, blocks of 1024 bytes, counting from 0

Device Boot Start End MiB #blocks Id System
/dev/mmcblk0p1 0+ 6799 6800- 6963199+ b W95 FAT32
/dev/mmcblk0p2 6800 7815 1016 1040384 82 Linux swap / Solaris
/dev/mmcblk0p3 0 - 0 0 0 Empty
/dev/mmcblk0p4 0 - 0 0 0 Empty
Input in the following format; absent fields get a default value.

Usually you only need to specify and (and perhaps ).

/dev/mmcblk0p1 :0,6800,82
/dev/mmcblk0p1 0+ 6799 6800- 6963199+ 82 Linux swap / Solaris
/dev/mmcblk0p2 :,,83
/dev/mmcblk0p2 6800 7815 1016 1040384 83 Linux
/dev/mmcblk0p3 :
/dev/mmcblk0p3 0 - 0 0 0 Empty
/dev/mmcblk0p4 :
/dev/mmcblk0p4 0 - 0 0 0 Empty
New situation:
Units = mebibytes of 1048576 bytes, blocks of 1024 bytes, counting from 0

Device Boot Start End MiB #blocks Id System
/dev/mmcblk0p1 0+ 6799 6800- 6963199+ 82 Linux swap / Solaris
/dev/mmcblk0p2 6800 7815 1016 1040384 83 Linux
/dev/mmcblk0p3 0 - 0 0 0 Empty
/dev/mmcblk0p4 0 - 0 0 0 Empty
Warning: no primary partition is marked bootable (active)
This does not matter for LILO, but the DOS MBR will not boot this disk.
Do you want to write this to disk? [ynq] y
Successfully wrote the new partition table

Re-reading the partition table ...

If you created or changed a DOS partition, /dev/foo7, say, then use dd(1)
to zero the first 512 bytes: dd if=/dev/zero of=/dev/foo7 bs=512 count=1
(See fdisk(8).)


El tamaño de la tarjeta no es exactamente 8GB asi que ajusto el tamaño como sigue:

/dev/mmcblk0p1 :0,6800,82

Partición tipo 82 (linux), el resto lo dejo como swap, tipo 83

/dev/mmcblk0p2 :,,83

Ahora doy formato a la partición swap:

hopper800:~# mkswap /dev/mmcblk0

Checo con free si lo tengo lo hice bien.
hopper800:~# free
total used free shared buffers
Mem: 126796 113880 12916 0 7712
Swap: 1040376 0 1040376
Total: 1167172 113880 1053292

Aparentemete el sistema me reconoce el swap de 1GB. weeeeee! y además es de tipo swap cosa que le ahorrará mucho proceso por no pasar por el sistema de archivos FAT32.

Ahora cada vez que se reinicie el tablet se perderá este swap, lo mejor es configurarlo para que se monte automáticamente.

Lo podemos agregar en /etc/fstab para que lo carge cada vez que se reinicie.

hopper800:~# cat /etc/fstab 
rootfs / rootfs defaults,errors=remount-ro,noatime 0 0
/dev/mmcblk0p1 /media/mmc1 vfat rw,noauto,nodev,noexec,nosuid,utf8,uid=29999 0 0
/dev/mmcblk0p2 none swap sw 0 0


Luego agregar la siguiente línea antes del exit 0 en /etc/init.d/rcS :
swapon -a

OK swap listo, ahora el resto de la tarjeta la formateamos como ext2, más eficiente que FAT32.

Tenemos que instalar las librerías necesarias por que no están incluídas por default en el OS2008.Diablo.

hopper800:~# apt-get install e2fsprogs

Ahora podemos formatear nuestra partición
hopper800:~# mkfs.ext2 /dev/mmcblk0p1 
mke2fs 1.37 (21-Mar-2005)
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
870912 inodes, 1740799 blocks
87039 blocks (5.00%) reserved for the super user
First data block=0
54 block groups
32768 blocks per group, 32768 fragments per group
16128 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Writing inode tables: done
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 32 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.


Esta partición se monta automáticamente.

Espero que el desempeño de mi tablet mejore con esta configuración.

lunes, julio 21, 2008

Agregando librerías al proyecto en Maven 2

Desarrollando una aplicación para facturación digital, requiero de aplicar un sello de seguridad y la librería de la cual depende ese método no está en los repositorios de Maven2, si quiero mantener el control con Maven2 debo de incluir esta librería al repositorio local.

La librería es Not-Yet-Commons-SSL, que maneja varios protocolos de seguridad y llaves.

Paso 1:
Bajar el jar a un direcotorio temporal.

curl -C - -O http://juliusdavies.ca/commons-ssl/not-yet-commons-ssl-0.3.10.jar

*No tengo el wget a la mano en Leopard, así que lo bajé con curl

Paso 2:
Lo agregamos en el repositorio local de Maven2

mvn install:install-file -Dfile=not-yet-commons-ssl-0.3.10.jar -DgroupId=org.apache.commons -DartifactId=not-yet-commons-ssl -Dversion=0.3.10 -Dpackaging=jar

Paso 3:
Lo ponemos como dependiencia en nuestro proyecto

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>not-yet-commons-ssl</artifactId>
<version>0.3.10</version>
<scope>compile</scope>
</dependency>


Como uso Eclipse, tengo que volver a ejecutar mvn install eclipse:eclipse para que la librería sea instalada.
Listo!

viernes, mayo 23, 2008

DisplayTag y TableDecorator

DisplayTag es una librería comúnmente utilizada en Spring-MVC y facilita muchísimo el despliegue de información en tablas.

TableDecorator es una clase, que extendida, permite customizar la salida y presentación de las propiedades del objeto listado.

Por ejemplo. Tengo el siguiente objeto.

@Entity
public class Partida extends BaseObject implements Serializable {
private static final long serialVersionUID = -3392581174026724271L;

private Long id;
private User user;
private EntidadFederativa estado;
private Intervencion intervencion;
private FuenteFinanciamiento ff;
private CatalogoPartidas partida;
private Double recurso;
private Boolean compraconsolidada;

@Id @GeneratedValue(strategy=GenerationType.AUTO)
public Long getId() {
return id;
}

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="user_id",nullable=false)
public User getUser() {
return user;
}

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="estado_id",nullable=false)
public EntidadFederativa getEstado() {
return estado;
}

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="intervencion_id",nullable=false)
public Intervencion getIntervencion() {
return intervencion;
}

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="ff_id",nullable=false)
public FuenteFinanciamiento getFf() {
return ff;
}

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="partida_id",nullable=false)
public CatalogoPartidas getPartida() {
return partida;
}

public Double getRecurso() {
return recurso;
}

public Boolean getCompraconsolidada() {
return compraconsolidada;
}

public void setId(Long id) {
this.id = id;
}

public void setUser(User user) {
this.user = user;
}

public void setEstado(EntidadFederativa estado) {
this.estado = estado;
}

public void setIntervencion(Intervencion intervencion) {
this.intervencion = intervencion;
}

public void setFf(FuenteFinanciamiento ff) {
this.ff = ff;
}

public void setPartida(CatalogoPartidas partida) {
this.partida = partida;
}

public void setRecurso(Double recurso) {
this.recurso = recurso;
}

public void setCompraconsolidada(Boolean compraconsolidada) {
this.compraconsolidada = compraconsolidada;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
final Partida other = (Partida) obj;
if (compraconsolidada == null) {
if (other.compraconsolidada != null)
return false;
} else if (!compraconsolidada.equals(other.compraconsolidada))
return false;
if (estado == null) {
if (other.estado != null)
return false;
} else if (!estado.equals(other.estado))
return false;
if (ff == null) {
if (other.ff != null)
return false;
} else if (!ff.equals(other.ff))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (intervencion == null) {
if (other.intervencion != null)
return false;
} else if (!intervencion.equals(other.intervencion))
return false;
if (partida == null) {
if (other.partida != null)
return false;
} else if (!partida.equals(other.partida))
return false;
if (recurso == null) {
if (other.recurso != null)
return false;
} else if (!recurso.equals(other.recurso))
return false;
if (user == null) {
if (other.user != null)
return false;
} else if (!user.equals(other.user))
return false;
return true;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime
* result
+ ((compraconsolidada == null) ? 0 : compraconsolidada
.hashCode());
result = prime * result + ((estado == null) ? 0 : estado.hashCode());
result = prime * result + ((ff == null) ? 0 : ff.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result
+ ((intervencion == null) ? 0 : intervencion.hashCode());
result = prime * result + ((partida == null) ? 0 : partida.hashCode());
result = prime * result + ((recurso == null) ? 0 : recurso.hashCode());
result = prime * result + ((user == null) ? 0 : user.hashCode());
return result;
}

/**
* @see java.lang.Object#toString()
*/
public String toString() {
return new ToStringBuilder(this).append("compraconsolidada",
this.compraconsolidada).append("estado", this.estado).append(
"id", this.id).append("intervencion", this.intervencion)
.append("user", this.user).append("ff", this.ff).append(
"recurso", this.recurso)
.append("partida", this.partida).toString();
}

}

y la siguiente tabla


<display:table name="partidaList" class="table"
style="width: 100%" id="partidaList" export="true" pagesize="25"
>
<display:column property="id" media="csv excel xml pdf"
titleKey="partida.id" />
<display:column property="ff" titleKey="partida.ff" />
</display:column>
<display:column property="partida.id" titleKey="partida.id"></display:column>
<display:column property="partida.nombre" titleKey="partida.nombre"></display:column>
<display:column property="recurso" titleKey="partida.recurso"></display:column>
</display:table>



Me despliega los datos de manera muy simple


La columna del recurso (dinero) se ve mal.

Extendemos ahora la clase TableDecorator.


public class WrapperPartida extends TableDecorator {

/**
* DecimalFormat usado para dar formato a getRecurso().
*/
private DecimalFormat moneyFormat;

/**
* Constructor que asigna el formato, según documentación hace más eficiente la clase
*/
public WrapperPartida() {
super();

this.moneyFormat = new DecimalFormat("$ #,###,###.00");
}

/**
* Método para regresar nulos
*
* @return <code>null</code>
*/
public String getNullValue() {
return null;
}

/**
* Damos formato al dinero como un String $ #,###,###.format.
* Estamos tomando directamente del objeto tipo Partida, la propiedad recurso
* @return String
*/
public String getRecurso() {
return this.moneyFormat.format(((Partida) this.getCurrentRowObject())
.getRecurso());
}

}

Ahora agregamos el decorator a la tabla en la declaración y además ponemos el total de los recursos asignados.
  decorator="com.deuxbits.ssaspps.webapp.decorator.WrapperPartida"

Para totalizar los recursos, debemos declarar una variable que almacene los totales.
 varTotals="totals"

y activar la operacion en las columnas que queramos, para este ejemplo, sólo la de recurso.
<display:column property="recurso" titleKey="partida.recurso"
class="textright" total="true"></display:column>

Al final queremos mostrar el total ¿no? para ello utilizamos otro tag de la librería
  <display:footer>
<tr><td>&nbsp;</td><td>&nbsp;</td>
<td>Total</td>
<td class="textright">
<fmt:formatNumber value="${totals.column4}" type="currency" currencyCode="USD" />
</td>
<tr>
</display:footer>


Todo junto queda así:


<display:table name="partidaList" class="table" requestURI="" varTotals="totals"
style="width: 100%" id="partidaList" export="true" pagesize="25"
decorator="com.deuxbits.ssaspps.webapp.decorator.WrapperPartida">

<display:column property="recurso" titleKey="partida.recurso"
class="textright" total="true"></display:column>

<display:footer>
<tr><td>&nbsp;</td><td>&nbsp;</td>
<td>Total</td>
<td class="textright">
<fmt:formatNumber value="${totals.column4}" type="currency" currencyCode="USD" />
</td>
<tr>
</display:footer>

</display:table>


Mostrando la tabla de la siguiente manera:

miércoles, abril 23, 2008

SVN Keywords

Para poner datos como versión, autor y última actualización dentro de los fuentes es necesario decirle a svn.

Una manera rápida de hacerlo es creando un archivo .keywords en la raíz del proyecto con el siguiente contenido.
Author
Rev
Id
Date
Luego indicar a svn que queremos llevar esos keywords en todos los archivos del proyecto.
$ svn -R propset svn:keywords -F .keywords *

Agregamos una cabecera para los archivos fuentes parecida a esta
/**
* Archivo.java
* $Rev:: $: Revision of last commit
* $Author:: $: Author of last commit
* $Date$:
*/

Y damos un commit a todo el proyecto
$ svn ci -m"Setting keywords"

Mas información en svnbook

miércoles, abril 16, 2008

Multiples instancias de Tomcat

Para correr multiples instancias de Tomcat en el mismo servidor.

Tengo un Tomcat instalado en un RHEL5 en /opt/apache-tomcat-5.5.23 con una liga simbólica de /opt/tomcat5
Dentro de esta instalación en /opt/tomcat5/conf/server.xml tengo configurados varias aplicaciones. Teniendo varias desventajas.
  • Si una aplicación consume muchos recursos, todas las demás bajan de rendimiento.
  • Si una aplicación congela el JVM, las demás dejan de funcionar también.
No es práctico tener una instancia para cada aplicación, sobre todo cuando estas son pequeñas soluciones.

Ahora estoy desarrollando para Tomcat 6 y no quise migrar todas la aplicaciones ya configuradas y probadas en Tomcat 5.5.

Cree un directorio nuevo /var/tomcat y extraje el último Tomcat 6 en /var/tomcat/apache-tomcat-6.0.16
Cree otro directorio, exclusivo para mi instancia lo llamé serverB (pensando en que luego habrá un serverA y un serverC) y cree el siguiente árbol.

|-- conf
| |-- server.xml
| |-- tomcat-users.xml
| `-- web.xml
|-- logs
| `-- catalina.out
|-- temp
|-- webapps
`-- work


Ahora viene lo bueno.
Para correr la instancia y que no choque con la que ya está corriendo, hay que cambiar algunos puertos.

<Service name="Catalina">
<Connector port="8082" URIEncoding="UTF-8" />

<!-- Define an AJP 1.3 Connector on port 8020 -->
<Connector port="8020" enableLookups="false" redirectPort="9972" protocol="AJP/1.3"
URIEncoding="UTF-8" />

Creamos un script de arranque,
run

#!/bin/sh -e
JAVA_HOME="/usr/lib/jvm/java-1.5.0-sun-1.5.0.11"
JAVA_OPTS="-Xmx800m -Xms800m"
#CATALINA_HOME=/opt/tomcat5
CATALINA_HOME=/var/tomcat/apache-tomcat-6.0.16
CATALINA_BASE=/var/tomcat/serverB
export JAVA_HOME JAVA_OPTS CATALINA_HOME CATALINA_BASE

# -p preserves the environment (for $JAVA_HOME etc.)
# -s is required because tomcat's login shell is /bin/false
su -p -s /bin/sh tomcat -c "$CATALINA_HOME/bin/catalina.sh start"


y un script de parada
stop

#!/bin/sh -e
JAVA_HOME="/usr/lib/jvm/java-1.5.0-sun-1.5.0.11"
JAVA_OPTS="-Xmx800m -Xms800m"
CATALINA_HOME=/var/tomcat/apache-tomcat-6.0.16
CATALINA_BASE=/var/tomcat/serverB
export JAVA_HOME JAVA_OPTS CATALINA_HOME CATALINA_BASE

su -p tomcat -c "$CATALINA_HOME/bin/catalina.sh stop"


Ahora tenemos otro Tomcat corriendo en el mismo servidor con puertos diferentes. Creamos un workerb en /etc/httpd/conf/worker.properties apuntando al puerto 8020.

worker.workerb.port=8020
worker.workerb.host=localhost
worker.workerb.type=ajp13
worker.workerb.lbfactor=1


y listo!

martes, abril 15, 2008

Appfuse 2. Creando un nuevo proyecto Spring MVC

Appfuse tiene varias plantillas de proyecto, crearemos nuestro proyecto con la plantilla de Spring-MVC.

En el directorio que queremos crear el proyecto:
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-basic-spring -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.1 -DgroupId=com.miproyecto.app -DartifactId=proyecto

Las dos últimas opciones son las que cambiamos para personalizar nuestro proyecto.
-DgroupId=com.miproyecto.app es el paquete raíz que utilizaremos, todos los nuevos paquetes que genera Appfuse se crearán dentro de este.
-DartifactId=proyecto es el nombre de nuestro proyecto.

Luego de ejecutar el comando, Appfuse y Maven bajarán todas las librearías de las que depende el proyecto Spring-MVC, entre ellas, Velocity, Hibernate, Spring, AcegiSecurity,...

Para probarlo primero nois aseguramos de tener un MySQL funcionando con una base de datos lista para el proyecto y editamos el archivo pom.xml en la sección Database Settings que está casi al final para introducir las credenciales.

<!-- Database settings -->

<dbunit.dataTypeFactoryName>org.dbunit.dataset.datatype.DefaultDataTypeFactory</dbunit.dataTypeFactoryName>
<dbunit.operation.type>CLEAN_INSERT</dbunit.operation.type>
<hibernate.dialect>org.hibernate.dialect.MySQL5InnoDBDialect</hibernate.dialect>

<jdbc.groupId>mysql</jdbc.groupId>
<jdbc.artifactId>mysql-connector-java</jdbc.artifactId>
<jdbc.version>5.0.5</jdbc.version>

<jdbc.driverClassName>com.mysql.jdbc.Driver</jdbc.driverClassName>
<jdbc.url><![CDATA[jdbc:mysql://localhost/mibasededatos?createDatabaseIfNotExist=true&amp;useUnicode=true&amp;characterEncoding=utf-8]]></jdbc.url>
<jdbc.username>miusuario</jdbc.username>

<jdbc.password>miclave</jdbc.password>

Para vestir el sitio, editamos las leyendas en /src/main/resources/ApplicationResources.properties


Lo incluimos a un servidor de control de versiones

svn import miproyecto svn://somesvnserver.com/proyecto/trunk -m "Inicio de miproyecto"

Ahora bajamos el resto de los archivos del proyecto

mvn appfuse:full-source

Con este paso dejamos a un lado muchos archivos de appfuse que después podemos integrar al svn cuando creamos necesario.

Generamos los archivos para edición con Eclipse:

mvn install eclipse:eclipse

Ahora probamos nuestra aplicación:

mvn jetty:run-war

Abrimos nuestro navegador en http://localhost:8080 y debemos ver una ventana de bienvenida

¡Listo!


Otros comandos usados comúnmente:



  • Para crear rápidamente una entidad con CRUD
    mvn appfuse:gen -Dentity=Page

  • Para correr y probar todo el sistema
    mvn clean; mvn jetty:run-war -Dmaven.buildNumber.doCheck=false

  • Para correr un JUnit
    mvn test -Dtest=UserFormControllerTest

  • Para exportar la BD usando DBUnit
    mvn dbunit:export -Ddest=sample-data.xml


domingo, abril 06, 2008

SVN mini tutorial

Para importar un proyecto nuevo.

svn import miproyecto svn://somesvnserver.com/miproyecto/trunk -m "Inicio de proyecto"


En el servidor SVN debe de estar configurado un repositorio con el nombre de "miproyecto"

Para extraer un proyecto

svn co svn://somesvnserver.com/miproyecto/trunk miproyecto


Para guardar los cambios
svn ci -m "Resolviendo error de dependencias"

*siempre es bueno poner un comentario corto cada vez

Para agregar un archivo o directorio

svn add nuevo.jsp otro.jsp algo.java


Este minitutorial está incompleto y poco a poco lo iré terminando

viernes, abril 04, 2008

Appfuse 2


Appfuse 2 es una herramienta muy útil para desarrollar proyectos. Conserva las dependencias de las librerías usadas comunmente en los desarrollos con Java/Hibernate/Spring-MVC.

También ofrece un ambiente de desarrollo y pruebas autónomo, que obliga a los desarrolladores a seguir siempre los mismos pasos para crear nuevos objetos, relaciones y tablas.

Se apoya en Maven para la administración del proyecto usando su modelo POM (project object model) facilitando la compilación integrando un ambiente uniforme para desarrollo independiente de la plataforma (Linux, OSX y Windows), ofreciendo información técnica del proyecto (librerías de las que depende con versiones; parámetros de compilación para pruebas y despliegue) y promoviendo la aplicación de mejores prácticas (best practices).

Appfuse crea toda la estructura básica para iniciar un nuevo proyecto. Después de esto puede también crear las configuraciones necesarias para utilizar el IDE de preferencia. Soporta Eclipse, IntelliJIDEA y Netbeans.

sábado, febrero 23, 2008

Convirtiendo de CMYK a RGB

Muchas veces cuando se procesan imágenes para sitios web, los diseñadores guardan los archivos en formato CMYK, que es muy común para impresiones. Cuando nos llegas las gráficas, el Internet Explorer o el Firefox no las muestran mandando un error.

Primero detectar cuáles son los archivos que están con ese formato:

identify -verbose *.jpg | grep -B 6 "Colorspace: CMYK" | awk '/^Image/ {printf $2"\n"}'

Luego, hay que convertir esos archivos a RGB usando otro comando.

convert -colorspace RGB imgCMYK.jpg jpg:imgRGB.jpg

En sistemas Linux se puede automatizar de la siguiente forma:

Copiar todos los archivos jpg en formato CMYK a un directorio temporal y ejecutar el siguiente script.


#!/bin/bash

PROG="/usr/bin/convert -colorspace RGB "

find . -iname '*.jpg' -exec $PROG {} jpg:{} \;


Se podría construir un script que haga todo, detecte los archivos, los copie a un directorio temporal, los convierta y luego los copie al directorio original.

Los comandos gráficos (identify, convert) son de la librería ImageMagick.