Showing posts with label Rant. Show all posts
Showing posts with label Rant. Show all posts

Saturday, October 5, 2013

CDI behaved unexpectedly in EAR, so OmniFaces 1.6.3 released!

After the OmniFaces 1.6 release, some OmniFaces users reported an undeployable EAR when it contained multiple WARs with OmniFaces bundled. It threw the following exception:

java.lang.IllegalArgumentException: 
    Registering converter 'class org.omnifaces.converter.ListIndexConverter' failed,
    duplicates converter ID 'omnifaces.ListIndexConverter' of other converter 'class org.omnifaces.converter.ListIndexConverter'.
        at org.omnifaces.cdi.converter.ConverterExtension.processConverters(ConverterExtension.java:82)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        ...

It completely blocked the deployment. Well, that was pretty awkward and astonishing. I could reproduce it myself as well by creating a very simple EAR with 2 WARs with each an own OmniFaces JAR file and beans.xml and no further additional code. In first instance, I had no idea how/why it was caused and didn't immediately bother to debug it ("will check it later on"), so I decided to just perform a quick fix by bypassing the exception if it concerns exactly the same converter (and validator) class. I.e. duplicate entries were allowed as long as it's exactly the same class.

WELD-001414 Bean name is ambiguous

However, when it's fixed, then it throws:

org.jboss.weld.exceptions.DeploymentException: 
    WELD-001414 Bean name is ambiguous. Name omnifaces_ViewScopeProvider resolves to beans 
    Managed Bean [class org.omnifaces.cdi.viewscope.ViewScopeManager] with qualifiers [@Any @Default @Named], 
    Managed Bean [class org.omnifaces.cdi.viewscope.ViewScopeManager] with qualifiers [@Any @Default @Named]
        at org.jboss.weld.bootstrap.Validator.validateBeanNames(Validator.java:476)
        at org.jboss.weld.bootstrap.Validator.validateDeployment(Validator.java:373)
        at org.jboss.weld.bootstrap.WeldBootstrap.validateBeans(WeldBootstrap.java:379)
        at org.jboss.as.weld.WeldStartService.start(WeldStartService.java:64)
        ...

It still completely blocked the deployment. My astonishment increased. When I remove the ViewScopeManager class for testing purposes, exactly the same exception is thrown for ConverterManager and ValidatorManager. All those beans have one common thing: a hardcoded @Named name:

@Named(ViewScopeProvider.NAME)
public class ViewScopeManager extends ViewScopeProvider {

}

CDI bean management context is by default EAR-wide

It turns out that the CDI is sharing the very same bean management context across all WARs in the same EAR. It is thus apparently not possible to have a @Named("somename") instance with the same name in multiple WARs in the same EAR, also not an implicit one (i.e. @Named without value). This problem was by Arjan T nailed down to the following SSCCE using an explicit name:

Deployment structure:

ear
    war1
        WEB-INF
            classes/test/Foo.class
            beans.xml
    war2
        WEB-INF
            classes/test/Bar.class
            beans.xml

Source codes:

package test;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

@Named("test")
@ApplicationScoped
public class Foo {
    //
}
package test;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;

@Named("test")
@ApplicationScoped
public class Bar {
    //
}

The same problem manifests when you have e.g. a @Named public class Foo {} in both WARs without an explicit name (and thus the implicit name "foo" is being used). Giving the @Named a different explicit name in each WAR or removing the @Named annotation altogether (with CDI, each Javabean/POJO found in the classpath is still implicitly automatically registered as a CDI managed bean, but without the @Named it would get an autogenerated name) fixes the "WELD-001414 Bean name is ambiguous" deployment problem. This is a very strange and completely unexpected problem. JSF managed beans doesn't behave like that. Spring managed beans also not — as far as I know, I never actually used Spring, but I couldn't find any evidence on the Internet confirming that.

It's beyond me why CDI behaves by default like that and that CDI is apparently not configurable in such way that only one CDI context per WAR of an EAR is created. It'd perhaps make sense if the EAR-wide CDI context was configurable via some configuration setting in a /META-INF/beans.xml in the EAR (not in WAR!), or even implicitly enabled as EAR-wide when a /META-INF/beans.xml is present in the EAR, but it is not. There have been numerous discussions about this, among others in CDI spec issue 129.

Well, to fix this I had to remove the @Named annotation from all OmniFaces-packaged CDI beans ConverterManager, ValidatorManager and ViewScopeManager. They had previously a hardcoded @Named annotation, because it allowed an easy way to grab an instance of them by just programmatically EL-evaluating their name as follows without the need for a CDI dependency (so that OmniFaces also deploys successfully in e.g. Tomcat where CDI isn't supported out the box!):


ConverterProvider instance = Faces.evaluateExpressionGet("#{" + ConverterProvider.NAME + "}");

Note: ConverterProvider is an interface. The ConverterManager is the actual implementation.

Right now, after removing the @Named, they have to be resolved through CDI's own BeanManager as follows *cough*:


@Inject
private BeanManager manager;

// ...

Set<Bean<?>> beans = manager.getBeans(ConverterProvider.class);
Bean<ConverterProvider> bean = (Bean<ConverterProvider>) manager.resolve(beans);
CreationalContext<ConverterProvider> context = manager.createCreationalContext(bean);
ConverterProvider instance = manager.getReference(bean, ConverterProvider.class, context);

In normal CDI managed beans, the BeanManager is available by just @Injecting it. However, this made it undeployable to Tomcat, so I had to resort to grabbing it from JNDI by java:comp/BeanManager (actually, Tomcat required an extra env/ path) and manually performing nasty reflection trickery to grab the instance. You can see all the code in the new org.omnifaces.config.BeanManager enum singleton. Ultimately, with help of it I ended up with the following Tomcat-compatible approach without any direct CDI dependencies:


ConverterProvider instance = BeanManager.INSTANCE.getReference(ConverterProvider.class);

WELD-001408 Unsatisfied dependencies for type

Still, it didn't deploy successfully, causing yet more astonishment:

Caused by: org.jboss.weld.exceptions.DeploymentException: 
    WELD-001408 Unsatisfied dependencies for type [ValidatorExtension] with qualifiers [@Default] at injection point 
    [[BackedAnnotatedParameter] Parameter 1 of [BackedAnnotatedConstructor] @Inject public org.omnifaces.cdi.validator.ValidatorManager(ValidatorExtension)]
        at org.jboss.weld.bootstrap.Validator.validateInjectionPointForDeploymentProblems(Validator.java:405)
        at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:327)
        at org.jboss.weld.bootstrap.Validator.validateGeneralBean(Validator.java:178)
        at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:209)
        at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:521)
        ...

This is regardless of whether I injected it as a property:


@Inject
private ValidatorExtension extension;

or in the constructor (as per Weld documentation):


private ValidatorExtension extension;

@Inject
public ValidatorManager(ValidatorExtension extension) {
    this.extension = extension;
}

When I remove the ValidatorExtension class for testing purposes, exactly the same exception is thrown for ConverterExtension.

CDI extensions from one WAR not injectable in another WAR of same EAR

After all, it turns out that there's only one CDI extension instance being created EAR-wide, even though the extension is loaded from a JAR in WAR and not from a JAR in EAR. This finally totally explains the initial IllegalArgumentException problem of converters and validators being added twice. The duplicate converter/validator class actually came from the other WAR.

However, as the CDI extension is loaded from a JAR in WAR, which has thus a different classloader than the other WARs, it's uninjectable in another WARs. In effect, the extension is successfully initialized and injected in one WAR, but not in another. The CDI extension loaded from one WAR couldn't be found in the other WARs of the same EAR (because of being loaded by a different .class file), causing exactly the aforementioned exception.

OmniFaces 1.6 has three extensions: ConverterExtension to proactively collect all @FacesConverter instances, ValidatorExtension to proactively collect all @FacesValidator instances and ViewScopeExtension to create the ViewScopeContext for @ViewScoped annotation. The extension wherein all converter/validator instances eligible for CDI injection are being collected, needs to be @Injected in an application scoped bean which implements an interface so that the application could create and resolve them via the dependency-free (Tomcat!) interface.

However, this peculiar CDI problem thus caused the whole CDI extension based approach to be completely unusable for proactively collecting CDI converter/validator instances. A workaround was found by lazily collecting them inside the application scoped bean itself, so that we could get rid of those CDI extensions ConverterExtension and ValidatorExtension.

With all those changes, the CDI converters/validators are finally fully usable throughout all WARs in the same EAR (only not in GlassFish 3.1.2.2 due to a bug in classloading, this is not related to OmniFaces, it works fine in GlassFish 4).

WELD-001303 No active contexts for scope type

The ViewScopeExtension fortunately doesn't need to be @Injected in the ViewScopeManager, so that part worked fine and the whole EAR finally deployed successfully. However, the ViewScopeContext, which is supposed to be registered by the extension, was available only in the very same WAR as where the CDI extension class is loaded from. The other WARs would throw the following exception:

org.jboss.weld.context.ContextNotActiveException:
    WELD-001303 No active contexts for scope type org.omnifaces.cdi.ViewScoped
        at org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:578)
        at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:71)
        at org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:79)
        at com.example.CdiViewScopedBean$Proxy$_$$_WeldClientProxy.submit(CdiViewScopedBean$Proxy$_$$_WeldClientProxy.java)
        ...

Noted should be that it didn't block the deployment. It's thrown during runtime when an OmniFaces CDI @ViewScoped bean is referenced for the first time and thus needs to be constructed.

Custom scope context created by CDI extension from one WAR not available in another WAR of same EAR

This is very unfortunate as there doesn't seem to be any way to register a custom context programmatically other than via a CDI extension. There's thus no feasible solution to this problem. We would be all ears to implement something like a ServletContextListener to programmatically register the custom CDI scope context WAR-wide, but there seems to be no API-provided way for even something simple like that. All initialization has to take place in a CDI extension per se.

So far, the new OmniFaces CDI @ViewScoped works unfortunately in only one WAR of such an EAR. This all is also summarized along with a table of the tested application servers in Known issues of OmniFaces CDI features in combination with specific application servers. I didn't test it with JSF 2.2's @ViewScoped, but by looking at the codebase of Mojarra and MyFaces, it would expose exactly the same problem when JSF 2.2 JARs are being bundled in those WARs instead of provided by the appserver (as by default on a decent Java EE application server).

OmniFaces 1.6.1 1.6.2 1.6.3 released

Our important goal was to make OmniFaces at least deployable, i.e. it shouldn't be blocking the deployment of WAR or EAR in any way, even though some CDI features may work only half due to the way how CDI works in EARs. We succeed in this with 1.6.3. It's available in Maven on the following coordinates:


<dependency>
    <groupId>org.omnifaces</groupId>
    <artifactId>omnifaces</artifactId>
    <version>1.6.3</version>
</dependency>

WAR-only users are also recommended to upgrade, because during some intensive stress testing of zeef.com by our server admin with hundreds of sessions, the new version 1.4 of the ConcurrentLinkedHashMap, which is repackaged by OmniFaces specifically for usage in <o:cache> and @ViewScoped, was without any clear reason allocating hundreds of megabytes of memory in flavor of a lot of PaddedAtomicLong references. Downgrading the repackaged ConcurrentLinkedHashMap back to its previous version 1.2 fixed the problem of excessive memory allocation while the performance has not significantly changed. Note that older OmniFaces versions before 1.6 already used version 1.2 of ConcurrentLinkedHashMap, so OmniFaces version 1.6 is the only version having this problematic version 1.4 of ConcurrentLinkedHashMap.

Also, a new CDI extension was added which should bypass ("veto") the registration as CDI managed bean of all classes in org.omnifaces package other than org.omnifaces.cdi.* and org.omnifaces.showcase.*. This should eliminate exceptions about ambiguous injection points (some classes in OmniFaces implement Map and that may conflict with a @Producer returning Map) and warnings in server logs about unmanagable beans (like as produced by OpenWebBeans in TomEE 1.6 and reported as issue 894).

By the way, to the users who encountered those problems in their EARs after upgrading to OmniFaces 1.6: herewith we want to wholeheartedly say "Sorry for that!" to them and hopefully OmniFaces 1.6.3 will do the needful job for them. We should really have tested the EAR part as well although those problems were after all completely unexpected. Also, thank you very much for reporting back those problems.

UPDATE: well, it appears that 1.6.1 blocked deployment on non-CDI containers like Tomcat and friends. This was my own mistake. My "Tomcat-Without-CDI" project in Eclipse was misconfigured to have a physical 1.6 JAR in /WEB-INF/lib which got precedence over 1.6.1 in "Deployment Assembly". In effect, I actually never tested 1.6.1 in Tomcat, but always 1.6. Now, 1.6.2 has really been tested on Tomcat and it works fine over there (and of course all others which we tested before).

UPDATE 2: an user of a server which we never used, Google App Engine, reported that 1.6.1/1.6.2 blocked deployment because GAE doesn't support JNDI. This is in turn fixed in 1.6.3.

Conclusion: CDI spec is broken as to behavior in EAR

The CDI guys really need to work out this. What started as clean code in OmniFaces converter/validator injection ended up as ugly code with some reflection trickery and even then it still doesn't work 100% as intented in an EAR with OmniFaces in multiple WARs.

In my opinion, the right solution would be to make the EAR-wide CDI context configurable via some configuration setting in a /META-INF/beans.xml in the EAR (not in WAR!), or even implicitly enabled as EAR-wide when a /META-INF/beans.xml is present in the EAR and otherwise default to WAR-wide (as intuitively expected by developers).

Admittedly, the CDI spec is in Java EE 6 "only" the crisp first version 1.0, so some astonishing teething problems like this are understandable (Servlet 1.0 and JSF 1.0 were also tear jerking), but those issues are even not covered/fixed in 1.1, so all of above still fails the same way in Java EE 7 containers (GlassFish 4.0 and WildFly 8 alpha 4 were tested).

Sunday, June 29, 2008

What is it with roseindia?

Cry or laugh?

There's another Java related website at roseindia.net which hosts lot of basic Java EE tutorials along a huge mass of advertisements. There are also some JSF tutorials over there. Since I saw that site for the first time, about 1.5 years ago, I quickly realized that almost every article and code sample at that site must be taken with a huge bag of salt. But OK, I thought, that kind of websites unfortunately exist, nothing to do against, so I ignored it for the rest of the time.

And today, while surfing over the net I accidently came across http://www.roseindia.net/jsf/actionListener.shtml (screenshot). Now I don't know whether I have to cry or laugh about it. Initially I laughed hard, very hard. Running a Swing JOptionPane component from inside a server side Java class with the intention to display a message at the client side!! And showing that off as a tutorial?!? But then I realized that there might be many new-to-JSF developers who came across the roseindia.net site and took the gross of the articles for serious and implemented it for production. Ouch, that hurts! Also see the comments at the bottom of that article.

Update: here and here are another examples of a horror story related to roseindia.net. A poor programmer decided to implement the JSP file upload example as described at http://www.roseindia.net/jsp/file_upload/Sinle_upload.xhtml.shtml (ouch, note the typo and double extensions in the roseindia.net URL, how professional). The code example is bad in almost every way. The HTML code contains nasty uppercases (the author is apparently hanging in the '90s HTML style). The HTML code contains the <center> tag which is deprecated since 1998 (yes, that is 10 years ago!! the same applies on the <font> tag which appears everywhere in roseindia.net's HTML source, how professional). The Java code is written entirely in a JSP file rather than a Java class (scriptlets are receipt for trouble). The uploaded file is allocated completely in memory instead of in a memory buffer (server will crash if the file or all files together are larger than JVM's available memory). The bytes are unnecessarily converted to String without respect to request encoding and using the system default encoding (ouch, what would happen with binary files? and with text files in another encoding?). Also read the comments at that article. Heck, almost every article has that kind of comments, shouldn't that already say something about the quality of the articles? See further also this stackoverflow.com answer.

Update 2: another horror story can be found here. One decided to download and install JSTL using the procedure as described at http://www.roseindia.net/jstl/downloading-jstl.shtml. Did you read it? It suggests to extract the complete JAR file, duplicate all of its TLD files in the WEB-INF and duplicate all of the TLD declarations in the web.xml of every webapplication which is going to use JSTL. Terrible. Just placing the JAR files in the classpath was been enough. The myth of extracting the JAR file is only applicable if you have downloaded the wrong JSTL version for your webapp which causes that EL (those ${} things) in JSTL tags may possibly fail. For more detail of the proper approach, read JSTL wiki page on stackoverflow.com.

Lesson learned: if you are new to Java EE, do not use roseindia.net! It might contain some good articles -which are generally copypasted/rephrased/changed from actually good sources (I've seen exact copypastes of parts of official TLD and API documentations, e.g. Tomahawk TLD and Java Servlet API, how lame..), or are in very rare cases just by coincidence good-, but for the rest it only introduces bad practices and/or contains stupid examples. That site almost seems to be maintained by amateurs with zero professional experience and targeted on the advertisement incomes only. If you are new, you never knows which is good and which is bad. So, just ignore that site. Forever.

Good JSF sites

At least here is a list of trusted and good JSF related sites:

And, of course, the JSF related articles at this blog. Check them out at the 'Tags' or 'Articles' section in the right column. The JSF wiki page at stackoverflow.com is also a nice starting point.

Sunday, June 4, 2006

DB2 LOWER() bug

Case insensitive search in a table with a foreign key

The goal is to execute a case insensitive Hibernate search in a DB2 table which has a foreign key linked to another DB2 table. This should be easily done using the LOWER() function of DB2.

The relevant DDL of the DB2 table to execute the search on, MyTable.ddl:

CREATE TABLE MYTABLE (
    ID BIGINT GENERATED ALWAYS AS IDENTITY CONSTRAINT MYTABLE_PK PRIMARY KEY,
    ANOTHERTABLE_ID BIGINT NOT NULL,
    FIELD1 VARCHAR (255) NOT NULL,
    FIELD2 VARCHAR (255) NOT NULL,
    CONSTRAINT MYTABLE_FK FOREIGN KEY (ANOTHERTABLE_ID) REFERENCES ANOTHERTABLE (ID)
);

The relevant DTO (Data Transfer Object) code of the DB2 table, MyTable.java:

public class MyTable {

    private Long id;
    private Long anotherTableId;
    private String field1;
    private String field2;

    // Implement public getters and setters here.
}

The relevant java code of the backing bean MyBean.java:

public class MyBean {

    public void actionSearch() {
        Map params = new HashMap();
        params.put("anotherTableId", getAnotherTableId());
        params.put("field1", "%" + getSearchFilter().toLowerCase() + "%");
        params.put("field2", "%" + getSearchFilter().toLowerCase() + "%");
        Command command = new QueryCommand(params, "MyFilter", MyTable.class);
        CommandInvoker.execute(command);
    }

}

The getSearchFilter() method is just a getter for the search string entered by the browser user.

The relevant code of the Hibernate mapping file mybean.hbm.xml including the case insensitive search query using the DB2 LOWER() function:

<hibernate-mapping package="mypackage.mywebapp">
    <class name="MyTable" table="MYTABLE">
        <id column="ID" name="id" type="long">
            <generator class="identity" />
        </id>
        <property column="ANOTHERTABLE_ID" name="anotherTableId" type="long" />
        <property column="FIELD1" name="field1" />
        <property column="FIELD2" name="field2" />
    </class>

    <query name="MyFilter">
        FROM
            MyTable table 
        WHERE
            table.anotherTableId = :anotherTableId
        AND
            ( LOWER(table.field1) LIKE :field1 OR LOWER(table.field2) LIKE :field2 )
    </query>
</hibernate-mapping>

OK, the code here above looks fine, but after executing the query command in MyBean.java, Hibernate will throw a SQLGrammarException, followed by a SQLException from the IBM DB2 wrapper:

org.hibernate.exception.SQLGrammarException: could not execute query
Caused by: com.ibm.db2.jcc.a.SqlException: No authorized routine named "LOWER" of type "FUNCTION" having compatible arguments was found.

Troubleshooting

The following Hibernate queries works correctly, the first query is without the LOWER() function and the second query is without the WHERE clause on the foreign key:

<query name="MyFilter">
    FROM
        MyTable table 
    WHERE
        table.anotherTableId = :anotherTableId
    AND
        ( table.field1 LIKE :field1 OR table.field2 LIKE :field2 )
</query>
<query name="MyFilter">
    FROM
        MyTable table 
    WHERE
        LOWER(table.field1) LIKE :field1 OR LOWER(table.field2) LIKE :field2
</query>

The conslusion is that the LOWER() function conflicts with the WHERE clause on the foreign key somehow.

The Solution

Use LCASE() instead of LOWER().

It's really weird, the LCASE() function is an alias of the LOWER() function, which should behave identical as the LOWER() function. BalusC spent 3 days on finding the solution. Blame IBM.

Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.

(C) June 2006, BalusC