| /* |
| * 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.apache.jackrabbit.oak.jcr.query; |
| |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.NoSuchElementException; |
| |
| import javax.jcr.NodeIterator; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Value; |
| import javax.jcr.query.QueryResult; |
| import javax.jcr.query.RowIterator; |
| |
| import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter; |
| import org.apache.jackrabbit.commons.iterator.RowIteratorAdapter; |
| import org.apache.jackrabbit.oak.api.PropertyState; |
| import org.apache.jackrabbit.oak.api.PropertyValue; |
| import org.apache.jackrabbit.oak.api.Result; |
| import org.apache.jackrabbit.oak.api.ResultRow; |
| import org.apache.jackrabbit.oak.api.Tree; |
| import org.apache.jackrabbit.oak.jcr.query.PrefetchIterator.PrefetchOptions; |
| import org.apache.jackrabbit.oak.jcr.session.NodeImpl; |
| import org.apache.jackrabbit.oak.jcr.session.SessionContext; |
| import org.apache.jackrabbit.oak.jcr.delegate.NodeDelegate; |
| import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate; |
| import org.apache.jackrabbit.oak.plugins.memory.PropertyValues; |
| import org.apache.jackrabbit.oak.plugins.value.jcr.PartialValueFactory; |
| import org.jetbrains.annotations.Nullable; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * The implementation of the corresponding JCR interface. |
| */ |
| public class QueryResultImpl implements QueryResult { |
| private static final Logger queryOpsLogger = LoggerFactory.getLogger("org.apache.jackrabbit.oak.jcr.operations.query"); |
| static final Logger LOG = LoggerFactory.getLogger(QueryResultImpl.class); |
| |
| protected final SessionContext sessionContext; |
| |
| final Result result; |
| |
| private final SessionDelegate sessionDelegate; |
| |
| private final PartialValueFactory valueFactory; |
| |
| public QueryResultImpl(SessionContext sessionContext, Result result) { |
| this.sessionContext = sessionContext; |
| this.sessionDelegate = sessionContext.getSessionDelegate(); |
| this.result = result; |
| this.valueFactory = new PartialValueFactory(sessionContext, sessionContext.getBlobAccessProvider()); |
| } |
| |
| @Override |
| public String[] getColumnNames() throws RepositoryException { |
| return result.getColumnNames(); |
| } |
| |
| @Override |
| public String[] getSelectorNames() { |
| return result.getSelectorNames(); |
| } |
| |
| @Override |
| public RowIterator getRows() throws RepositoryException { |
| Iterator<RowImpl> rowIterator = new Iterator<RowImpl>() { |
| |
| private final Iterator<? extends ResultRow> it = result.getRows().iterator(); |
| private final String pathSelector; |
| private RowImpl current; |
| private int rowCount; |
| //Avoid log check for every row access |
| private final boolean debugEnabled = queryOpsLogger.isDebugEnabled(); |
| |
| { |
| String[] columnSelectorNames = result.getColumnSelectorNames(); |
| if (columnSelectorNames.length == 1) { |
| pathSelector = columnSelectorNames[0]; |
| } else { |
| pathSelector = null; |
| } |
| fetch(); |
| } |
| |
| private void fetch() { |
| if (it.hasNext()) { |
| current = new RowImpl( |
| QueryResultImpl.this, it.next(), pathSelector); |
| if (debugEnabled) { |
| rowCount++; |
| if (rowCount % 100 == 0) { |
| queryOpsLogger.debug("Iterated over [{}] results so far", rowCount); |
| } |
| } |
| } else { |
| current = null; |
| } |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return current != null; |
| } |
| |
| @Override |
| public RowImpl next() { |
| if (current == null) { |
| throw new NoSuchElementException(); |
| } |
| RowImpl r = current; |
| fetch(); |
| return r; |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| }; |
| final PrefetchIterator<RowImpl> prefIt = new PrefetchIterator<RowImpl>( |
| sessionDelegate.sync(rowIterator), |
| new PrefetchOptions() { { |
| size = result.getSize(); |
| fastSize = sessionContext.getFastQueryResultSize(); |
| fastSizeCallback = result; |
| } }); |
| return new RowIteratorAdapter(prefIt) { |
| @Override |
| public long getSize() { |
| return prefIt.size(); |
| } |
| }; |
| } |
| |
| @Nullable |
| NodeImpl<? extends NodeDelegate> getNode(Tree tree) throws RepositoryException { |
| if (tree != null && tree.exists()) { |
| NodeDelegate node = new NodeDelegate(sessionDelegate, tree); |
| return NodeImpl.createNode(node, sessionContext); |
| } |
| return null; |
| } |
| |
| @Override |
| public NodeIterator getNodes() throws RepositoryException { |
| String[] columnSelectorNames = result.getColumnSelectorNames(); |
| if (columnSelectorNames.length != 1) { |
| throw new RepositoryException("Query contains more than one selector: " + |
| Arrays.toString(columnSelectorNames)); |
| } |
| final String selectorName = columnSelectorNames[0]; |
| if (selectorName == null) { |
| throw new RepositoryException("Query does not contain a selector: " + |
| Arrays.toString(columnSelectorNames)); |
| } |
| Iterator<NodeImpl<? extends NodeDelegate>> nodeIterator = |
| new Iterator<NodeImpl<? extends NodeDelegate>>() { |
| |
| private final Iterator<? extends ResultRow> it = result.getRows().iterator(); |
| private NodeImpl<? extends NodeDelegate> current; |
| |
| { |
| fetch(); |
| } |
| |
| private void fetch() { |
| current = null; |
| while (it.hasNext()) { |
| ResultRow r = it.next(); |
| Tree tree = r.getTree(selectorName); |
| if (tree != null && tree.exists()) { |
| try { |
| current = getNode(tree); |
| break; |
| } catch (RepositoryException e) { |
| LOG.warn("Unable to fetch result node for path " |
| + tree.getPath(), e); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return current != null; |
| } |
| |
| @Override |
| public NodeImpl<? extends NodeDelegate> next() { |
| if (current == null) { |
| throw new NoSuchElementException(); |
| } |
| NodeImpl<? extends NodeDelegate> n = current; |
| fetch(); |
| return n; |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| }; |
| final PrefetchIterator<NodeImpl<? extends NodeDelegate>> prefIt = |
| new PrefetchIterator<NodeImpl<? extends NodeDelegate>>( |
| sessionDelegate.sync(nodeIterator), |
| new PrefetchOptions() { { |
| size = result.getSize(); |
| fastSize = sessionContext.getFastQueryResultSize(); |
| fastSizeCallback = result; |
| } }); |
| return new NodeIteratorAdapter(prefIt) { |
| @Override |
| public long getSize() { |
| return prefIt.size(); |
| } |
| }; |
| } |
| |
| Value createValue(PropertyValue value) { |
| if (value == null) { |
| return null; |
| } |
| PropertyState state = PropertyValues.create(value); |
| if (state == null) { |
| throw new IllegalArgumentException("Failed to convert the specified property value to a property state."); |
| } |
| return valueFactory.createValue(state); |
| } |
| |
| } |