[jboss-cvs] jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/search/metamodel ...
Christian Bauer
christian at hibernate.org
Tue Jun 12 08:30:00 EDT 2007
User: cbauer
Date: 07/06/12 08:30:00
Added: examples/wiki/src/main/org/jboss/seam/wiki/core/search/metamodel
SearchableEntity.java SearchableProperty.java
SearchablePropertySingle.java
SearchablePropertyComposite.java SearchSupport.java
SearchableEntityHandler.java SearchRegistry.java
Log:
Completed first iteration of search engine
Revision Changes Path
1.1 date: 2007/06/12 12:30:00; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/search/metamodel/SearchableEntity.java
Index: SearchableEntity.java
===================================================================
package org.jboss.seam.wiki.core.search.metamodel;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Meta-information about a particular entity that can be searched.
* <p>
* Provides global statistics properties which need to be set before they are read - this is
* just a value holder.
*
* @author Christian Bauer
*/
public class SearchableEntity implements Serializable, Comparable {
private Class clazz;
private String description;
private SearchableEntityHandler handler;
private List<SearchableProperty> properties = new ArrayList<SearchableProperty>();
public SearchableEntity(Class clazz) {
this.clazz = clazz;
}
public SearchableEntity(Class clazz, String description, SearchableEntityHandler handler) {
this.clazz = clazz;
this.description = description;
this.handler = handler;
}
public Class getClazz() {
return clazz;
}
public String getDescription() {
return description;
}
public SearchableEntityHandler getHandler() {
return handler;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SearchableEntity that = (SearchableEntity) o;
if (clazz != null ? !clazz.equals(that.clazz) : that.clazz != null) return false;
return true;
}
public int hashCode() {
return (clazz != null ? clazz.hashCode() : 0);
}
public List<SearchableProperty> getProperties() {
return properties;
}
public void setProperties(List<SearchableProperty> properties) {
this.properties = properties;
}
// Some stats, used for admin screens
private long numOfIndexedDocuments;
private long numOfIndexedTerms;
private long indexSizeInBytes;
public long getNumOfIndexedDocuments() {
return numOfIndexedDocuments;
}
public void setNumOfIndexedDocuments(long numOfIndexedDocuments) {
this.numOfIndexedDocuments = numOfIndexedDocuments;
}
public long getNumOfIndexedTerms() {
return numOfIndexedTerms;
}
public void setNumOfIndexedTerms(long numOfIndexedTerms) {
this.numOfIndexedTerms = numOfIndexedTerms;
}
public long getIndexSizeInBytes() {
return indexSizeInBytes;
}
public void setIndexSizeInBytes(long indexSizeInBytes) {
this.indexSizeInBytes = indexSizeInBytes;
}
public int compareTo(Object o) {
return getDescription().compareTo( ((SearchableEntity)o).getDescription() );
}
public String toString() {
return getClazz().getName();
}
}
1.1 date: 2007/06/12 12:30:00; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/search/metamodel/SearchableProperty.java
Index: SearchableProperty.java
===================================================================
package org.jboss.seam.wiki.core.search.metamodel;
import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.jboss.seam.log.Log;
import org.jboss.seam.wiki.core.search.PropertySearch;
import org.jboss.seam.wiki.core.search.annotations.SearchableType;
import java.io.Serializable;
import java.io.StringReader;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.regex.Pattern;
/**
* Meta-information about a logical searchable property.
* <p>
* Generalized building of Lucene queries, called by subclasses.
* <p>
* TODO: Implement NUMRANGE query building
*
* @author Christian Bauer
*/
public abstract class SearchableProperty implements Serializable, Comparable {
public static final String TERM_INCLUDE = "include";
public static final String TERM_EXCLUDE = "exclude";
public static final String TERM_MATCHEXACTPHRASE = "matchExactPhrase";
public static final String TERM_NUMOFDAYS = "numOfDays";
protected Log log = org.jboss.seam.log.Logging.getLog(getClass());
private String description;
private SearchableType type;
public SearchableProperty() {}
public SearchableProperty(String description, SearchableType type) {
this.description = description;
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public SearchableType getType() {
return type;
}
public void setType(SearchableType type) {
this.type = type;
}
public int compareTo(Object o) {
return getDescription().compareTo( ((SearchableProperty)o).getDescription() );
}
public abstract Query getQuery(PropertySearch search);
protected Query buildIncludeQuery(String fieldName, PropertySearch search) {
Query query = null;
if (getType().equals(SearchableType.PHRASE)) {
String includeString = (String)search.getTerms().get(TERM_INCLUDE);
Boolean matchExactPhrase = (Boolean)search.getTerms().get(TERM_MATCHEXACTPHRASE);
if (includeString != null && includeString.length() >0) {
if(matchExactPhrase != null && matchExactPhrase) {
log.debug("building include phrase query for field: " + fieldName);
query = buildPhraseQuery(fieldName, includeString);
} else {
log.debug("building include term query for field: " + fieldName);
query = buildTermQuery(fieldName, includeString);
}
}
} else if (getType().equals(SearchableType.PASTDATE)) {
String numOfDays = (String)search.getTerms().get(TERM_NUMOFDAYS);
if (numOfDays != null && numOfDays.length() >0) {
log.debug("building past date query for field: " + fieldName);
DateFormat df = new SimpleDateFormat("yyyyMMdd");
Calendar today = new GregorianCalendar();
Calendar startDate = new GregorianCalendar();
startDate.add(Calendar.DAY_OF_YEAR, -Integer.valueOf(numOfDays));
log.debug("date range query start: " + df.format(startDate.getTime()) );
log.debug("date range query end: " + df.format(today.getTime()) );
query = buildRangeQuery(fieldName, df.format(startDate.getTime()), df.format(today.getTime()));
}
}
return query;
}
protected Query buildExcludeQuery(String fieldName, PropertySearch search) {
Query query = null;
if (getType().equals(SearchableType.PHRASE)) {
log.debug("building exclude phrase query for field: " + fieldName);
String includeString = (String)search.getTerms().get(TERM_INCLUDE);
String excludeString = (String)search.getTerms().get(TERM_EXCLUDE);
Boolean matchExactPhrase = (Boolean)search.getTerms().get(TERM_MATCHEXACTPHRASE);
if (includeString != null && includeString.length() >0 && excludeString != null && excludeString.length() > 0) {
if(matchExactPhrase != null && matchExactPhrase) {
log.debug("building exclude phrase query for field: " + fieldName);
query = buildPhraseQuery(fieldName, includeString);
} else {
log.debug("building exclude term query for field: " + fieldName);
query = buildTermQuery(fieldName, excludeString);
}
}
}
return query;
}
private Query buildPhraseQuery(String fieldName, String terms) {
try {
PhraseQuery query = new PhraseQuery();
query.setSlop(0);
TokenStream includeStream =
new StandardAnalyzer().tokenStream(null, new StringReader(escape(terms).toLowerCase()));
while (true) {
Token t = includeStream.next();
if (t == null) break;
query.add( new Term(fieldName, t.termText()) );
}
return query.getTerms().length > 0 ? query : null;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private Query buildTermQuery(String fieldName, String terms) {
String[] termsArray = escape(terms).toLowerCase().split("\\s"); // Just split user input by whitespace
if (termsArray.length > 1) {
BooleanQuery query = new BooleanQuery();
for (String s: termsArray) {
TermQuery termQuery = new TermQuery(new Term(fieldName, s) );
query.add(termQuery, BooleanClause.Occur.SHOULD);
}
return query.getClauses().length > 0 ? query : null;
} else {
return termsArray.length != 0 ? new TermQuery(new Term(fieldName, termsArray[0])) : null;
}
}
private Query buildRangeQuery(String fieldName, String begin, String end) {
return new ConstantScoreRangeQuery(fieldName, begin, end, true, true); // Inclusive of begin and end
}
private String escape(String userInput) {
userInput = userInput.replaceAll(Pattern.quote("("), "\\(");
userInput = userInput.replaceAll(Pattern.quote(")"), "\\)");
return userInput;
}
}
1.1 date: 2007/06/12 12:30:00; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/search/metamodel/SearchablePropertySingle.java
Index: SearchablePropertySingle.java
===================================================================
package org.jboss.seam.wiki.core.search.metamodel;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BooleanClause;
import org.jboss.seam.wiki.core.search.PropertySearch;
import org.jboss.seam.wiki.core.search.annotations.SearchableType;
/**
* A logical searchable property of a single indexed field.
*
* @author Christian Bauer
*/
public class SearchablePropertySingle extends SearchableProperty {
private String name;
public SearchablePropertySingle(String name, String description, SearchableType type) {
super(description, type);
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return getName();
}
public Query getQuery(PropertySearch search) {
BooleanQuery query = new BooleanQuery();
Query iq = buildIncludeQuery(getName(), search);
if (iq != null) {
log.debug("include query: " + iq.toString());
query.add(iq, BooleanClause.Occur.MUST );
Query eq= buildExcludeQuery(getName(), search);
if (eq != null) {
log.debug("exclude query: " + eq.toString());
query.add(eq, BooleanClause.Occur.MUST_NOT);
}
}
return query.getClauses().length > 0 ? query : null;
}
}
1.1 date: 2007/06/12 12:30:00; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/search/metamodel/SearchablePropertyComposite.java
Index: SearchablePropertyComposite.java
===================================================================
package org.jboss.seam.wiki.core.search.metamodel;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Query;
import org.jboss.seam.wiki.core.search.PropertySearch;
import org.jboss.seam.wiki.core.search.annotations.SearchableType;
/**
* A logical searchable property that is a composite of several indexed fields.
*
* @author Christian Bauer
*/
public class SearchablePropertyComposite extends SearchableProperty {
private String[] names;
public SearchablePropertyComposite(String[] names, String description, SearchableType type) {
super(description, type);
this.names = names;
}
public String[] getNames() {
return names;
}
public void setNames(String[] names) {
this.names = names;
}
public String toString() {
String name = "Composite: ";
for (String s : getNames()) {
name += s + " ";
}
return name;
}
public Query getQuery(PropertySearch search) {
BooleanQuery query = new BooleanQuery();
BooleanQuery includeQuery = new BooleanQuery();
BooleanQuery excludeQuery = new BooleanQuery();
for (String s : getNames()) {
Query iq = buildIncludeQuery(s, search);
if (iq != null) {
log.debug("include query: " + iq.toString());
includeQuery.add(iq, BooleanClause.Occur.SHOULD);
Query eq = buildExcludeQuery(s, search);
if (eq != null) {
log.debug("exclude query: " + eq.toString());
excludeQuery.add(eq, BooleanClause.Occur.SHOULD);
}
}
}
if (includeQuery.getClauses().length > 0) query.add(includeQuery, BooleanClause.Occur.MUST);
if (excludeQuery.getClauses().length > 0) query.add(excludeQuery, BooleanClause.Occur.MUST_NOT);
return query.getClauses().length > 0 ? query : null;
}
}
1.1 date: 2007/06/12 12:30:00; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/search/metamodel/SearchSupport.java
Index: SearchSupport.java
===================================================================
package org.jboss.seam.wiki.core.search.metamodel;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.ScopeType;
import org.jboss.seam.wiki.util.WikiUtil;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.search.Query;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import java.util.Set;
import java.io.StringReader;
/**
* Superclass for search support, extend it to add search options to the wiki.
* <p>
* Extend this class and return <tt>SearchableEntityHandler</tt> instances for each
* entity you want to be able to search in the user interface. The handlers need to
* be able to extract a <tt>SearchHit</tt> from a given query and the original entity
* instance. This <tt>SearchHit</tt> is then displayed. If you have a string-based
* property and you want to simply show the "best" fragments of a hit, use the
* <tt>escapeBestFragments()</tt> convenience method.
* <p>
* Note that you also need to annotate any entity class and its properties
* with <tt>@Searchable</tt>.
*
* @see org.jboss.seam.wiki.core.search.annotations.Searchable
* @see SearchableEntityHandler
* @see org.jboss.seam.wiki.core.search.SearchHit
*
* @author Christian Bauer
*/
@Scope(ScopeType.APPLICATION)
public abstract class SearchSupport {
private static final String INTERNAL_BEGIN_HIT = "!!!BEGIN_HIT!!!";
private static final String INTERNAL_END_HIT = "!!!END_HIT!!!";
@Observer("Search.addSearchSupport")
public void add(Set<SearchSupport> searchSupportComponents) {
searchSupportComponents.add(this);
}
/**
* Returns the hits of the given query as fragments, highlighted, concatenated, and separated.
* <p>
* Pass in a <tt>NullFragmenter</tt> if you don't want any fragmentation by terms but
* simply the hits highlighted. Otherwise, you will most likely use <tt>SimpleFragmenter</tt>.
* The text you supply must be the same that was indexed, it will be run through the same
* analysis procedure to find the hits. Do not pass a different String than the one indexed
* by Hibernate Search! If you use transparent string bridge with Hibernate Search, run the
* bridge before passing the string into this method.
* <p>
* This method escapes any dangerous HTML characters in the indexed text and fragments by
* replacing it with HTML entities. You can use the returned string directly to build a
* <tt>SearchHit</tt>.
*
* @param query the query that produced hits
* @param fragmenter a fragmenter that can split the indexed text
* @param indexedText the original text that was analyzed and indexed by Hibernate Search (after any bridges!)
* @param numOfFragments the number of fragments to include in the returned result
* @param alternativeLength if there are no hits to highlight, how many characters of the original text to return
* @return the fragmented, highglighted, and then concatenated substring of the indexed text
*/
protected String escapeBestFragments(Query query, Fragmenter fragmenter,
String indexedText, int numOfFragments, int alternativeLength) {
// The HTML escaping forces us to first fragment with internal placeholders...
Highlighter highlighter = new Highlighter(new SimpleHTMLFormatter(INTERNAL_BEGIN_HIT, INTERNAL_END_HIT), new QueryScorer(query));
highlighter.setTextFragmenter(fragmenter);
try {
// Use the same analyzer as the indexer!
TokenStream tokenStream = new StandardAnalyzer().tokenStream(null, new StringReader(indexedText));
String unescapedFragements =
highlighter.getBestFragments(tokenStream, indexedText, numOfFragments, getFragmentSeparator());
String escapedFragments = WikiUtil.escapeHtml(unescapedFragements);
// .. and then replace the internal placeholders with real tags after HTML has been escaped
escapedFragments = escapedFragments.replaceAll(INTERNAL_BEGIN_HIT, getBeginHitTag());
escapedFragments = escapedFragments.replaceAll(INTERNAL_END_HIT, getEndHitTag());
// If no fragments were produced (no hits), return the original text as an alternative
if (escapedFragments.length() == 0 && alternativeLength != 0) {
return WikiUtil.escapeHtml(
indexedText.substring(
0,
indexedText.length()>alternativeLength ? alternativeLength : indexedText.length()
)
);
} else if (escapedFragments.length() == 0 && alternativeLength == 0){
return WikiUtil.escapeHtml(indexedText);
}
return escapedFragments;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* String used to mark the beginning of a fragment.
* <p>
* Defaults to <:b>, can be overriden by subclass.
*
* @return String used to mark the beginning of a fragment.
*/
protected String getBeginHitTag() {
return "<b>";
}
/**
* String used to mark the end of a fragment.
* <p>
* Defaults to <:/b>, can be overriden by subclass.
*
* @return String used to mark the end of a fragment.
*/
protected String getEndHitTag() {
return "</b>";
}
/**
* Separator string between two fragments.
* <p>
* Defaults to <tt>... ...</tt> (just dots with a space).
*
* @return Separator string between two fragments.
*/
protected String getFragmentSeparator() {
return "... ...";
}
/**
* Create an return any <tt>SearchableEntityHandler</tt> you require search functionality for.
* <p>
* This is called on startup only by the internal registry, to assemble all handlers.
*
* @return SearchableEntityHandler typed for a particular indexed/searchable entity class
*/
public abstract Set<SearchableEntityHandler> getSearchableEntityHandlers();
}
1.1 date: 2007/06/12 12:30:00; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/search/metamodel/SearchableEntityHandler.java
Index: SearchableEntityHandler.java
===================================================================
package org.jboss.seam.wiki.core.search.metamodel;
import org.apache.lucene.search.Query;
import org.jboss.seam.wiki.core.search.SearchHit;
import java.lang.reflect.ParameterizedType;
/**
* Superclass of a search handler for a particular searchable entity.
* <p>
* Extend this class to complete the search functionality for a particular entity, in
* addition to placing <tt>@Searchable</tt> annotations on it. This handler extracts
* hits from a search result (does highlighting, fragmentation, etc.) and supports
* other dynamically overridable metadata (not much in this first incarnation).
*
* @author Christian Bauer
*/
public abstract class SearchableEntityHandler<E> {
private Class<E> searchableEntityClass;
public SearchableEntityHandler() {
//noinspection unchecked
this.searchableEntityClass = (Class<E>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
public Class<E> getSearchableEntityClass() {
return searchableEntityClass;
}
public boolean isReadAccessChecked() { return false; }
public abstract SearchHit extractHit(Query query, E hit) throws Exception;
}
1.1 date: 2007/06/12 12:30:00; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/search/metamodel/SearchRegistry.java
Index: SearchRegistry.java
===================================================================
package org.jboss.seam.wiki.core.search.metamodel;
import org.jboss.seam.annotations.*;
import org.jboss.seam.ScopeType;
import org.jboss.seam.wiki.core.search.annotations.*;
import org.jboss.seam.core.Events;
import org.jboss.seam.log.Log;
import java.util.*;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.annotation.Annotation;
import java.beans.Introspector;
/**
* Runs on startup and reads all <tt>@Searchable</tt> entities.
* <p>
* Extracts all annotated entities and properties and builds a registry of metadata that is
* used by the search engine to render the UI, build queries, extract hits, etc.
* <p>
* TODO: Once the Seam scanner is public, all of this can be simplified and
* even the SearchableEntityHandlers can be declared with annotations
*
* @author Christian Bauer
*/
@Name("searchRegistry")
@Scope(ScopeType.APPLICATION)
@Startup(depends = "wikiInit")
public class SearchRegistry {
@Logger
static Log log;
Map<String, SearchableEntity> searchableEntitiesByName = new HashMap<String, SearchableEntity>();
List<SearchableEntity> searchableEntities = new ArrayList<SearchableEntity>();
@Create
public void scanForSearchSupportComponents() {
log.debug("initializing search registry");
searchableEntities.clear();
searchableEntitiesByName.clear();
// Fire an event and let all listeners add themself into the given collection
// TODO: Is this smarter than PreferenceRegistry scanning approach?
Set<SearchSupport> searchSupportComponents = new HashSet<SearchSupport>();
Events.instance().raiseEvent("Search.addSearchSupport", searchSupportComponents);
log.debug("found search support components: " + searchSupportComponents.size());
for (SearchSupport component : searchSupportComponents) {
for (SearchableEntityHandler searchableEntityHandler : component.getSearchableEntityHandlers()) {
SearchableEntity searchableEntity = extractSearchableEntity(searchableEntityHandler);
searchableEntities.add(searchableEntity);
searchableEntitiesByName.put(searchableEntity.getClazz().getName(), searchableEntity);
}
}
// Sort entities
Collections.sort(searchableEntities);
log.debug("done extracting metadata for searchable entities: " + searchableEntities.size());
}
private SearchableEntity extractSearchableEntity(SearchableEntityHandler handler) {
Class<?> entityClass = handler.getSearchableEntityClass();
if (!entityClass.isAnnotationPresent(Searchable.class) ||
!entityClass.isAnnotationPresent(org.hibernate.search.annotations.Indexed.class)) {
log.error("Not indexed or not searchable but configured as searchable: " + entityClass.getName());
return null;
}
log.debug("extracting entity search information from: " + entityClass.getName());
// Extract entity information
String entityDescription = entityClass.getAnnotation(Searchable.class).description();
SearchableEntity searchableEntity = new SearchableEntity(entityClass, entityDescription, handler);
// Extract composite property information
if (entityClass.isAnnotationPresent(CompositeSearchable.class)) {
searchableEntity.getProperties().add(
extractCompositeSearchable(entityClass, entityClass.getAnnotation(CompositeSearchable.class))
);
}
if (entityClass.isAnnotationPresent(CompositeSearchables.class)) {
for (CompositeSearchable compositeSearchable : entityClass.getAnnotation(CompositeSearchables.class).value()) {
searchableEntity.getProperties().add(extractCompositeSearchable(entityClass, compositeSearchable));
}
}
// Extract property information
String propertyName;
String indexFieldName;
String propertyDescription;
SearchableType type;
// @Searchable getter methods
for (Method method : getGetters(entityClass, Searchable.class, org.hibernate.search.annotations.Field.class)) {
indexFieldName = method.getAnnotation(org.hibernate.search.annotations.Field.class).name();
propertyName = Introspector.decapitalize(method.getName().substring(3));
propertyDescription = method.getAnnotation(Searchable.class).description();
type = method.getAnnotation(Searchable.class).type();
SearchableProperty property = new SearchablePropertySingle(
indexFieldName != null && indexFieldName.length() > 0 ? indexFieldName : propertyName,
propertyDescription,
type
);
searchableEntity.getProperties().add(property);
}
// @Searchable fields
for (Field field : getFields(entityClass, Searchable.class, org.hibernate.search.annotations.Field.class)) {
indexFieldName = field.getAnnotation(org.hibernate.search.annotations.Field.class).name();
propertyName = field.getName();
propertyDescription = field.getAnnotation(Searchable.class).description();
type = field.getAnnotation(Searchable.class).type();
SearchableProperty property = new SearchablePropertySingle(
indexFieldName != null && indexFieldName.length() > 0? indexFieldName : propertyName,
propertyDescription,
type
);
searchableEntity.getProperties().add(property);
}
return searchableEntity;
}
private SearchablePropertyComposite extractCompositeSearchable(Class entityClass, CompositeSearchable compositeSearchable) {
// Get all fields/getter methods with a Hibernate Search @Field annotation
Set<String> searchableProperties = new HashSet<String>();
for (Method method : getGetters(entityClass, org.hibernate.search.annotations.Field.class)) {
String indexFieldName = method.getAnnotation(org.hibernate.search.annotations.Field.class).name();
String propertyName = Introspector.decapitalize(method.getName().substring(3));
searchableProperties.add(
indexFieldName != null && indexFieldName.length() > 0? indexFieldName : propertyName
);
}
for (Field field : getFields(entityClass, org.hibernate.search.annotations.Field.class)) {
String propertyName = field.getName();
String indexFieldName = field.getAnnotation(org.hibernate.search.annotations.Field.class).name();
searchableProperties.add(
indexFieldName != null && indexFieldName.length() > 0? indexFieldName : propertyName
);
}
// Validate configured composite property names against fields/getters
for (String s : compositeSearchable.properties()) {
if (!searchableProperties.contains(s)) {
throw new RuntimeException(
"No indexed field/getter could be found for " +
"configured searchable composite property '" + s + "' in entity: " + entityClass.getName());
}
}
return new SearchablePropertyComposite(
compositeSearchable.properties(),
compositeSearchable.description(),
compositeSearchable.type()
);
}
private Method[] getGetters(Class clazz, Class<? extends Annotation>... requiredAnnotations) {
List<Method> allMethods = new ArrayList<Method>();
Class c = clazz;
do {
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
boolean annotationsPresent = true;
for (Class<? extends Annotation> requiredAnnotation : requiredAnnotations) {
if (!method.isAnnotationPresent(requiredAnnotation)) annotationsPresent = false;
}
if (annotationsPresent &&
method.getName().startsWith("get") &&
method.getParameterTypes().length == 0) {
allMethods.add(method);
if (!method.isAccessible()) method.setAccessible(true);
}
}
c = c.getSuperclass();
} while (c != Object.class);
return allMethods.toArray(new Method[allMethods.size()]);
}
private Field[] getFields(Class clazz, Class<? extends Annotation>... requiredAnnotations) {
List<Field> allFields = new ArrayList<Field>();
Class c = clazz;
do {
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
boolean annotationsPresent = true;
for (Class<? extends Annotation> requiredAnnotation : requiredAnnotations) {
if (!field.isAnnotationPresent(requiredAnnotation)) annotationsPresent = false;
}
if (annotationsPresent) {
allFields.add(field);
if (!field.isAccessible()) field.setAccessible(true);
}
}
c = c.getSuperclass();
} while (c != Object.class);
return allFields.toArray(new Field[allFields.size()]);
}
public Map<String, SearchableEntity> getSearchableEntitiesByName() {
return searchableEntitiesByName;
}
public List<SearchableEntity> getSearchableEntities() {
return searchableEntities;
}
}
More information about the jboss-cvs-commits
mailing list