Comercialización:
ventas@acsinet-solutions.com
Servicios Educacionales:
educacional@acsinet-solutions.com
Servicios de Consultoría:
consultoría@acsinet-solutions.com
Comercialización:
soporte@acsinet-solutions.com

Teléfonos:
58 93 36 63
Oficina:
58 93 36 63
Fax:
58 93 36 63
Celulares:
044 55 54 56 77 45







Mapeo Sencillo

La siguiente serie de documentos tiene por objetivo presentar ejemplos de tipos de mapeos que se pueden realizar con Hibernate 3 y Java Persistence API ( JPA ). La intención es cubrir paulatinamente con ejemplos desde los mapeos más sencillos, hasta los más complicados.

En este primer documento se muestra el manejo manual de sesiones y transacciones; escenario que se simplifica en otros tutoriales mediante el manejo de dependency injection y de transacciones declarativas.



Mapeo Sencillo

Este primer documento presenta un mapeo sencillo de una clase ( FacturaDTO ) hacia una tabla en base de datos ( factura1 ). Las clases de código que se manejarán en este tutorial se pueden obtener a partir de este archivo comprimido; y se presentan visualmente en la siguiente imagen:

Las clases que se ejecutarán en esta aplicación sencilla se almacenan en este caso en el paquete de java single_table_simple_key. Obviamente, una aplicación de producción no llevaría nombres de paquetes como éste, pero para efectos de poder organizar todos los ejemplos de esta serie en un solo proyecto java, esta organización resulta práctica.

Dentro de este paquete se definen los siguientes archivos:

Fuera del paquete single_table_simple_key se definen los archivos para la configuración de facilidades de bitácora commons-logging.properties y log4j.properties. El objetivo de estos archivos es poder direccionar la salida de bitácora producida por la aplicación y sus componentes, y también poder configurar el nivel de filtrado de los mensajes que se desplegarán. Se puede experimentar con la configuración de estos archivos si se desea, pero con el objetivo de mantener la discusión lo más simple posible, no se entrará a detalle acerca del funcionamiento de los mismos a lo largo de este documento.

Además de estos archivos de código, se requieren una serie de librerías para poder hacer funcionar la aplicación. Las librerías que se utilizaron para hacer funcionar esta aplicación se pueden obtener de aquí, y a las mismas habría que agregar las librerías estándares de J2EE 1.4 ( o superior ) y los drivers de la base de datos que se esté utilizando.

La siguiente imagen reorganiza algunos de los archivos mostrados anteriormente, en términos de la funcionalidad que tendrán como parte de la aplicación:


Las siguientes secciones desglosan el contenido cada uno de estos archivos.




Base de Datos: create.sql


La estructura de la tabla factura1 es muy sencilla:


  CREATE TABLE factura1
    (
    fa_id SERIAL NOT NULL,
    fa_version INT NOT NULL,
    fa_name VARCHAR(40) NOT NULL,

    PRIMARY KEY( fa_id )
    );

Como puede verse, se trata de una tabla con tres campos:

Se pueden adicionar columnas adicionales con otros tipos de datos si así se desea. En este caso se mantiene la tabla lo más sencilla posible con el fin de facilitar las explicaciones.

El contenido de este archivo deberá ser ejecutado manualmente sobre la base de datos que se vaya a utilizar utilizando las herramientas propias de dicho manejador.




Configuración de Hibernate: hibernate.cfg.xml


El archivo de configuración de Hibernate tiene la siguiente estructura:


   <?xml version='1.0' encoding='utf-8'?>

   <!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

  <hibernate-configuration>

    <session-factory>

    <property name="hibernate.connection.driver_class">
      driver</property>
    <property name="hibernate.connection.url">
      url</property>
    <property name="hibernate.connection.username">
      login</property>
    <property name="hibernate.connection.password">
      password</property>

    <property name="hibernate.connection.pool_size">
      1</property>
    <property name="hibernate.dialect">
      org.hibernate.dialect.XXXDialect</property>
    <property name="transaction.factory_class">
       org.hibernate.transaction.JDBCTransactionFactory
    </property>

    <mapping class="single_table_simple_key.FacturaDTO"/>

    </session-factory>

   </hibernate-configuration>

En el código anterior, se deben sustituir los valores driver, url, login y password por los correspondientes a la conexión que se esté realizando en ese momento. El valor org.hibernate.dialect.XXXDialect también debe personalizarse a que corresponda con la clase que define el dialecto de la base de datos que se está utilizando. Revisar esta referencia para verificar cuáles son los dialectos disponibles.

La configuración de Hibernate listada con anterioridad puede visualizarse como separada en dos partes: propiedades de configuración y mapeos:


Las propiedades de configuración se refieren a:




Mapeo: FacturaDTO.java



El código de la clase FacturaDTO es el siguiente:


    package single_table_simple_key;

    import org.hibernate.annotations.Proxy;

    import static javax.persistence.GenerationType.IDENTITY;

    import javax.persistence.*;

    import java.io.Serializable;

    /**
    * Mapeo simple de un objeto factura
    */
    @Entity @Table( name="factura1" )
    @Proxy( lazy=false )
    public class FacturaDTO implements Serializable
      {
       private int _id;

       private int _version;

       private String _nombre;

       /////////////////////////////////////////////////////////
       // Getter Persistent Methods

       @Id @Column( name="fa_id" )
       @GeneratedValue( strategy=IDENTITY )
       public int getId()
         { return _id; }

       @Version @Column( name="fa_version" )
       public int getVersion()
         { return _version; }

       @Column( name="fa_name" )
       public String getNombre()
         { return _nombre; }

       /////////////////////////////////////////////////////////
       // Setter Methods

       public void setId( int id )
         { _id = id; }

       public void setVersion( int version )
         { _version = version; }

       public void setNombre( String nombre )
         { _ nombre = nombre; }

      }

Como puede verse, esta es una clase sencilla que almacena tres propiedades, cada una de las cuales se refiere a una columna en la tabla factura1. Visualmente se puede ver esta relación como sigue:

El mapeo de este código java contra la base de datos se desglosa en la siguiente tabla:

FacturaDTO.java create.sql

@Entity @Table( name="factura1" )
@Proxy( lazy=false )
public class FacturaDTO
implements Serializable

CREATE TABLE factura1

@Id @Column( name="fa_id" )
@GeneratedValue( strategy=IDENTITY )
public int getId()
{ return _id; }

fa_id SERIAL NOT NULL,
PRIMARY KEY( fa_id )

@Version @Column( name="fa_version" )
public int getVersion()
{ return _version; }

fa_version INT NOT NULL

@Column( name="fa_name" )
public String getNombre()
{ return _nombre; }

fa_name VARCHAR(40) NOT NULL




Mapeo de la Tabla



    @Entity @Table( name="factura1" )
    @Proxy( lazy=false )
    public class FacturaDTO implements Serializable
    {
         ...
    }

La configuración de la tabla hace uso en este caso de las siguientes anotaciones:



Mapeo de la Llave Primaria




        @Id @Column( name="fa_id" )
        @GeneratedValue( strategy=IDENTITY )
         public int getId()
        { return _id; }

La configuración del campo que implementa la llave primaria hace uso en este caso de las siguientes anotaciones:



Mapeo del Campo de Versión ( Opcional )



        @Version @Column( name="fa_version" )
        public int getVersion()
        { return _version; }

El campo de versión es una columna numérica o de tipo timestamp, y se denota por medio de la anotación javax.persistence.Version. Este tipo de campos definen una columna para el manejo de bloqueos de registros optimistas. Un bloque optimista es una técnica común en aplicaciones web para minimizar el número de actualizaciones concurrentes hacia un mismo registro. En un escenario de este tipo cada registro maneja un contador ( campo de versión ) que es actualizado con cada escritura hacia los datos de dicho registro.

Una operación de escritura sobre el registro siempre verifica primero que esta marca coincida con la lectura anterior que realizó un usuario sobre dicho registro. Si las marcas coinciden, el registro se actualiza y el número de versión se incrementa.

En caso de que el número de versión de la actualización no coincida con el número de versión en el registro de la base de datos, se lanza una excepción de tipo org.hibernate.StaleObjectStateException.



      [ERROR]27/08/2007 15:24:42,844 (AbstractFlushingEventListener):
      Could not synchronize database state with session
      org.hibernate.StaleObjectStateException:
      Row was updated or deleted
      by another transaction (or unsaved-value mapping was incorrect):
      [single_table_simple_key.FacturaDTO#2]
      at org.hibernate.persister.entity.AbstractEntityPersister.check
      (AbstractEntityPersister.java:1680)

Hibernate realiza esta verificación y actualización del campo de versión de forma transparente al usuario.




Mapeo de Campos Sencillos




        @Column( name="fa_name" )
        public String getNombre()
        { return _nombre; }

La anotación javax.persistence.Column ( @Column ) permite personalizar el nombre de una columna hacia la base de datos. Si no se definiera esta anotación para una propiedad, ésta se buscaría mapear contra la base de datos utilizando el nombre por defecto para la propiedad; en este caso “nombre”.

En el caso que una propiedad del componente no se deseara mapear hacia la base de datos, se tendría que marcar dicha propiedad con la anotación javax.persistence.Transient, por ejemplo:


        @Transient // no mapeada
        public String getNombre()
        { return _nombre; }



Prueba Ejecutable: Test.java



El siguiento fragmento de código muestra la parte inicial de la clase de prueba:


    package single_table_simple_key;

    import org.hibernate.*;
    import org.hibernate.cfg.AnnotationConfiguration;
    import org.hibernate.criterion.Expression;

    import org.apache.log4j.Logger;

    import java.util.List;

    /**
     * Manipulación de una tabla simple con llave sencilla.
     */
    public final class Test
     {
       private static Logger _logger = Logger.getLogger( Test.class );

       public static void main(String[] args)
       {
         try
         { new Test().execute(); }
         catch( Exception e )
         { _logger.error( "Error:", e ); }
       }

       ...
     }

Aquí simplemente se crea una instancia para escritura a bitácora llamada _logger, y se ejecuta el método execute() de la clase actual. Todos los errores que se produzcan se mandarán a bitácora.



Creación de la Fábrica de Sesiones de Hibernate

La primera parte del método execute() de la clase Test se lista a continuación:


    /**
     * Método de ejecución de altas, bajas, cambios y consultas.
     */
     private void execute() throws Exception
     {
         SessionFactory factory = createSessionFactory();

         ...
     }

La clase org.hibernate.SessionFactory es el objeto que permitirá crear sesiones de Hibernate hacia la base de datos.

El método createSessionFactory() se define como sigue:


    private SessionFactory createSessionFactory()
    throws HibernateException
    {
      AnnotationConfiguration configuration =
        new AnnotationConfiguration();

      configuration.configure(
         "/single_table_simple_key/hibernate.cfg.xml" );

      return configuration.buildSessionFactory();

    }

Esto simplemente construye la configuración de Hibernate a partir del archivo hibernate.cfg.xml que se discutió con anterioridad.





Inserción de Datos

Posteriormente, se realiza la inserción de datos como parte del método execute() de la clase Test, y se obtiene el valor autogenerado para el nuevo registro:


    /**
     * Método de ejecución de altas, bajas, cambios y consultas.
     */
     private void execute() throws Exception
     {
         SessionFactory factory = createSessionFactory();

         int id = insertObject( factory );

         ...
     }

El método insertObject() crear una nueva instancia de tipo FacturaDTO, abre una sesión de Hibernate a partir de la fábrica de sesiones, abre una transacción sobre dicha sesión, y por último realiza la inserción del dato a través del método save() de la misma sesión.

El listado completo de esta funcionalidad es el siguiente:


    private int insertObject( SessionFactory factory )
     {
         FacturaDTO factura = new FacturaDTO();

         factura.setNombre( "factura1" );

         Session session = factory.openSession();

         try
         { insertObject( session, factura ); }
         finally
         { session.close(); }

         return factura.getId();
     }

     private void insertObject( Session session, FacturaDTO factura )
     {
         boolean commit = false;
         Transaction transaction = session.beginTransaction();

         try
         {
           session.save( factura );

           commit = true;
         }
         finally
         {
           if( commit )
             transaction.commit();
           else
             transaction.rollback();
           }
         }

Como puede verse, se hace uso de un bloque try/finally para asegurar el cierre de la sesión de Hibernate que se ha abierto ( org.hibernate.Session ), y también se hace uso de un bloque try/finally para asegurar el cierre de la transacción ( org.hibernate.Transaction ).

Nótese que el identificador generado por la base de datos está disponible automáticamente por medio del método getId() de la clase una vez que la inserción se ha realizado.



Lectura de Datos

El siguiente paso es mostrar cómo se puede hacer la lectura de un registro, mediante la obtención del registro que se ha insertado como parte del método execute() de la clase Test:


    /**
     * Método de ejecución de altas, bajas, cambios y consultas.
     */
     private void execute() throws Exception
     {
         SessionFactory factory = createSessionFactory();

         int id = insertObject( factory );

         FacturaDTO factura = getObject( factory, id );

         _logger.debug( "Nombre inicial: " + factura.getNombre() );

         ...
     }

El método getObject() utiliza una sesión de Hibernate para obtener una instancia de tipo FacturaDTO a partir de su identificador por medio del método load(). En este caso, como se trata de una operación de solo lectura, no se utilizan transacciones:

El listado completo de esta funcionalidad es el siguiente:


    private FacturaDTO getObject( SessionFactory factory, int id )
     {
         Session session = factory.openSession();

         try
         { return ( FacturaDTO )session.load(
           FacturaDTO.class, id ); }
         finally
         { session.close(); }
     }

El método load() de la interfase org.hibernate.Session recibe el nombre de la clase, y el identificador único del dato a cargar. Si se quisiera cargar un dato de un identificador no válido, se lanzaría una excepción de tipo org.hibernate.ObjectNotFoundException:


    [ERROR]27/08/2007 16:49:14,238 (Test): Error:
    org.hibernate.ObjectNotFoundException:
      No row with the given identifier
     exists: [single_table_simple_key.FacturaDTO#2000]
    at org.hibernate.impl.SessionFactoryImpl$1.handleEntityNotFound
     (SessionFactoryImpl.java:372)

La interfase org.hibernate.Session define un método get() que puede manejarse en lugar de load() y que regresa null si no encuentra el objeto asociado a la llave solicitada, en lugar de lanzar excepciones.



Actualización de Datos

Después se realiza una actualización de datos:


    /**
     * Método de ejecución de altas, bajas, cambios y consultas.
     */
     private void execute() throws Exception
     {
         SessionFactory factory = createSessionFactory();

         int id = insertObject( factory );

         FacturaDTO factura = getObject( factory, id );

         _logger.debug( "Nombre inicial: " + factura.getNombre() );

         updateObject( factory, factura );

         ...
     }

El método updateObject() modifica un campo del DTO ( que actualmente se encuentra en modo desconectado ), y por medio del método update() de una sesión de Hibernate se realiza la actualización en la base de datos en el contexto de una transacción:

El listado completo de esta funcionalidad es el siguiente:


    private void updateObject(
      SessionFactory factory, FacturaDTO factura )
     {
         factura.setNombre( "factura2" );

         Session session = factory.openSession();

         try
         { updateObject( session, factura ); }
         finally
         { session.close(); }
         }

     private void updateObject(
      Session session, FacturaDTO factura )
     {
         boolean commit = false;
         Transaction transaction = session.beginTransaction();

         try
         {
           session.update( factura );

           commit = true;
         }
         finally
         {
           if( commit )
             transaction.commit();
           else
             transaction.rollback();
         }
     }

La estructura de este código es muy similar a la de inserción. La verificación del campo de versión del DTO proporcionado ( en caso de haberse definido alguno ) contra la base de datos se realiza de forma automática como parte de la ejecución del método update().

En este caso el uso del método update() es importante para la actualización, ya que se está trabajando con un componente que se encontraba desconectado de la sesión; esto significa que la sesión a través de la cual el objeto fue leído de base de datos ya había sido cerrada con anterioridad, como será el caso en aplicaciones web donde cada uno de estos pasos se realiza en una pantalla distinta de la interfase de usuario.

Si se está trabajando con un objeto que se encuentra relacionado con una sesión, la simple modificación del estado de este objeto ( como cuando se ejecutan sus métodos setter ) ocasionará actualizaciones hacia la base de datos al cerrarse la transacción actual o ejecutar el método flush() definido en la interfase org.hibernate.Session. Un ejemplo de este tipo de actualizaciones sería el siguiente:


    private void updateObject(
      Session session, int id, String nombre )
     {
         boolean commit = false;
         Transaction transaction = session.beginTransaction();

         try
         {
           FacturaDTO factura =
           ( FacturaDTO )session.load( FacturaDTO.class, id );

           factura.setNombre( nombre );
          // se altera el estado del objeto

           // no se requiere ejecutar el método update()

           commit = true;
         }
         finally
         {
           if( commit )
             transaction.commit();
           else
             transaction.rollback();
         }
       }


Lectura de Datos por Criteria

Una vez actualizada la información en base de datos, el programa de prueba realiza de nuevo la lectura del registro pero ahora utilizando el método queryObject():


    /**
     * Método de ejecución de altas, bajas, cambios y consultas.
     */
     private void execute() throws Exception
     {
         SessionFactory factory = createSessionFactory();

         int id = insertObject( factory );

         FacturaDTO factura = getObject( factory, id );

         _logger.debug( "Nombre inicial: " + factura.getNombre() );

         updateObject( factory, factura );

         factura = queryObject( factory, id );

         _logger.debug( "Nombre final: " + factura.getNombre() );

         ...

     }

El método queryObject() realiza la consulta de un dato utilizando la interfase org.hibernate.Criteria. Esta interfase permite construir consultas hacia la base de datos utilizando una perspectiva orientada a objetos:

El listado completo de esta funcionalidad es el siguiente:


    private FacturaDTO queryObject(
      SessionFactory factory, int id )
     {
         Session session = factory.openSession();

         try
         {
             Criteria criteria = session.createCriteria(
              FacturaDTO.class );

             criteria.add( Expression.eq( "id", id ) );

             List results = criteria.list();

             return ( FacturaDTO )results.get( 0 );
         }
         finally
         { session.close(); }
     }

En este caso se construye un objeto Criteria para el tipo de dato FacturaDTO por medio del método createCriteria() de la interfase Session. Posteriormente se agrega una expresión a la búsqueda que limita los resultados a únicamente aquellos valores cuya propiedad “id” corresponda al identificador pasado como parámetro.

El método list() permite regresar un listado de todos aquellos objetos que cumplan con los criterios de búsqueda, y de este listado obtenemos el primer elemento y lo regresamos.

En lugar del método list(), también se puede utilizar el método uniqueResult() cuando se realizan consultar que sabemos que regresan un solo resultado. En este caso el método anteriorquedaría como sigue:


    private FacturaDTO queryObject(
      SessionFactory factory, int id )
    {
       Session session = factory.openSession();

        try
         {
           Criteria criteria = session.createCriteria(
             FacturaDTO.class );

           criteria.add( Expression.eq( "id", id ) );

           return ( FacturaDTO )criteria.uniqueResult();
        }
         finally
         { session.close(); }
     }


Lectura de Datos por Query ( HQL )

Otra manera de realizar lectura de datos es utilizando HQL ( Hibernate Query Language ). El programa de pruebas hace uso de esta facilidad en su siguiente paso:


    /**
     * Método de ejecución de altas, bajas, cambios y consultas.
     */
     private void execute() throws Exception
     {
         SessionFactory factory = createSessionFactory();

         int id = insertObject( factory );

         FacturaDTO factura = getObject( factory, id );

         _logger.debug( "Nombre inicial: " + factura.getNombre() );

         updateObject( factory, factura );

         factura = queryObject( factory, id );

         _logger.debug( "Nombre final: " + factura.getNombre() );

         int max = findMaxKey( factory );

         _logger.debug( "max key: " + max );

         ...
     }

El método findMaxKey() realiza la consulta de un dato utilizando la interfase org.hibernate.Query. Esta interfase permite construir consultas hacia la base de datos utilizando HQL. Las búsquedas pueden regresar colecciones completas de objetos, o valores individuales; en este caso se regresa un valor entero simple:

El listado completo de esta funcionalidad es el siguiente:


    private int findMaxKey( SessionFactory factory )
     {
         Session session = factory.openSession();

         try
         {
           Query query = session.createQuery
           ( "SELECT MAX( factura.id ) FROM FacturaDTO factura" );

           List results = query.list();

           return ( Integer )results.get( 0 );

         }
         finally
         { session.close(); }
     }

En este caso se lee el valor máximo de la propiedad id, y se regresa. De la misma manera que con Criteria, este método puede reescribirse para hacer uso de uniqueResult():


    private int findMaxKey( SessionFactory factory )
     {
       Session session = factory.openSession();

       try
       {
         Query query = session.createQuery
           ( "SELECT MAX( factura.id ) FROM FacturaDTO factura" );

         return ( Integer )query.uniqueResult();
       }
       finally
       { session.close(); }
       }

HQL es un lenguaje de consultas bastante poderoso, y una discusión a fondo del mismo supera el alcance de este documento; sin embargo, será presentado a detalle en otros tutoriales posteriores.



Borrado de Datos

Finalmente, se realiza el borrado del registro que fue insertado; el listado completo del método execute() es el siguiente:


    /**
     * Método de ejecución de altas, bajas, cambios y consultas.
     */
     private void execute() throws Exception
     {
       SessionFactory factory = createSessionFactory();

       int id = insertObject( factory );

       FacturaDTO factura = getObject( factory, id );

       _logger.debug( "Nombre inicial: " + factura.getNombre() );

       updateObject( factory, factura );

       factura = queryObject( factory, id );

       _logger.debug( "Nombre final: " + factura.getNombre() );

       int max = findMaxKey( factory );

       _logger.debug( "max key: " + max );

       deleteObject( factory, id );
     }

El método deleteObject() realiza el borrado del registro utilizando las facilidades de borrado presentes en HQL, y disponibles a través de org.hibernate.Query. En ocasiones es conveniente realizar actualizaciones y borrados a través de este tipo de facilidades:

El listado completo de esta funcionalidad es el siguiente:


    private void deleteObject( SessionFactory factory, int id )
     {
       Session session = factory.openSession();

       try
         { deleteObject( session, id ); }
       finally
         { session.close(); }
     }

     private void deleteObject( Session session, Integer pk )
     {
       boolean commit = false;
       Transaction transaction = session.beginTransaction();

       try
       {
         Query query = session.createQuery
         ( "DELETE FROM FacturaDTO factura WHERE factura.id = :id" );

         query.setInteger( "id", pk );

         query.executeUpdate();

         commit = true;
       }
       finally
       {
         if( commit )
           transaction.commit();
         else
           transaction.rollback();
       }
     }

Nótese que el borrado se ejecuta en el contexto de una transacción, y se define el borrado a través del código HQL "DELETE FROM FacturaDTO factura WHERE factura.id = :id". En este código, “:id” representa un parámetro variable que se establece en la siguiente línea de código a través del método setInteger(). El borrado es enviado a la base de datos a través del método executeUpdate() de la interfase org.hibernate.Query.

Una manera alternativa de realizar borrados es por medio del método delete() de la interfase org.hibernate.Session.

En este caso, primero se requiere realizar una consulta del dato, y posteriormente aplicar este método para borrarlo. El segundo método deleteObject() en el listado anterior puede ser reescrito de la siguiente forma de acuerdo a esta aproximación:


    private void deleteObject( Session session, Integer pk )
     {
       boolean commit = false;
       Transaction transaction = session.beginTransaction();

       try
         {
           FacturaDTO factura =
           ( FacturaDTO )session.load( FacturaDTO.class, pk );

           session.delete( factura );

           commit = true;
         }
         finally
         {
           if( commit )
             transaction.commit();
           else
             transaction.rollback();
         }
       }


Conclusión

De esta manera finaliza la presentación de un programa Java SE que realiza un mapeo básico con Hibernate, y que implementa las operaciones básicas de inserción, búsqueda, actualización y borrado sobre registros, haciendo manejo manual de conexiones y transacciones.

}Los siguientes tutoriales en esta serie presentarán manejo más sofisticado de servicios, con el fin de simplificar el código referente al manejo de conexiones y transacciones; y poder trabajar fragmentos de este código de forma declarativa. También se presentarán mapeos más avanzados, y relaciones entre entidades.



Regresar






Copyright © ACSINET S.A. de C.V. 2007 http://www.acsinet-solutions.com

Derechos Reservados de este Material:
Atribución-No Comercial-Licenciamiento Recíproco 2.5 México

http://creativecommons.org/licenses/by-nc-sa/2.5/mx/

Eres libre de: a) copiar, distribuir y comunicar públicamente la obra, b) hacer obras derivadas. Bajo las condiciones siguientes: a) Atribución. Debes reconocer la autoría de la obra en los términos especificados por el propio autor o licenciante. b) No comercial. No puedes utilizar esta obra para fines comerciales. c) Licenciamiento Recíproco. Si alteras, transformas o creas una obra a partir de esta obra, solo podrás distribuir la obra resultante bajo una licencia igual a ésta.
Al reutilizar o distribuir la obra, tiene que dejar bien claro los términos de la licencia de esta obra. Alguna de estas condiciones puede no aplicarse si se obtiene el permiso del titular de los derechos de autor. Nada en esta licencia menoscaba o restringe los derechos morales del autor.

Java and all Java-based trademarks and logos are trademarks of Sun Microsystems in the United States and/or other countries.

Other products and services are trademarks of their respective owners.




Copyright © ACSINET S.A. de C.V. 2000 - 2007 Derechos Reservados.


Java™ is a trademark of Sun Microsystems in the United States and/or other countries All other products and services are trademarks of their respective owners.