Blog do projektu Open Source JavaHotel

piątek, 9 maja 2014

Glassfish to JBoss (WildFly) migration

Introduction
I decided to migrate JavaHotel application to WildFly (former JBoss) keeping Glassfish version still alive. The reason was very simple. I was unable to call remote EJB deployed to Glassfish. It works from another Glassfish, also from Java standalone application  (following instructions under this link) but I failed to call EJB from another web container (Tomcat or Jetty). I tried to follow instructions from this blog. But the list of jars is valid for Glassfish 3.0, in 4.0 the jars are different and after spending a lot of time combining different sets of jars I finally gave up and switch to WildFly. I was successful but had to overcome some problems related not only to migration from one web container to another but also related to migration from EclipseLink JPA to Hibernate JPA.
Last but not least - to call remote EJB deployed to WifdFly server it is enough to add jboss-client.jar to classpath regardless of the client type.
I'm ignoring trivial tasks related to migration like: data source migration, EJB addressing, JNDI variables etc.
Several persistence-units in the single persistence.xml
Problem
I used to have a single persistence.xml file containing all persitence-units I'm using in different configurations. For instance "RESOURCE-LOCAL" persistence-unit for Tomcat and "JTA" fo JEE container. EclipseLinks at the moment of invoking Persistence.createEntityManagerFactory reads only persistence-unit requested, Hibernate reads the whole content of persistence.xml just failing if any of the persistence-units cannot be initialized.
Solution
Very simple : just split the single persistence.xml into several containing only persitence-unit necessary in the given context.
Different strategies for @Id @GeneratedValue(strategy=GenerationType.AUTO)
Problem
EclipseLink and Hibernate use different strategies for generating global object IDs. EclipseLink creates a separate table (sequence) and Hibernate JPA uses sequence hibernate_sequence (checked for Postresql and ApacheDerby). So after switching from one framework to another on existing table one can run into trouble (duplicate primary key) when both methods overlaps. It costed me a lot of time to discover the problem.
Solution
Use different @GeneratedValue strategy or do not switch framework.
@Lob column for PostgresSql
Problem
@Entity
public class LobRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    @Lob
    private byte b[];

    public Long getId() {
        return id;
    }

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

    /**
     * @return the b
     */
    public byte[] getB() {
        return b;
    }

    /**
     * @param b the b to set
     */
    public void setB(byte[] b) {
        this.b = b;
    }
    
}
Unfortunately, Postgresql does not have direct BLOB or LOB column type. EclipseLink translates the @Lob column to bytea (what I expected) but Hiberbate generates oid column type which causes a lot of trouble.
Solution
There is no universal solution to this problem.
  1. Remove @Lob annotation. Both EclipseLink and Hibernate generates bytea column type. But after removing this annotation for ApacheDerby this column is generated as VARCHAR () FOR BIT DATA (not BLOB) which is not valid
  2. Add direct specification @Column(columnDefinition="bytea". But it will not work for ApacheDerby (or any other database, it is PostreSql specific)
  3. Adjust annotation any time database or framework is changed. 
Hiberate does not like hierarchical references in DELETE command
Problem
@Entity
public class CountryRecord implements Serializable {
....
}

@Entity
public class ProvinceRecord implements Serializable {
...    
    @JoinColumn(nullable=false)
    private CountryRecord country;
}

@Entity
)
public class CityRecord implements Serializable {

...
    @JoinColumn(nullable = false)
    private ProvinceRecord prov;
...
}

@NamedQueries({
    @NamedQuery(name = "removeCities", query = "DELETE FROM CityRecord r WHERE r.prov.country= ?1")
p.country= ?1)")
}

EclipseLink does not complain but Hibernate throws an exception
ERROR: HHH000177: Error in named query: removeCities
org.hibernate.QueryException: could not resolve property: country of: teste.entity.CityRecord [DELETE FROM teste.entity.CityRecord r WHERE r.prov.country= ?1]
 at org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:83)
 at org.hibernate.persister.entity.AbstractPropertyMapping.toType(AbstractPropertyMapping.java:77)

What is more intriguing - Hibernate has nothing against this type of hierarchy in SELECT statement.
Solution
Replace hierarchical reference by nested SELECT.
@Entity
@NamedQueries({
    @NamedQuery(name = "removeCities", query = "DELETE FROM CityRecord r WHERE r.prov in (SELECT p FROM ProvinceRecord p WHERE p.country= ?1)")
}

Hibernate does not like duplicated named query

@Entity
@NamedQueries({
    @NamedQuery(name = "removeCities", query = "DELETE FROM CityRecord r WHERE r.prov in (SELECT p FROM ProvinceRecord p WHERE p.country= ?1)"),
    @NamedQuery(name = "removeCities", query = "DELETE FROM CityRecord r WHERE r.prov in (SELECT p FROM ProvinceRecord p WHERE p.country= 
}

EclipseLink accepts it.  
Different default column naming for class references
Problem
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "name", "company" }))
public class PersonRecord implements Serializable {


    private String name;

    @JoinColumn(nullable = false)
    private CompanyRecord company;
,,,,
}

It works for Hibernate but failed for EclipseLink. This is because Hibernate generates company as column name for company reference class but EclipseLink generates company_id column name so the unique constraint fails. After replacing company with company_id in the UniqueConstraint annotation the result is opposite.
Solution
Unfortunately, Hibernate ignores name parameter in JoinColumn annotation. To redefine column name it is necessary to add Column annotation. But EclipseLink does not accept both JoinColumn and Column annotation for the same field. So the only solution for both frameworks is to add name parameter to JoinColumn annotation which is ignored by Hibernate but works for EclipseLink
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = { "name", "company" }))
public class PersonRecord implements Serializable {

    private String name;

    @JoinColumn(nullable = false,name="company")
    private CompanyRecord company;

Different resource filepath
Problem
Assume we want to deploy a simple .war to web container containing  resource/param.txt and want to read this file.
That's pretty simple just:

public class Listener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("Start");
        URL u = Listener.class.getClassLoader().getResource("resource/param.txt");
        System.out.println("U=" + u.getFile());
...
But sometimes is was convenient for me to read this file using :

InputStream i = u.openStream 
and sometimes to read this resource like regular file in the file system using getFile method.
There is no problem on all webcontainers (even Google App Engine infrastructure). For instance, on Tomcat getFile() method returns:

U=/home/hotel/.netbeans/8.0/apache-tomcat-8.0.3.0_base/webapps/ReadRes/WEB-INF/classes/resource/param.txt
But on WildFly it returns something like:
U=/content/ReadRes.war/WEB-INF/classes/resource/param.txt
It does not reflect the place where resource is unpacked, it should be something like:
.../{standalone}/tmp/vfs/temp/temp577a2348203a1442/content-34f69a82c7d2649f/WEB-INF/classes/resource/param.txt
Solution
Of course, it is necessary to read resource file using OpenStream (or any other method) and read file in the file system using file path. 
But I cannot use it for Jython code deployed as a resource which needs file path and there is no simple way to overcome it. The only solution is to deploy Jython code outside .war file and use directory file path.

Brak komentarzy:

Publikowanie komentarza