-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 
Author Message
 Post subject: HibernateSearch and custom PropertyAccessors
PostPosted: Fri Apr 03, 2009 9:19 am 
Newbie

Joined: Fri Apr 03, 2009 8:30 am
Posts: 3
Location: Ulm, Germany
Hi!

I'm using Hibernate without Annotations, and I'd like to add a full text search to my persistent objects, using HibernateSearch.

My persistent class Event has a field called '_id' and two accessor methods which are not conform to the JavaBean specification. In order to persist this class with Hibernate, I wrote my own PropertyAccessor which maps this property to 'id'. Well, this works fine, except if i try to perform a full text search using HibernateSearch. A QueryException is raised, when i try to call the 'list()' method on the Query returned by HibernateSearch. I debugged through the sources of HibernateSearch and I noticed that the DocumentBuilder class retrieves the name of the id property by using reflection on my Event class and thus tries to access the property '_id'. It does not care about the PropertyAccessor i configured in the mapping file (method initalizeMember() in DocumentBuilder).

Is there a way to circumvent this Problem?

Best regards,
Ralf!

I use HibernateCore v3.2 and HibernateSearch v3.0.1 with Sun's JVM 1.5.0_16 on Linux. I also tried it with Core 3.3/Search 3.1. Doesn't work either. Stacktrace and Code can be found below.

Code:
Exception in thread "main" org.hibernate.QueryException: could not resolve property: _id of: test.Event
   at org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:67)
   at org.hibernate.persister.entity.AbstractPropertyMapping.toType(AbstractPropertyMapping.java:61)
   at org.hibernate.persister.entity.AbstractEntityPersister.getSubclassPropertyTableNumber(AbstractEntityPersister.java:1402)
   at org.hibernate.persister.entity.BasicEntityPropertyMapping.toColumns(BasicEntityPropertyMapping.java:54)
   at org.hibernate.persister.entity.AbstractEntityPersister.toColumns(AbstractEntityPersister.java:1377)
   at org.hibernate.loader.criteria.CriteriaQueryTranslator.getColumns(CriteriaQueryTranslator.java:457)
   at org.hibernate.loader.criteria.CriteriaQueryTranslator.getColumnsUsingProjection(CriteriaQueryTranslator.java:417)
   at org.hibernate.criterion.InExpression.toSqlString(InExpression.java:55)
   at org.hibernate.criterion.Junction.toSqlString(Junction.java:82)
   at org.hibernate.loader.criteria.CriteriaQueryTranslator.getWhereCondition(CriteriaQueryTranslator.java:357)
   at org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:113)
   at org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:82)
   at org.hibernate.loader.criteria.CriteriaLoader.<init>(CriteriaLoader.java:91)
   at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1577)
   at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:306)
   at org.hibernate.search.engine.ObjectLoaderHelper.initializeObjects(ObjectLoaderHelper.java:66)
   at org.hibernate.search.engine.QueryLoader.load(QueryLoader.java:42)
   at org.hibernate.search.query.FullTextQueryImpl.list(FullTextQueryImpl.java:284)
   at test.Main.search(Main.java:83)
   at test.Main.searchEvents(Main.java:67)
   at test.main(Main.java:36)


Code:
@Indexed
public class Event {
    @DocumentId
    private Long _id;

    @Field(index = Index.TOKENIZED, store = Store.NO)
    private String title;

    @Field(index = Index.TOKENIZED, store = Store.NO)
    @DateBridge(resolution = Resolution.DAY)
    private Date date;

    public Event() {}

    public Long _id() {
        return _id;
    }
    private void _id(Long id) {
        this._id = id;
    }
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}


Code:
<hibernate-mapping>
    <class name="test.Event" table="EVENTS">
        <id name="id"
            access="test.EventPropertyAccessor"
            type="long"
            column="EVENT_ID">
                <generator class="native"/>
        </id>
   <property name="date"
            type="timestamp" column="EVENT_DATE"/>
        <property name="title"/>
    </class>
</hibernate-mapping>
[/code]


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 03, 2009 9:54 am 
Hibernate Team
Hibernate Team

Joined: Thu Apr 05, 2007 5:52 am
Posts: 1689
Location: Sweden
Hi,

I am a little confused about your stack trace. First of all, you cannot expect that Hibernate Search for the Document building part uses you PropertyAccessor. The document id is orthogonal to the entity id. The document id can be on any property/field.

That said, the stacktrace does not seem to come out of the Search code directly, but rather out of a criteria query. Maybe it would help to see some code.

--Hardy


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 03, 2009 10:22 am 
Newbie

Joined: Fri Apr 03, 2009 8:30 am
Posts: 3
Location: Ulm, Germany
Hi Hardy,

thanks for the quick reply.

hardy.ferentschik wrote:
That said, the stacktrace does not seem to come out of the Search code directly, but rather out of a criteria query. Maybe it would help to see some code.


That's right. The exception does occur, when i call 'list()' on the FullTextQuery object, which is returned by HibernateSearch. Creating the Query object (i.e. calling 'createFullTextQuery()' on the Session) does not raise an exception. But doesn't this mean, that the FullTextQuery returned by HibernateSearch is not correct? It contains '_id' where it should contain 'id'.

The code of the remaining files (Main.java, EventPropertyAccessor.java) is below.

Regards, Ralf!

Code:
package test;

import java.util.Date;
import java.util.List;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.Query;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;


public class Main {

   private static SessionFactory factory;
   
   static {
      factory =  new Configuration().configure().buildSessionFactory();
   }
   
   public static void main(String[] args) throws Exception {
      createEvents();

      indexEvents();
      

      List<Event> result1 = searchEvents("Event");
      for (Event event : result1) {
         System.out.println(event.getTitle());
      }

      factory.close();
   }

   
   private static void createEvents() {
      Session session = factory.getCurrentSession();
      Transaction tx = session.beginTransaction();
      Event e1 = new Event();
      e1.setDate(new Date());
      e1.setTitle("Event One");
      session.save(e1);
      Event e2 = new Event();
      e2.setDate( new Date());
      e2.setTitle("Event two");
      session.save(e2);
      tx.commit();
   }

   private static List<Event> searchEvents(String queryString) throws ParseException {
      String[] fields = new String[] {"title"};
      
      return search(queryString, fields, Event.class);
   }

   private static <T> List<T> search(String queryString, String[] fields, Class<T> theClass) throws ParseException {
      Session session = factory.getCurrentSession();
      FullTextSession fullTextSession = Search.getFullTextSession(session);
      Transaction tx = fullTextSession.beginTransaction();
      
      MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
      Query query = parser.parse( queryString);
      
      org.hibernate.Query hibQuery = fullTextSession.createFullTextQuery(query, theClass);

      @SuppressWarnings("unchecked")
      List<T> result = hibQuery.list();

      tx.commit();
      return result;
   }

   private static void indexEvents() {
      Session session = factory.getCurrentSession();
      FullTextSession fullTextSession = Search.getFullTextSession(session);
      Transaction tx = fullTextSession.beginTransaction();

      @SuppressWarnings("unchecked")
      List<Event> events = session.createQuery("from Event as event").list();
      for (Event event : events) {
         fullTextSession.index(event);
      }
      
      tx.commit();
   }
}


Code:
package test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

import org.hibernate.HibernateException;
import org.hibernate.PropertyAccessException;
import org.hibernate.PropertyNotFoundException;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.property.Getter;
import org.hibernate.property.PropertyAccessor;
import org.hibernate.property.Setter;


public class EventPropertyAccessor implements PropertyAccessor {

    @SuppressWarnings("unchecked")
    public Getter getGetter(Class clazz, String propertyName) throws PropertyNotFoundException {
        return createGetter(clazz, propertyName);
    }

    @SuppressWarnings("unchecked")
    public Setter getSetter(Class clazz, String propertyName) throws PropertyNotFoundException {
        return createSetter(clazz, propertyName);
    }

    private static Getter createGetter(Class<?> clazz, String propertyName) {
       Method getterMethod = findGetterMethod(clazz, propertyName);
       return new BasicGetter(clazz, getterMethod, propertyName);
    }
   
    private static Setter createSetter(Class<?> theClass, String propertyName) {
        Method getter = findGetterMethod(theClass, propertyName);
        try {
           Method m = theClass.getDeclaredMethod(getter.getName(), getter.getReturnType());
           m.setAccessible(true);
           return new BasicSetter(theClass, m, propertyName);
        } catch (NoSuchMethodException e) {
            throw new PropertyNotFoundException("Could not find a setter for property " + propertyName + " in class " +
                    theClass.getName());
        }
    }

    private static final Method findGetterMethod(Class<?> theClass, String propertyName)
            throws PropertyNotFoundException {
       try {
          Method m = theClass.getDeclaredMethod("_" + propertyName, new Class<?>[0]);
          m.setAccessible(true);
          return m;
        } catch (NoSuchMethodException e) {
           throw new PropertyNotFoundException("Could not find a getter for " + propertyName + " in class " +
                 theClass.getName());
        }
    }
    private static final class BasicGetter implements Getter {
        private static final long serialVersionUID = 796233579190385370L;
        private Class<?> clazz;
        private final transient Method method;
        private final String propertyName;

        BasicGetter(Class<?> clazz, Method method, String propertyName) {
            this.clazz = clazz;
            this.method = method;
            this.propertyName = propertyName;
        }

        public Object get(Object target) throws HibernateException {
            try {
                return method.invoke(target, (Object[]) null);
            } catch (InvocationTargetException ite) {
                throw new PropertyAccessException(ite, "Exception occurred inside", false, clazz, propertyName);
            } catch (IllegalAccessException iae) {
                throw new PropertyAccessException(iae, "IllegalAccessException occurred while calling", false, clazz,
                        propertyName);
                // cannot occur
            } catch (IllegalArgumentException iae) {
                throw new PropertyAccessException(iae, "IllegalArgumentException occurred calling", false, clazz,
                        propertyName);
            }
        }

        @SuppressWarnings("unchecked")
        public Object getForInsert(Object target, Map mergeMap, SessionImplementor session) {
            return get(target);
        }

        public Class<?> getReturnType() {
            return method.getReturnType();
        }

        public Method getMethod() {
            return method;
        }

        public String getMethodName() {
            return method.getName();
        }

        @Override
        public String toString() {
            return "BasicGetter(" + clazz.getName() + '.' + propertyName + ')';
        }

        Object readResolve() {
            return EventPropertyAccessor.createGetter(clazz, propertyName);
        }
    }
   
    private static final class BasicSetter implements Setter {
        private static final long serialVersionUID = -8350321782961402054L;
        private Class<?> clazz;
        private final transient Method method;
        private final String propertyName;

        BasicSetter(Class<?> clazz, Method method, String propertyName) {
            this.clazz = clazz;
            this.method = method;
            this.propertyName = propertyName;
        }

        public void set(Object target, Object value, SessionFactoryImplementor factory) throws HibernateException {
            try {
                method.invoke(target, new Object[] { value });
            } catch (NullPointerException npe) {
                if (value == null && method.getParameterTypes()[0].isPrimitive()) {
                    throw new PropertyAccessException(npe, "Null value was assigned to a property of primitive type", true,
                            clazz, propertyName);
                }
                throw new PropertyAccessException(npe, "NullPointerException occurred while calling", true, clazz,
                        propertyName);
            } catch (InvocationTargetException ite) {
                throw new PropertyAccessException(ite, "Exception occurred inside", true, clazz, propertyName);
            } catch (IllegalAccessException iae) {
                throw new PropertyAccessException(iae, "IllegalAccessException occurred while calling", true, clazz,
                        propertyName);
                // cannot occur
            } catch (IllegalArgumentException iae) {
                if (value == null && method.getParameterTypes()[0].isPrimitive()) {
                    throw new PropertyAccessException(iae, "Null value was assigned to a property of primitive type", true,
                            clazz, propertyName);
                }
                throw new PropertyAccessException(iae, "IllegalArgumentException occurred while calling", true, clazz,
                        propertyName);
            }
        }

        public Method getMethod() {
            return method;
        }

        public String getMethodName() {
            return method.getName();
        }

        Object readResolve() {
            return EventPropertyAccessor.createSetter(clazz, propertyName);
        }

        @Override
        public String toString() {
            return "BasicSetter(" + clazz.getName() + '.' + propertyName + ')';
        }
    }

}


Code:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">org.apache.derby.jdbc.ClientDriver</property>
        <property name="connection.url">jdbc:derby://localhost:1527/HibernateTest</property>
       
        <property name="connection.username">test</property>
        <property name="connection.password">test</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.DerbyDialect</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">create</property>

      <!-- Hibernate search properties -->
      <property name="hibernate.search.default.directory_provider">org.hibernate.search.store.RAMDirectoryProvider</property>

        <mapping resource="Event.hbm.xml"/>

      <!-- Register event listeners -->
        <event type="post-update">
            <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
        </event>
        <event type="post-insert">
            <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
        </event>
        <event type="post-delete">
            <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
        </event>


    </session-factory>

</hibernate-configuration>


Top
 Profile  
 
 Post subject:
PostPosted: Mon Apr 06, 2009 6:06 am 
Newbie

Joined: Fri Apr 03, 2009 8:30 am
Posts: 3
Location: Ulm, Germany
Hi again!

I did some further investigation on this. It seams to me, that it should be possible to circumvent this by explicitly specifying a name for the DocumentID. So i changed the Annotation in the Event Class to
Code:
@Indexed
public class Event {
   
   @DocumentId(name="id")
   private Long _id;

...
}


Unfortunately this did not help either. I tried to figure out why this name is ignored when executing the query, and I found that in "org.hibernate.search.engine.QueryLoader.load()" the name of the ID is retrieved by calling "String idName = builder.getIdentifierName();". In my case, builder.getIdentifierName() returns "_id" whereas calling "builder.getIdKeywordName()" here would return the correct name ("id"). So my question is: Is this behaviour correct? If so, why is it possible to specify a name for the DocumentID explicitly when this is ignored during the execution of the Query?

regards, Ralf


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.