Publicidad:
La Coctelera

un rato de sol

i will work harder, ja ja ja, no ahora en serio.

18 Diciembre 2007

JRuby y el classpath

Pruebas y más pruebas con JRuby. Hoy ha tocado darle vueltas al misteriosos funcionamiento del classpath cuando se ejecuta JRuby.

Imaginemos el siguiente escenario, creamos una nueva aplicación rails con JRuby y accedemos a la consola con un $jruby script/console.
Una vez lanzado la consola y cargado en entorno, preguntamos a jruby cual es el classpath de la aplicación.
Para ello podemos consultar el valor de $CLASSPATH para obtener una lista las rutas en el classpath de jruby:

 >> $CLASSPATH
 => ["file:/usr/local/jruby/jruby-1.1b1/lib/ruby/gems/1.8/gems/jruby-openssl-0.0.4/lib/jopenssl.jar", "file:/usr/local/jruby/jruby-1.1b1/lib/ruby/gems/1.8/gems/ActiveRecord-JDBC-0.5/lib/jdbc_adapter_internal.jar"]
 

Alternativamente podemos preguntarle a Java que nos diga el valor del classpath usando la clase System:

 >> java.lang.System.get_properties.get_property "java.class.path"
 => "/usr/local/jruby/jruby-1.1b1/lib/bsf.jar:/usr/local/jruby/jruby-1.1b1/lib/jruby.jar:/usr/local/jruby/jruby-1.1b1/lib/mysql-connector-java-5.1.5-bin.jar"
 

Hasta aquí nada raro, JRuby incluye en el classpath por defecto las rutas del classpath del entorno desde donde se ejecutando el proceso java que lanza el interprete JRuby y los ficheros localizados en el directorio $JRUBY_HOME/lib.

De este modo usando la función include_class podremos incluir cualquier clase java que pertenezca a las clases estandar del JDK o que se encuentren localizada en cualquiera de las rutas anteriores.

Supongamos que ahora queremos utilizar en nuestra aplicacion una serie de bibliotecas java para su uso desde rails. Una posible solución es crear un directorio como RAILS_ROOT/lib/classes, copiar ahí todos los jars con las clases que queramos utilizar y hacer desde environment.rb algo como:

 if RUBY_PLATFORM =~ /java/
 
   if File.exist?(File.join(RAILS_ROOT,'lib','classes'))
     Dir.open(File.join(RAILS_ROOT,'lib','classes')).each do |filename|
       if filename =~ /.jar|.zip/
         $CLASSPATH <<     File.expand_path(File.join(RAILS_ROOT,'lib','classes',filename))
       end
     end
   end
 
 end
 

Si ahora abrimos la consola y preguntamos por el valor de $CLASSPATH, obtendremos el comportamiento deseado:

 >> $CLASSPATH
 => ["file:/usr/local/jruby/jruby-1.1b1/lib/ruby/gems/1.8/gems/jruby-openssl-0.0.4/lib/jopenssl.jar", "file:/usr/local/jruby/jruby-1.1b1/lib/ruby/gems/1.8/gems/ActiveRecord-JDBC-0.5/lib/jdbc_adapter_internal.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/antlr-2.7.6.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/asm.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/Caja4HibernateModel.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/cglib-2.1.3.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/commons-collections-2.1.1.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/commons-logging-1.0.4.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/dom4j-1.6.1.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/ehcache-1.2.3.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/ejb3-persistence.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/hibernate-annotations.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/hibernate-commons-annotations.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/hibernate-entitymanager.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/hibernate-validator.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/hibernate3.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/javassist.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/jboss-archive-browsing.jar", "file:/Users/antoniogarrotehernandez/Desarrollo/Caja4JRuby/lib/classes/jta.jar"]
 

Y por supuesto podremos incluir las clases de cualquiera de esos jars.
Por ejemplo un POJO correspondiente a un modelo Hibernate:

 >> include_class "model.Post"
 => ["model.Post"]
 

Parece, que tenemos todo lo necesario para utilizar cualquier clase java que queramos. Intentemos obtener una entidad persistente de ese modelo hibernate usando las interfaces JPA de la especificación EJB3.0:

 >> javax.persistence.Persistence.create_entity_manager_factory "Caja4HibernateModelPU"
 NativeException: javax.persistence.PersistenceException: No Persistence provider for EntityManager named Caja4HibernateModelPU
 ...
 

Algo está fallando.
El problema es que con la técnica anterior hemos modificado el classpath de JRuby, pero no el classpath de la JVM que ejecuta JRuby. Esto provoca que cuando las clases de la implementación JPA de hibernate buscan el fichero persistence.xml con la configuración de la unidad de persistencia, no la encuentran en el classpath, a pesar de aparecer listada en el valor de $CLASSPATH.
Podemos comprobar esto preguntandole a la clase System por el classpath:

 >> java.lang.System.get_properties.get_property "java.class.path"
 => "/usr/local/jruby/jruby-1.1b1/lib/bsf.jar:/usr/local/jruby/jruby-1.1b1/lib/jruby.jar:/usr/local/jruby/jruby-1.1b1/lib/mysql-connector-java-5.1.5-bin.jar"
 >>
 

Ni rastro de los jars.

Las posibles soluciones a esto por ahora pasan por:

- copiar todos los jars $JRUBY_HOME/lib
- modificar el script jruby para que incluya las rutas
- fijar la variable CLASSPATH en la shell que lanza el script server

La solución por la que he optado es crear una tarea Rake que utilice una modificación del script anterior para que genere un script que fije el classpath y para modificar el classpath con un $source script/setup_classpath:

 desc 'setup classpath script for the application'
 task :setup_classpath_script => :environment do
   actual = ""
   jars = Dir.entries(File.join(RAILS_ROOT,"lib","classes")).map{ |f|
     if(f =~ /.jar|.zip/)
       puts "Added: #{File.expand_path(f,File.join(RAILS_ROOT,"lib","classes"))}"
       File.expand_path(f,File.join(RAILS_ROOT,"lib","classes"))
     else
       nil
     end
   }.compact
 
   if(ENV['CLASSPATH']!=nil && ENV['CLASSPATH']!="" && ENV['CLASSPATH']!=" " && jars.length>0)
     actual = ENV['CLASSPATH']+":"
   else
     actual = ENV['CLASSPATH']
   end
 
   to_append = ""
   if(jars.length>0)
     to_append = jars.join(":")
   end
 
   File.open(File.join(RAILS_ROOT,"script","setup_classpath"),"w") do |f|
     f << "export CLASSPATH=#{actual+to_append}"
   end
 
   `chmod uga+x #{File.expand_path(File.join(RAILS_ROOT,'script','setup_classpath'))}`
 
   puts "You can setup the classpath executing \n$source script/setup_classpath"
 end
 

De todas formas, esto no pasa de ser un parche temporal. (jruby 1.1b1) Además curiosamente, con esta técnica, la clase System devuelve el classpath correcto pero si preguntamos a $CLASSPATH ninguno de estos jars aparecerán listados. A pesar de que podremos crear las clases sin ningún problema:

 >> javax.persistence.Persistence.create_entity_manager_factory "Caja4HibernateModelPU"
 18-dic-2007 23:20:45 org.hibernate.cfg.annotations.Version 
 INFO: Hibernate Annotations 3.3.0.GA
 18-dic-2007 23:20:45 org.hibernate.cfg.Environment 
 INFO: Hibernate 3.2.5
 18-dic-2007 23:20:45 org.hibernate.cfg.Environment 
 INFO: hibernate.properties not found
 18-dic-2007 23:20:45 org.hibernate.cfg.Environment buildBytecodeProvider
 INFO: Bytecode provider name : cglib
 18-dic-2007 23:20:45 org.hibernate.cfg.Environment 
 INFO: using JDK 1.4 java.sql.Timestamp handling
 18-dic-2007 23:20:46 org.hibernate.ejb.Version 
 INFO: Hibernate EntityManager 3.3.1.GA
 18-dic-2007 23:20:46 org.hibernate.cfg.AnnotationBinder bindClass
 INFO: Binding entity from annotated class: model.Post
 18-dic-2007 23:20:46 org.hibernate.cfg.annotations.QueryBinder bindQuery
 INFO: Binding Named query: Post.findById => SELECT p FROM Post p WHERE p.id = :id
 18-dic-2007 23:20:46 org.hibernate.cfg.annotations.QueryBinder bindQuery
 INFO: Binding Named query: Post.findByTitle => SELECT p FROM Post p WHERE p.title = :title
 18-dic-2007 23:20:46 org.hibernate.cfg.annotations.QueryBinder bindQuery
 INFO: Binding Named query: Post.findByCreatedAt => SELECT p FROM Post p WHERE p.createdAt = :createdAt
 18-dic-2007 23:20:46 org.hibernate.cfg.annotations.EntityBinder bindTable
 INFO: Bind entity model.Post on table posts
 18-dic-2007 23:20:47 org.hibernate.validator.Version 
 INFO: Hibernate Validator 3.0.0.GA
 18-dic-2007 23:20:47 org.hibernate.connection.DriverManagerConnectionProvider configure
 INFO: Using Hibernate built-in connection pool (not for production use!)
 18-dic-2007 23:20:47 org.hibernate.connection.DriverManagerConnectionProvider configure
 INFO: Hibernate connection pool size: 20
 18-dic-2007 23:20:47 org.hibernate.connection.DriverManagerConnectionProvider configure
 INFO: autocommit mode: true
 18-dic-2007 23:20:47 org.hibernate.connection.DriverManagerConnectionProvider configure
 INFO: using driver: com.mysql.jdbc.Driver at URL: jdbc:mysql://localhost:3306/caja4
 18-dic-2007 23:20:47 org.hibernate.connection.DriverManagerConnectionProvider configure
 INFO: connection properties: {user=root, password=****, autocommit=true, release_mode=auto}
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: RDBMS: MySQL, version: 5.0.45
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: JDBC driver: MySQL-AB JDBC Driver, version: mysql-connector-java-5.1.5 ( Revision: ${svn.Revision} )
 18-dic-2007 23:20:48 org.hibernate.dialect.Dialect 
 INFO: Using dialect: org.hibernate.dialect.MySQLDialect
 18-dic-2007 23:20:48 org.hibernate.transaction.TransactionFactoryFactory buildTransactionFactory
 INFO: Transaction strategy: org.hibernate.transaction.JDBCTransactionFactory
 18-dic-2007 23:20:48 org.hibernate.transaction.TransactionManagerLookupFactory getTransactionManagerLookup
 INFO: No TransactionManagerLookup configured (in JTA environment, use of read-write or transactional second-level cache is not recommended)
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Automatic flush during beforeCompletion(): disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Automatic session close at end of transaction: disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: JDBC batch size: 15
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: JDBC batch updates for versioned data: disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Scrollable result sets: enabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: JDBC3 getGeneratedKeys(): enabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Connection release mode: auto
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Maximum outer join fetch depth: 2
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Default batch fetch size: 1
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Generate SQL with comments: disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Order SQL updates by primary key: disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Order SQL inserts for batching: disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory createQueryTranslatorFactory
 INFO: Query translator: org.hibernate.hql.ast.ASTQueryTranslatorFactory
 18-dic-2007 23:20:48 org.hibernate.hql.ast.ASTQueryTranslatorFactory 
 INFO: Using ASTQueryTranslatorFactory
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Query language substitutions: {}
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: JPA-QL strict compliance: enabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Second-level cache: enabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Query cache: disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory createCacheProvider
 INFO: Cache provider: org.hibernate.cache.NoCacheProvider
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Optimize cache for minimal puts: disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Structured second-level cache entries: disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Statistics: disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Deleted entity synthetic identifier rollback: disabled
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Default entity-mode: pojo
 18-dic-2007 23:20:48 org.hibernate.cfg.SettingsFactory buildSettings
 INFO: Named query checking : enabled
 18-dic-2007 23:20:48 org.hibernate.impl.SessionFactoryImpl 
 INFO: building session factory
 18-dic-2007 23:20:48 org.hibernate.impl.SessionFactoryObjectFactory addInstance
 INFO: Not binding factory to JNDI, no JNDI name configured
 18-dic-2007 23:20:48 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
 INFO: Running hbm2ddl schema update
 18-dic-2007 23:20:48 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
 INFO: fetching database metadata
 18-dic-2007 23:20:48 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
 INFO: updating schema
 18-dic-2007 23:20:49 org.hibernate.tool.hbm2ddl.TableMetadata 
 INFO: table found: caja4.posts
 18-dic-2007 23:20:49 org.hibernate.tool.hbm2ddl.TableMetadata 
 INFO: columns: [updated_at, title, created_at, body, id]
 18-dic-2007 23:20:49 org.hibernate.tool.hbm2ddl.TableMetadata 
 INFO: foreign keys: []
 18-dic-2007 23:20:49 org.hibernate.tool.hbm2ddl.TableMetadata 
 INFO: indexes: [primary]
 18-dic-2007 23:20:49 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
 INFO: schema update complete
 => #
 
Tags: ejb3 0, rails, jruby, jpa

servido por Antonio sin comentarios compártelo

sin comentarios · Escribe aquí tu comentario

Escribe tu comentario


Sobre mí

Avatar de Antonio

un rato de sol

Barcelona/Salamanca, España
ver perfil »
contacto »
Trabajador del metal y del acero, en la gloriosa XING AG, escribo software con el que poder ganarme el jornal. En mi tiempo libre sigo tecleando código de bonitos colores a medio camino entre lo sublime y lo terrible. Últimamente me gustan mucho los gatos.

Fotos

Antonio Garrote Hernández todavía no ha subido ninguna foto.

¡Anímale a hacerlo!

Buscar

suscríbete

Selecciona el agregador que utilices para suscribirte a este blog (también puedes obtener la URL de los feeds):

¿Qué es esto?

Crea tu blog gratis en La Coctelera