3

I work with JSF 2 (MyFaces 2.1.7 and Primefaces 3.4.2), CDI (Weld-servlet 1.1.10), JPA 2 (Hibernate 4.1.7) and Lombok 0.11.2. All this runs on Tomcat 6 and 7.

I use the OpenSessionInView pattern, implemented through a Filter.

@Advanced
@Data
@Slf4j
public class TransactionalFilter implements Filter, Serializable {

    private static final long serialVersionUID = 999173590695648899L;

    @Inject
    private EntityManager em;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        boolean newTransaction = false;
        EntityTransaction tx = em.getTransaction();
        if (!tx.isActive()) {
            tx.begin();
            newTransaction = true;
        }

        try {
            chain.doFilter(request, response);
            if (newTransaction && tx.isActive()) {
                tx.commit();
            }
        } catch (Exception e) {
            if (newTransaction && tx.isActive()) {
                tx.rollback();
            }
            throw new ServletException(e);
        }

    }
    (...)
}

The injected RequestScoped EntityManager is provided by my EntityManagerFactory, who also provides it for all my services fronts.

@ApplicationScoped
@Data
@Slf4j
public class TransactionalEntityManagerFactory implements Serializable {

    private static final String PU_NAME = "fr.senat.dosleg";
    private static final long serialVersionUID = -3595175390458199193L;
    private EntityManagerFactory emf;

    /** (...)
     * @return un nouvel EntityManager.
     */
    @Produces
    @RequestScoped
    public EntityManager getEntityManager() {
        if (emf == null) {
            emf = Persistence.createEntityManagerFactory(PU_NAME);
        }
        return emf.createEntityManager();
    }

    /**(...)
     * @param em le gestionnaire d'entité à libérer.
     */
    public void closeEntityManager(@Disposes EntityManager em) {
        if (em != null && em.getTransaction().isActive()) {
            em.getTransaction().rollback();
        }

        if (em != null && em.isOpen()) {
            em.close();
        }
    }
}

This all worked fine, until I added the List<Theme> shown in the Controle Entity below

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString(exclude = { "organisme", "groupePolitique", "lois", "livrables",
    "acteurs", "themes" })
public class Controle implements Serializable {

    private static final long serialVersionUID = -6471695606036735891L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @NotNull
    @Size(max = 256, message = "trop long.")
    private String libelle;

    @Pattern(regexp = Constants.URL_PATTERN, message = "pas au bon format")
    private String url;

    @NotNull
    @Type(type = "fr.senat.util.hibernate.usertype.OuiNonSmallType")
    private boolean initiativeDesGroupes;

    @NotNull
    @Type(type = "fr.senat.util.hibernate.usertype.OuiNonSmallType")
    private boolean courDesComptes;

    @NotNull
    private int anneeCreation;

    @Embedded
    private EcheanceControle echeance = new EcheanceControle();

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "ORGCOD")
    private Organisme organisme;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "GRPPOL")
    private GroupePolitique groupePolitique;

    @ManyToMany
    @JoinTable(name = "CONTROLE_LOI", joinColumns = @JoinColumn(name = "CON_ID"), inverseJoinColumns = @JoinColumn(name = "LOICOD"))
    private List<Loi> lois;

    @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL)
    @JoinColumn(name = "CON_ID", nullable = false)
    private List<LivrableControle> livrables;

    @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL)
    @JoinColumn(name = "CON_ID", nullable = false)
    private List<ActeurControle> acteurs;

    @ManyToMany
    @JoinTable(name = "THEME_CONTROLE", joinColumns = @JoinColumn(name = "CON_ID"), inverseJoinColumns = @JoinColumn(name = "THECLE"))
    private List<Theme> themes;

    (...)

}

Now when I try to save an existing Controle, through the controle.xhtml page, I get an ajax response error. Below is controle.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:p="http://primefaces.org/ui"
    xmlns:s="http://www.senat.fr/taglib/util"
    xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="template.xhtml">
    <ui:define name="title">
        <h:outputText value="#{controle.controle.libelle}"
            rendered="#{not empty controle.controle.id}" />
        <h:outputText value="Création d'un contrôle"
            rendered="#{empty controle.controle.id}" />
    </ui:define>
    <ui:define name="specific_header">
        <h:outputScript library="fr.senat.util.primefaces"
            name="calendar-locale.js" />
    </ui:define>
    <ui:define name="content">
        <h:form id="controle">

            <p:messages />

            <p:panel>

                <h:panelGrid columns="2" columnClasses="label,">

                    <p:outputLabel for="libelle" value="Libellé" />
                    <p:inputText id="libelle" value="#{controle.controle.libelle}"
                        size="60" />

                    <p:outputLabel for="url" value="URL" />
                    <p:inputText id="url" value="#{controle.controle.url}" size="60" />

                    <h:outputText value="Acteurs" />
                    <h:panelGroup>
                        <h:panelGroup id="acteurs">
                            <p:dataTable var="a" value="#{controle.controle.acteurs}"
                                rendered="#{not empty controle.controle.acteurs}">
                                <p:column headerText="Rôle">
                                    <h:outputText value="#{a.role}" />
                                </p:column>

                                <p:column headerText="Nom">
                                    <h:outputText
                                        value="#{a.senateur.nomUsuel} #{a.senateur.prenomUsuel}" />
                                </p:column>

                                <p:column headerText="Enlever">
                                    <p:commandButton
                                        title="Enlever #{a.senateur.nomUsuel} #{a.senateur.prenomUsuel}"
                                        icon="ui-icon-trash"
                                        actionListener="#{controle.removeElement}"
                                        update=":controle:acteurs" immediate="true">
                                        <f:attribute name="ancien" value="#{a}" />
                                    </p:commandButton>
                                </p:column>
                            </p:dataTable>
                        </h:panelGroup>
                        <p:commandButton id="addActeur" value="Ajouter un acteur"
                            icon="ui-icon-plus" onclick="addActeurDialog.show()"
                            immediate="true" />
                    </h:panelGroup>

                    <p:outputLabel for="themes" value="Thèmes" />
                    <p:selectManyButton id="themes" value="#{controle.controle.themes}">
                        <f:selectItems value="#{controle.themes}" var="t"
                            itemLabel="#{t.libelle}" itemValue="#{t}" />
                    </p:selectManyButton>

                </h:panelGrid>

                <f:facet name="footer">
                    <p:commandButton onclick="deleteControleDialog.show()"
                        value="Supprimer" icon="ui-icon-trash"
                        styleClass="ui-priority-secondary" type="button" />
                    <p:button outcome="pretty:start" value="Annuler"
                        icon="ui-icon-cancel" />
                    <p:commandButton id="saveSubmit" value="Sauvegarder"
                        actionListener="#{controle.save}" icon="ui-icon-disk"
                        styleClass="ui-priority-primary" />
                </f:facet>
            </p:panel>
            <p:defaultCommand target="saveSubmit" />
        </h:form>

        <p:dialog header="Ajoute un nouvel acteur" id="addActeurDialog"
            widgetVar="addActeurDialog" modal="true" width="650">
            <h:form id="addActeurForm">
                <p:messages />
                <h:panelGrid columns="2">

                    <p:outputLabel for="newRole" value="Rôle" />
                    <p:autoComplete id="newRole" dropdown="true"
                        value="#{controle.newRole}"
                        completeMethod="#{controle.completeRole}" />

                    <p:outputLabel for="senateurSearch" value="Sénateur" />
                    <h:panelGroup>
                        <p:inputText id="senateurSearch"
                            value="#{controle.senateurSearch}" size="30" />
                        <p:commandButton id="senateurSearchSubmit" value="Chercher"
                            actionListener="#{controle.searchSenateurs}"
                            update="senateursFound" icon="ui-icon-search"
                            oncomplete="addActeurDialog.initPosition()" />
                        <p:selectBooleanButton value="#{controle.senateurSearchActif}"
                            onLabel="en activité seulement" offLabel="en activité ou non" />
                    </h:panelGroup>
                </h:panelGrid>
                <h:panelGroup id="senateursFound">
                    <p:dataTable value="#{controle.senateurs}" var="s"
                        rendered="#{not empty controle.senateurs}" rows="10">
                        <p:column headerText="Nom">
                            <h:outputText value="#{s.nomCompletUsuel}" />
                        </p:column>

                        <p:column headerText="Ajout">
                            <p:commandButton title="Ajouter le sénateur #{s.nomCompletUsuel}"
                                icon="ui-icon-plus" actionListener="#{controle.addElement}"
                                oncomplete="addActeurDialog.hide()" update=":controle:acteurs">
                                <f:attribute name="nouveau" value="#{s}" />
                            </p:commandButton>
                        </p:column>
                    </p:dataTable>
                </h:panelGroup>

            </h:form>
        </p:dialog>

        <p:confirmDialog id="deleteControleDialog"
            message="Etes-vous sûr de vouloir supprimer ce contrôle ?"
            header="Suppression du contrôle" severity="alert"
            widgetVar="deleteControleDialog">
            <p:commandButton value="Annuler"
                onclick="deleteControleDialog.hide()" />
            <p:commandButton value="Confirmer la suppression"
                action="#{controle.deleteControle}" styleClass="ui-priority-primary" />
        </p:confirmDialog>
    </ui:define>
</ui:composition>
</html>

and this is the error

<?xml version="1.0" encoding="utf-8"?>
<partial-response>
    <error>
        <error-name>org.hibernate.LazyInitializationException</error-name>
        <error-message><![CDATA[could not initialize proxy - no Session]]></error-message>
    </error>
</partial-response>

With a debugger I was able to determine the Stacktrace when the Exception is first thrown

PersistentBag(AbstractPersistentCollection).withTemporarySessionIfNeeded(LazyInitializationWork<T>) line: 180   
PersistentBag(AbstractPersistentCollection).initialize(boolean) line: 520   
PersistentBag(AbstractPersistentCollection).write() line: 345   
PersistentBag.add(Object) line: 291 
_SharedRendererUtils.getConvertedUISelectManyValue(FacesContext, UISelectMany, String[], boolean) line: 339 
RendererUtils.getConvertedUISelectManyValue(FacesContext, UISelectMany, Object, boolean) line: 1088 
RendererUtils.getConvertedUISelectManyValue(FacesContext, UISelectMany, Object) line: 1056  
HtmlCheckboxRenderer(HtmlCheckboxRendererBase).getConvertedValue(FacesContext, UIComponent, Object) line: 525   
SelectManyButtonRenderer.getConvertedValue(FacesContext, UIComponent, Object) line: 36  
SelectManyButton(UISelectMany).getConvertedValue(FacesContext, Object) line: 402    
SelectManyButton(UIInput).validate(FacesContext) line: 584  
SelectManyButton(UISelectMany).validate(FacesContext) line: 393 
SelectManyButton(UIInput).processValidators(FacesContext) line: 274 
HtmlPanelGrid(UIComponentBase).processValidators(FacesContext) line: 1421   
Panel(UIComponentBase).processValidators(FacesContext) line: 1421   
Panel.processValidators(FacesContext) line: 297 
HtmlForm(UIForm).processValidators(FacesContext) line: 209  
HtmlBody(UIComponentBase).processValidators(FacesContext) line: 1421    
UIViewRoot(UIComponentBase).processValidators(FacesContext) line: 1421  
UIViewRoot._processValidatorsDefault(FacesContext) line: 1401   
UIViewRoot.access$500(UIViewRoot, FacesContext) line: 74    
UIViewRoot$ProcessValidatorPhaseProcessor.process(FacesContext, UIViewRoot) line: 1508  
UIViewRoot._process(FacesContext, PhaseId, UIViewRoot$PhaseProcessor) line: 1357    
UIViewRoot.processValidators(FacesContext) line: 799    
ProcessValidationsExecutor.execute(FacesContext) line: 38   
LifecycleImpl.executePhase(FacesContext, PhaseExecutor, PhaseListenerManager) line: 170 
LifecycleImpl.execute(FacesContext) line: 117   
CodiLifecycleWrapper.execute(FacesContext) line: 95 
FacesServlet.service(ServletRequest, ServletResponse) line: 197 
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 290  
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206  
PrettyFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 145   
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 235  
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206  
ApplicationDispatcher.invoke(ServletRequest, ServletResponse, ApplicationDispatcher$State) line: 646    
ApplicationDispatcher.processRequest(ServletRequest, ServletResponse, ApplicationDispatcher$State) line: 436    
ApplicationDispatcher.doForward(ServletRequest, ServletResponse) line: 374  
ApplicationDispatcher.forward(ServletRequest, ServletResponse) line: 302    
PrettyFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 137   
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 235  
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206  
TransactionalFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 60 
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 235  
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206  
SetUtf8CharacterEncodingFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 44  
(...)
Thread.run() line: 662  

Now I do realise that many posts on SO concern that Exception. All answers generally revolve around the entitymanager (or session in Hibernate dialect) being closed. They provide two solutions : use OpenSessionInView or fetch properties lazyly. I think my problem is different for several reasons.

  1. I already use OpenSessionInView. Furthermore, the logs indeed show that when the problem arises, the transaction is still open.
  2. Trying to lazyly fetch the themes properties does not help

Going through the code, it seemed to me that this was generic to all SelectMultiXXX widgets, so I tried with SelectManyCheckbox. It didn't work either. I have also tried to use a specific converter or not on the widget and the result is the same.

On a final note, I wanted to point out that all the other List<> properties work on the same controle.xhtml. The difference however might be that these are modified through dialog boxes (hence different requests).

I hope that you can see something that I don't, or that you can confirm it is a bug, or that you can provide me with a workaround. If you don't, I still thank you for the time you have taken to read that long question.

UPDATE 29/11/2012 : I am now certain the entitymanager is indeed open at the time of the exception. Furthermore, I am convinced that the problem comes from the following method

_SharedRendererUtils.getConvertedUISelectManyValue(FacesContext, UISelectMany, String[], boolean) (line 143)

Here are a few pointers from the debug of that method if somebody wants to give it a go

  1. Class<?> modelType = expression.getType(facesContext.getELContext()); is java.util.List
  2. Collection.class.isAssignableFrom(modelType) is true
  3. collectionTypeAttr != null is false
  4. Collection.class.isAssignableFrom(modelType) is true
  5. Collection<?> componentValue = (Collection<?>) component.getValue(); is a PersistenBag which seems correctly initialized with data (storedSnapshot and role) but with a null session.
  6. targetForConvertedValues = (componentValue != null ? componentValue.getClass() : modelType).newInstance(); ends up being a PersistentBag with everything null (including data). Could that be the problem ?
  7. boolean isArray = (targetForConvertedValues.getClass().isArray()); is false
  8. ((Collection) targetForConvertedValues).add(value); is where everything goes awry.

Any thoughts ?

clark
  • 388
  • 3
  • 20
  • Your collection is by default lazily loaded. If you try to access your list outside of the context of the session, and your proxy is not loaded, it will complain since the proxied object can no longer access it's session. Try session.merge to merge your object to the current session if you think it comes from another session. Or, if you want, add eager on your collections, although in most cases it is not desired. – Nikola Yovchev Nov 27 '12 at 14:17

2 Answers2

2

This to me looks like a really nasty bug I found in Mojarra, could be the same in this version of MyFaces. The basics are when it does validation it does a copy of the list, but it uses the concrete type of the collection, using the no-arg constructor to create the collection. In hibernate's case, this new list doesn't have all the init code run on it and it doesn't link back up to the session. It to me a long time debugging and the Mojarra source to figure out what was actually happening.

I found I had to use the collectionType attribute and set it to the java.util interface type. I don't do anything with collections anymore without explicitly telling JSF the collection type to use.

LightGuard
  • 5,298
  • 19
  • 19
  • Thank you for your reply. I am not convinced this is the solution because the stacktrace provided indicates that the exception occurs while inside PersistentBag which I think would not be the case if Hibernate had not realized this was a proxy. I have however tried adding a `@CollectionType(type = "java.util.List")` to my `private List themes;` and I get a `Custom type does not implement UserCollectionType: java.util.List` : maybe I haven't understood what your suggestion was ? – clark Nov 28 '12 at 16:09
  • I thought the same thing initially. Like I said, it took a very long time to find it (whole day wasted) give it a try, it's a minor change. If it doesn't work, then you know what it isn't. It's in the view, not the entity. It's on multiple select components. – LightGuard Nov 28 '12 at 19:35
  • Thank you for that follow up. I would like to try what you propose but I can't find anything about a `collectionType` attribute on the ``, in the PrimeFaces Reference or on the internet. Can you be more specific ? – clark Nov 29 '12 at 16:32
  • It's a standard jsf attribute on the base class. Here are a few other links describing either the problem or the attribute: [pdl docs](http://docs.oracle.com/javaee/6/javaserverfaces/2.0/docs/pdldocs/facelets/h/selectManyCheckbox.html) [blog about the problem](http://manuel-palacio.blogspot.com/2011/02/hibernate-jsf-2-and-manytomany.html) – LightGuard Nov 29 '12 at 17:41
  • In the end, I actually had to set `collectionType` to the actual implementation `ArrayList` and not the interface. I also had to make sure I had my converter in there. And now it works. Couldn't that be considered a bug on myfaces or primefaces or is that normal ? – clark Nov 30 '12 at 15:40
  • It's a bug in MyFaces (and also Mojarra), or perhaps in the actually jsf-api, I don't remember where the actual code lives. – LightGuard Dec 01 '12 at 05:05
0

Collections in Hibernate are by default lazily loaded when you receive an object from db that contains a collection, in place of the List<> or Set<> there is a proxy for that list or set. Upon calling the getter method for that collection, it will be fetched. However, a problem would be if the session from which the object originated, is closed or no longer accessible (which should not be in the constraints of your OpenSessionInView filter).

You can try calling session.merge on your objects if they are handled by different sessions. Or you can manually call the getter of your List during fetching which will trigger the proxy. Or you can add FetchType.Eager on your collection, in which case the object will not be a proxy but a real object even at the time of fetching.

But I see you are using Weld for CDI, why not use Seam Persistence module which has support for transaction management? Then you will be able to implement your open session in view approach very quickly.

http://www.seamframework.org/Seam3/PersistenceModule

Nikola Yovchev
  • 9,498
  • 4
  • 46
  • 72
  • As the StackTrace shows, the exception happens during validation, before actually going into my Bean code. Therefore, I wouldn't know where to add the "hack" of manually merging. The FetchType.Eager I have tried, as explained in the question. It doesn't change the problem. I might look in to the PersistenceModule but I was thinking it might be a bit "big" for the simple thing I need. – clark Nov 27 '12 at 14:33