/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat, Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.hibernate.search.engine.impl; import java.io.Serializable; import java.util.Arrays; import java.util.zip.DataFormatException; import org.apache.lucene.document.CompressionTools; import org.apache.lucene.document.Document; import org.apache.lucene.document.Fieldable; import org.apache.lucene.document.NumericField; import org.hibernate.search.SearchException; import org.hibernate.search.annotations.Store; import org.hibernate.search.bridge.FieldBridge; import org.hibernate.search.bridge.TwoWayFieldBridge; import org.hibernate.search.bridge.spi.ConversionContext; import org.hibernate.search.engine.spi.AbstractDocumentBuilder; import org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity; import org.hibernate.search.engine.spi.EntityIndexBinder; import org.hibernate.search.engine.spi.SearchFactoryImplementor; import org.hibernate.search.util.impl.ClassLoaderHelper; import org.hibernate.search.util.logging.impl.Log; import org.hibernate.search.util.logging.impl.LoggerFactory; /** * @author Hardy Ferentschik * @author Sanne Grinovero * @author Ales Justin */ public final class DocumentBuilderHelper { private static final Log log = LoggerFactory.make(); private static final Object NOT_SET = new Object(); private DocumentBuilderHelper() { } public static Class getDocumentClass(String className) { try { // Use the same class loader used to load this class ... return ClassLoaderHelper.classForName( className, DocumentBuilderHelper.class.getClassLoader() ); } catch ( ClassNotFoundException e ) { throw new SearchException( "Unable to load indexed class: " + className, e ); } } public static Serializable getDocumentId(SearchFactoryImplementor searchFactoryImplementor, Class<?> clazz, Document document, ConversionContext conversionContext) { final DocumentBuilderIndexedEntity<?> builderIndexedEntity = getDocumentBuilder( searchFactoryImplementor, clazz ); final TwoWayFieldBridge fieldBridge = builderIndexedEntity.getIdBridge(); final String fieldName = builderIndexedEntity.getIdKeywordName(); try { return (Serializable) conversionContext .setClass( clazz ) .pushIdentifierProperty() .twoWayConversionContext( fieldBridge ) .get( fieldName, document ); } finally { conversionContext.popProperty(); } } public static String getDocumentIdName(SearchFactoryImplementor searchFactoryImplementor, Class<?> clazz) { DocumentBuilderIndexedEntity<?> documentBuilder = getDocumentBuilder( searchFactoryImplementor, clazz ); return documentBuilder.getIdentifierName(); } public static Object[] getDocumentFields(SearchFactoryImplementor searchFactoryImplementor, Class<?> clazz, Document document, String[] fields, ConversionContext conversionContext) { DocumentBuilderIndexedEntity<?> builderIndexedEntity = getDocumentBuilder( searchFactoryImplementor, clazz ); final int fieldNbr = fields.length; Object[] result = new Object[fieldNbr]; Arrays.fill( result, NOT_SET ); conversionContext.setClass( clazz ); if ( builderIndexedEntity.getIdKeywordName() != null ) { final String fieldName = builderIndexedEntity.getIdKeywordName(); int matchingPosition = getFieldPosition( fields, fieldName ); if ( matchingPosition != -1 ) { conversionContext.pushProperty( fieldName ); try { populateResult( fieldName, builderIndexedEntity.getIdBridge(), Store.YES, result, document, conversionContext, matchingPosition ); } finally { conversionContext.popProperty(); } } } final AbstractDocumentBuilder.PropertiesMetadata metadata = builderIndexedEntity.getMetadata(); processFieldsForProjection( metadata, fields, result, document, conversionContext ); return result; } public static void populateResult(String fieldName, FieldBridge fieldBridge, Store store, Object[] result, Document document, ConversionContext conversionContext, int matchingPosition) { //TODO make use of an isTwoWay() method if ( store != Store.NO && TwoWayFieldBridge.class.isAssignableFrom( fieldBridge.getClass() ) ) { result[matchingPosition] = conversionContext .twoWayConversionContext( (TwoWayFieldBridge) fieldBridge ) .get( fieldName, document ); if ( log.isTraceEnabled() ) { log.tracef( "Field %s projected as %s", fieldName, result[matchingPosition] ); } } else { if ( store == Store.NO ) { throw new SearchException( "Projecting an unstored field: " + fieldName ); } else { throw new SearchException( "FieldBridge is not a TwoWayFieldBridge: " + fieldBridge.getClass() ); } } } private static void processFieldsForProjection(AbstractDocumentBuilder.PropertiesMetadata metadata, String[] fields, Object[] result, Document document, ConversionContext contextualBridge) { //process base fields final int nbrFoEntityFields = metadata.fieldNames.size(); for ( int index = 0; index < nbrFoEntityFields; index++ ) { final String fieldName = metadata.fieldNames.get( index ); int matchingPosition = getFieldPosition( fields, fieldName ); if ( matchingPosition != -1 ) { contextualBridge.pushProperty( fieldName ); try { populateResult( fieldName, metadata.fieldBridges.get( index ), metadata.fieldStore.get( index ), result, document, contextualBridge, matchingPosition ); } finally { contextualBridge.popProperty(); } } } //process fields of embedded final int nbrOfEmbeddedObjects = metadata.embeddedPropertiesMetadata.size(); for ( int index = 0; index < nbrOfEmbeddedObjects; index++ ) { //there is nothing we can do for collections if ( metadata.embeddedContainers.get( index ) == AbstractDocumentBuilder.PropertiesMetadata.Container.OBJECT ) { contextualBridge.pushProperty( metadata.embeddedFieldNames.get( index ) ); try { processFieldsForProjection( metadata.embeddedPropertiesMetadata.get( index ), fields, result, document, contextualBridge ); } finally { contextualBridge.popProperty(); } } } //process class bridges final int nbrOfClassBridges = metadata.classBridges.size(); for ( int index = 0; index < nbrOfClassBridges; index++ ) { final String fieldName = metadata.classNames.get( index ); int matchingPosition = getFieldPosition( fields, fieldName ); if ( matchingPosition != -1 ) { populateResult( fieldName, metadata.classBridges.get( index ), metadata.classStores.get( index ), result, document, contextualBridge, matchingPosition ); } } //If we still didn't know the value using any bridge, return the raw value or string: for ( int index = 0; index < result.length; index++ ) { if ( result[index] == NOT_SET ) { result[index] = null; // make sure we never return NOT_SET if ( document != null ) { Fieldable field = document.getFieldable( fields[index] ); if ( field != null ) { result[index] = extractObjectFromFieldable( field ); } } } } } public static Object extractObjectFromFieldable(Fieldable field) { if ( field instanceof NumericField ) { return NumericField.class.cast( field ).getNumericValue(); } else { return extractStringFromFieldable( field ); } } public static String extractStringFromFieldable(Fieldable field) { if ( field.isBinary() ) { try { return CompressionTools.decompressString( field.getBinaryValue() ); } catch ( DataFormatException e ) { throw log.fieldLooksBinaryButDecompressionFailed( field.name() ); } } else { return field.stringValue(); } } public static int getFieldPosition(String[] fields, String fieldName) { int fieldNbr = fields.length; for ( int index = 0; index < fieldNbr; index++ ) { if ( fieldName.equals( fields[index] ) ) { return index; } } return -1; } private static DocumentBuilderIndexedEntity<?> getDocumentBuilder(SearchFactoryImplementor searchFactoryImplementor, Class<?> clazz) { EntityIndexBinder entityIndexBinding = searchFactoryImplementor.getIndexBindingForEntity( clazz ); if ( entityIndexBinding == null ) { throw new SearchException( "No Lucene configuration set up for: " + clazz ); } return entityIndexBinding.getDocumentBuilder(); } }