/* * Copyright 2002-2007 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springmodules.lucene.index.document.handler.object; import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.springframework.aop.support.AopUtils; /** * * @author Thierry Templier */ public abstract class AbstractAttributeObjectDocumentHandler extends AbstractObjectDocumentHandler { public final static Object INDEXABLE_CLASS = new Object(); public final static Object NON_INDEXABLE_CLASS = new Object(); public static final Object KEYWORD = "keyword"; public static final Object TEXT = "text"; protected Map cache = new HashMap(); protected Map indexableClasses = new HashMap(); /** * Determine a cache key for the given method and target class. * Must not produce same key for overloaded methods. * Must produce same key for different instances of the same method. * @param method the method * @param targetClass the target class (may be <code>null</code>) * @return the cache key */ protected Object getCacheKey(Method method, Class targetClass) { // TODO this works fine, but could consider making it faster in future: // Method.toString() is relatively (although not disastrously) slow. return targetClass + "." + method; } /** * Returns all the attributes found for the given method. */ protected abstract Collection findAllAttributes(Method method); /** * Returns all the attributes found for the given class. */ protected abstract Collection findAllAttributes(Class clazz); /** * Same return as getIndexAttribute method, but doesn't cache the result. * getIndexAttribute is a caching decorator for this method. */ private IndexAttribute computeIndexAttribute(Method method, Class targetClass) { // The method may be on an interface, but we need attributes from the target class. // The AopUtils class provides a convenience method for this. If the target class // is null, the method will be unchanged. Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); // First try is the method in the target class. IndexAttribute indexAtt = findIndexAttribute(findAllAttributes(specificMethod)); if (indexAtt != null) { return indexAtt; } // Second try is the transaction attribute on the target class. indexAtt = findIndexAttribute(findAllAttributes(specificMethod.getDeclaringClass())); if (indexAtt != null) { return indexAtt; } if (specificMethod != method ) { // Fallback is to look at the original method. indexAtt = findIndexAttribute(findAllAttributes(method)); if (indexAtt != null) { return indexAtt; } // Last fallback is the class of the original method. return findIndexAttribute(findAllAttributes(method.getDeclaringClass())); } return null; } public final IndexAttribute getIndexAttribute(Method method, Class targetClass) { // First, see if we have a cached value. Object cacheKey = getCacheKey(method, targetClass); Object cached = this.cache.get(cacheKey); if (cached != null) { // Value will either be canonical value indicating there is no transaction attribute, // or an actual transaction attribute. if (cached == NON_INDEXABLE_CLASS) { return null; } else { return (IndexAttribute) cached; } } else { // We need to work it out. IndexAttribute indexAtt = computeIndexAttribute(method, targetClass); // Put it in the cache. if (indexAtt == null) { this.cache.put(cacheKey, NON_INDEXABLE_CLASS); } else { if (logger.isDebugEnabled()) { logger.debug("Adding index method [" + method.getName() + "] with attribute [" + indexAtt + "]"); } this.cache.put(cacheKey, indexAtt); } return indexAtt; } } private Field createField(String fieldName, IndexAttribute indexAtt, Object value) { if( KEYWORD.equals(indexAtt.getType()) ) { return new Field(fieldName, String.valueOf(value), Field.Store.YES, Field.Index.UN_TOKENIZED); } else if( TEXT.equals(indexAtt.getType()) ) { return new Field(fieldName, String.valueOf(value), Field.Store.YES, Field.Index.TOKENIZED); } else { return new Field(fieldName, String.valueOf(value), Field.Store.YES, Field.Index.TOKENIZED); } } protected Document doGetDocument(Map description, Object object) throws Exception { Class clazz=object.getClass(); Method[] methods=clazz.getDeclaredMethods(); Document document = new Document(); document.add(new Field("class", clazz.getName(), Field.Store.YES, Field.Index.UN_TOKENIZED)); for(int cpt=0;cpt<methods.length;cpt++) { String name=methods[cpt].getName(); IndexAttribute indexAtt=computeIndexAttribute(methods[cpt], clazz); if( indexAtt==null || indexAtt.isExcluded() ) { continue; } if( name.startsWith(PREFIX_ACCESSOR) ) { String fieldName=null; if( !("".equals(indexAtt.getName())) ) { fieldName=indexAtt.getName(); } else { fieldName=constructFieldName(name); } Object value=methods[cpt].invoke(object,new Object[] {}); document.add(createField(fieldName, indexAtt, value)); } } return document; } /** * Return the indexable attribute, given this set of attributes * attached to a method or class. Overrides method from parent class. * This version actually converts JDK 5.0+ Annotations to the Spring * classes. Returns null if it's not indexable. * @param atts attributes attached to a method or class. May * be <code>null</code>, in which case a null IndexAttribute will be returned. * @return IndexAttribute configured indexable attribute, * or <code>null</code> if none was found */ protected abstract IndexAttribute findIndexAttribute(Collection atts); protected abstract Object findIndexClassProperty(Class clazz); public boolean supports(Class clazz) { Object indexableClass=indexableClasses.get(clazz); if( indexableClass!=null && indexableClass==INDEXABLE_CLASS ) { return true; } indexableClass=findIndexClassProperty(clazz); indexableClasses.put(clazz,indexableClass); return (indexableClass==INDEXABLE_CLASS); } }