/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.exoplatform.services.jcr.impl.core.query.lucene; import org.apache.lucene.document.Document; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.FieldComparator; import org.apache.lucene.search.FieldComparatorSource; import org.exoplatform.services.jcr.dataflow.ItemDataConsumer; import org.exoplatform.services.jcr.datamodel.IllegalNameException; import org.exoplatform.services.jcr.datamodel.IllegalPathException; import org.exoplatform.services.jcr.datamodel.ItemData; import org.exoplatform.services.jcr.datamodel.ItemType; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.PropertyData; import org.exoplatform.services.jcr.datamodel.QPath; import org.exoplatform.services.jcr.datamodel.QPathEntry; import org.exoplatform.services.jcr.datamodel.ValueData; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.core.JCRPath; import org.exoplatform.services.jcr.impl.core.LocationFactory; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.io.IOException; import java.util.List; import javax.jcr.InvalidItemStateException; import javax.jcr.RepositoryException; /** * Created by The eXo Platform SAS * Author : eXoPlatform * exo@exoplatform.com * Feb 18, 2012 */ public class SharedFieldComparatorSource extends FieldComparatorSource { /** * The logger */ protected static Log LOG = ExoLogger.getLogger("exo.jcr.component.core.SharedFieldSortComparator"); private static final long serialVersionUID = -5803240954874585429L; /** * The name of the shared field in the lucene index. */ protected final String field; /** * The item state manager. */ protected final ItemDataConsumer ism; /** * LocationFactory. */ protected final LocationFactory locationFactory; /** * The index internal namespace mappings. */ protected final NamespaceMappings nsMappings; /** * Creates a new <code>SharedFieldSortComparator</code> for a given shared * field. * * @param fieldname the shared field. * @param ism the item state manager of this workspace. * @param nsMappings the index internal namespace mappings. */ public SharedFieldComparatorSource(String fieldname, ItemDataConsumer ism, NamespaceMappings nsMappings) { this.field = fieldname; this.ism = ism; this.locationFactory = new LocationFactory(nsMappings); this.nsMappings = nsMappings; } /** * Create a new <code>FieldComparator</code> for an embedded <code>propertyName</code> * and a <code>reader</code>. * * @param propertyName the relative path to the property to sort on as returned * by org.apache.jackrabbit.spi.Path#getString(). * @return a <code>FieldComparator</code> * @throws java.io.IOException if an error occurs */ @Override public FieldComparator<?> newComparator(String propertyName, int numHits, int sortPos, boolean reversed) throws IOException { try { QPath path = locationFactory.parseJCRPath(propertyName).getInternalPath(); SimpleFieldComparator simple = (SimpleFieldComparator)createSimpleComparator(numHits, path); if (path.getEntries().length == 1) { return simple; } else { return createCompoundComparator(numHits, path, simple); } } catch (IllegalNameException e) { throw Util.createIOException(e); } catch (RepositoryException e) { throw Util.createIOException(e); } } protected FieldComparator<?> createCompoundComparator(int numHits, QPath path, SimpleFieldComparator simple) { return new CompoundScoreFieldComparator(new FieldComparator[]{simple, new RelPathFieldComparator(path, numHits)}, numHits); } protected FieldComparator<?> createSimpleComparator(int numHits, QPath path) throws IllegalNameException { return new SimpleFieldComparator(nsMappings.translatePath(path), field, numHits); } private ItemData getItemData(NodeData parent, QPathEntry name, ItemType itemType) throws RepositoryException { if (name.getName().equals(JCRPath.PARENT_RELPATH) && name.getNamespace().equals(Constants.NS_DEFAULT_URI)) { if (parent.getIdentifier().equals(Constants.ROOT_UUID)) { return null; } else { return ism.getItemData(parent.getParentIdentifier()); } } return ism.getItemData(parent, name, itemType); } private ItemData getItemData(NodeData parent, QPath relPath, ItemType itemType) throws RepositoryException { QPathEntry[] relPathEntries = relPath.getEntries(); //relPath.getRelPath(relPath.getDepth()); ItemData item = parent; for (int i = 0; i < relPathEntries.length; i++) { if (i == relPathEntries.length - 1) { item = getItemData(parent, relPathEntries[i], itemType); } else { item = getItemData(parent, relPathEntries[i], ItemType.UNKNOWN); } if (item == null) { break; } if (item.isNode()) { parent = (NodeData)item; } else if (i < relPathEntries.length - 1) { throw new IllegalPathException("Path can not contains a property as the intermediate element"); } } return item; } static class SimpleFieldComparator extends AbstractFieldComparator { /** * The term look ups of the index segments. */ protected SharedFieldCache.ValueIndex[] indexes; /** * The name of the property */ private final String propertyName; /** * The name of the field in the index */ private final String fieldName; /** * Create a new instance of the <code>FieldComparator</code>. * * @param propertyName the name of the property * @param fieldName the name of the field in the index * @param numHits the number of values */ public SimpleFieldComparator(String propertyName, String fieldName, int numHits) { super(numHits); this.propertyName = propertyName; this.fieldName = fieldName; } @Override public void setNextReader(IndexReader reader, int docBase) throws IOException { super.setNextReader(reader, docBase); indexes = new SharedFieldCache.ValueIndex[readers.size()]; String namedValue = FieldNames.createNamedValue(propertyName, ""); for (int i = 0; i < readers.size(); i++) { IndexReader r = readers.get(i); indexes[i] = SharedFieldCache.INSTANCE.getValueIndex(r, fieldName, namedValue); } } @Override protected Comparable<?> sortValue(int doc) { int idx = readerIndex(doc); return indexes[idx].getValue(doc - starts[idx]); } } /** * Implements a compound <code>FieldComparator</code> which delegates to several * other comparators. The comparators are asked for a sort value in the * sequence they are passed to the constructor. */ static class CompoundScoreFieldComparator extends AbstractFieldComparator { private final FieldComparator<?>[] fieldComparators; /** * Create a new instance of the <code>FieldComparator</code>. * * @param fieldComparators delegates * @param numHits the number of values */ public CompoundScoreFieldComparator(FieldComparator<?>[] fieldComparators, int numHits) { super(numHits); this.fieldComparators = fieldComparators; } @Override public Comparable<?> sortValue(int doc) { for (FieldComparator<?> fieldComparator : fieldComparators) { if (fieldComparator instanceof FieldComparatorBase) { Comparable<?> c = ((FieldComparatorBase)fieldComparator).sortValue(doc); if (c != null) { return c; } } } return null; } @Override public void setNextReader(IndexReader reader, int docBase) throws IOException { for (FieldComparator<?> fieldComparator : fieldComparators) { fieldComparator.setNextReader(reader, docBase); } } } /** * A <code>FieldComparator</code> which works with order by clauses that use a * relative path to a property to sort on. */ final class RelPathFieldComparator extends AbstractFieldComparator { /** * Relative path to the property */ private final QPath relPath; /** * Create a new instance of the <code>FieldComparator</code>. * * @param relPath relative path of the property * @param numHits the number of values */ public RelPathFieldComparator(QPath relPath, int numHits) { super(numHits); this.relPath = relPath; } @Override protected Comparable<?> sortValue(int doc) { try { int idx = readerIndex(doc); IndexReader reader = readers.get(idx); Document document = reader.document(doc - starts[idx], FieldSelectors.UUID); String uuid = document.get(FieldNames.UUID); ItemData parent = ism.getItemData(uuid); if (!parent.isNode()) { throw new InvalidItemStateException(); } ItemData property = getItemData((NodeData)parent, relPath, ItemType.PROPERTY); if (property != null) { if (property.isNode()) { throw new InvalidItemStateException(); } PropertyData propertyData = (PropertyData)property; List<ValueData> values = propertyData.getValues(); if (values.size() > 0) { return Util.getComparable(values.get(0), propertyData.getType()); } } return null; } catch (RepositoryException ignore) { LOG.error(ignore.getLocalizedMessage(), ignore); } catch (CorruptIndexException ignore) { LOG.error(ignore.getLocalizedMessage(), ignore); } catch (IOException ignore) { LOG.error(ignore.getLocalizedMessage(), ignore); } catch (IllegalStateException ignore) { LOG.error(ignore.getLocalizedMessage(), ignore); } catch (IllegalNameException ignore) { LOG.error(ignore.getLocalizedMessage(), ignore); } return null; } } }