2 Replies Latest reply on Jan 27, 2011 2:56 AM by Martin Frey

    New EntityConverter POC

    Martin Frey Newbie

      Hello


      I did some investigation on our very slow pages this week and after digging around i found multiple threads stating that the EntityConverter is alot of times a bottleneck. We have 3 selectboxes for selecting a user in our editor. That is 3 times the same content. I found out that the entityconverter keeps his own map to identify the selected entity later. (Thanks Tobias for the timinginterceptor! again :D )


      So i've created my own entityConverter and would like to get some ideas from you if you see some problems with it and what you think about it's performance. It uses the EntityManager directly (possible issue?) and keeps only the class and @Id field definition instead of the whole entity.


      My testpage contains 20 selectboxes (10 times the userselect and 10 times the scopeselect).



      For the SelectItem version 10x this code


      <h:selectOneMenu value="#{requirementInstance.owner}" style="width:300px;" required="true">
           <f:selectItems value="#{activeUsersSI}" />
      </h:selectOneMenu>
      <h:selectOneMenu value="#{requirementInstance.scope}" style="width:300px;" required="true">
           <f:selectItems value="#{scopeListAllScopeItems}" />
      </h:selectOneMenu>
      <br />



      For the EntityConverter version 10x this code



      <h:selectOneMenu value="#{requirementInstance.owner}" style="width:300px;">
           <s:selectItems var="user" label="#{user.fullName}" noSelectionLabel="Select a user" value="#{activeUsers}" />
           <s:convertEntity />
      </h:selectOneMenu>
      <h:selectOneMenu value="#{requirementInstance.scope}" style="width :300px;">
           <s:selectItems var="scope" noSelectionLabel="Select a Scope" label="#{scope.label}" value="#{allScopes}" />
           <s:convertEntity />
      </h:selectOneMenu>
      <br />





      I compared first the SelectItem version with the original EntityConverter and i got some timing values of:



      • SI:     780ms - 1070ms

      • EC:    7800ms - 8360ms (nope there is not a 0 to much)



      So i created this code:



           @Name("org.jboss.seam.ui.EntityConverter")
      @Scope(CONVERSATION)
      @Install(precedence = Install.APPLICATION)
      @Converter
      @BypassInterceptors
      public class EntityConverter implements javax.faces.convert.Converter, Serializable {
      
           private class EntityDef {
      
                protected Class<?> entityClass = null;
                protected Field pkField = null;
                protected String simpleName = null;
      
                public EntityDef(Class<?> entityClass) {
                     super();
                     simpleName = entityClass.getSimpleName();
                     this.entityClass = entityClass;
                     for (Field field : ReflectionHelper.getFields(entityClass)) {
                          if (field.isAnnotationPresent(Id.class)) {
                               pkField = field;
                               break;
                          }
                     }
                }
           }
      
           /**
            * 
            */
           private static final long serialVersionUID = 1L;
      
           private EntityDef entityDef;
      
           public Object getAsObject(FacesContext facesContext, UIComponent cmp, String value) throws ConverterException {
                if (value == null) {
                     return null;
                }
                EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");
                String simpleClassName = value.substring(0, value.indexOf(":"));
                String id = value.substring(value.indexOf(":") + 1).trim();
                if (!simpleClassName.equals(entityDef.simpleName)) {
                     Map<String, EntityDef> entityDefinitions = getEntityDefinitions();
                     entityDef = entityDefinitions.get(simpleClassName);
                }
                Object pkValue = null;
                if (entityDef.pkField.getType().equals(String.class)) {
                     pkValue = id;
                } else if (entityDef.pkField.getType().equals(Boolean.class)) {
                     pkValue = Boolean.parseBoolean(id);
                } else if (entityDef.pkField.getType().equals(Long.class)) {
                     pkValue = Long.parseLong(id);
                } else if (entityDef.pkField.getType().equals(Integer.class)) {
                     pkValue = Integer.parseInt(id);
                } else if (entityDef.pkField.getType().equals(Float.class)) {
                     pkValue = Float.parseFloat(id);
                } else if (entityDef.pkField.getType().equals(Double.class)) {
                     pkValue = Double.parseDouble(id);
                }
                return entityManager.find(entityDef.entityClass, pkValue);
           }
      
           public String getAsString(FacesContext facesContext, UIComponent cmp, Object value) throws ConverterException {
                if (value == null) {
                     return null;
                }
                if (value instanceof String) {
                     return (String) value;
                }
                if (entityDef == null || !value.getClass().equals(entityDef.entityClass)) {
                     String simpleName = value.getClass().getSimpleName();
                     Map<String, EntityDef> entityDefinitions = getEntityDefinitions();
                     if (!entityDefinitions.containsKey(value.getClass().getSimpleName())) {
                          entityDefinitions.put(simpleName, new EntityDef(value.getClass()));
                     }
                     entityDef = entityDefinitions.get(simpleName);
                }
                boolean acc = entityDef.pkField.isAccessible();
                try {
                     if (!acc) {
                          entityDef.pkField.setAccessible(true);
                     }
                     return String.valueOf(entityDef.simpleName + ": " + entityDef.pkField.get(value));
                } catch (Exception e) {
                     return "Cannot get PK";
                } finally {
                     entityDef.pkField.setAccessible(acc);
                }
           }
      
           @SuppressWarnings("unchecked")
           private Map<String, EntityDef> getEntityDefinitions() {
                Map<String, EntityDef> entityDefinitions = (Map<String, EntityDef>) Contexts.getSessionContext().get(
                          "entityDefinitions");
                if (entityDefinitions == null) {
                     Contexts.getSessionContext().set("entityDefinitions", new HashMap<String, EntityDef>());
                }
                return entityDefinitions;
      
           }
      }



      And now i got timings like this:



      • SI:     780ms - 1070ms

      • EC:    7800ms - 8360ms (nope there is not a 0 to much)

      • New EC: 920ms - 1300ms (once 2100ms)



      My Entityconverter uses the SimpleClassName (bandwidth) and PrimaryKey as a select value instead of keeping the entities somewhere around and reference them through the array location.


      This converter works if there are no SimpleClassName for entities in the application and the entity does only have one primary key (@Id).


      I would be very interessed if someone could verify this and / or get some other input.


      Martin



        • 1. Re: New EntityConverter POC
          Martin Frey Newbie

          Just found a minor issue in the code:
          getEntityDefinitions should look like that. Else the first time it's creating a nullpointer exception.




               @SuppressWarnings("unchecked")
               private Map<String, EntityDef> getEntityDefinitions() {
                    Map<String, EntityDef> entityDefinitions = (Map<String, EntityDef>) Contexts.getSessionContext().get(
                              "entityDefinitions");
                    if (entityDefinitions == null) {
                         Contexts.getSessionContext().set("entityDefinitions", new HashMap<String, EntityDef>());
                         entityDefinitions = (Map<String, EntityDef>) Contexts.getSessionContext().get("entityDefinitions");
                    }
                    return entityDefinitions;
               }



          • 2. Re: New EntityConverter POC
            Martin Frey Newbie

            I did now some reallife testing and included the code in the project i'm working on. It seems that in my application nothing has changed from the implementation point of view and i was able to gain more or less 200ms per page in the complete application test with selenium. Each page contains at least 1 select box.


            Until now i did not run into any issues related to this converter code. I already have an idea about the single class name issue but this i need a bit of free time first.


            Some input from outside would be appreciated anyway ;)