/*
* 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.core.security.user;
import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
import org.apache.jackrabbit.core.NodeImpl;
import org.apache.jackrabbit.spi.Name;
import org.apache.jackrabbit.spi.Path;
import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
import org.apache.jackrabbit.util.ISO9075;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
*
*/
class IndexNodeResolver extends NodeResolver {
private static Logger log = LoggerFactory.getLogger(IndexNodeResolver.class);
private final QueryManager queryManager;
IndexNodeResolver(Session session, NamePathResolver resolver) throws RepositoryException {
super(session, resolver);
queryManager = session.getWorkspace().getQueryManager();
}
//-------------------------------------------------------< NodeResolver >---
/**
* @inheritDoc
*/
@Override
public Node findNode(Name nodeName, Name ntName) throws RepositoryException {
Query query = buildQuery(nodeName, ntName);
query.setLimit(1);
NodeIterator res = query.execute().getNodes();
if (res.hasNext()) {
return res.nextNode();
}
return null;
}
/**
* @inheritDoc
*/
@Override
public Node findNode(Name propertyName, String value, Name ntName) throws RepositoryException {
Query query = buildQuery(value, Collections.singleton(propertyName), ntName, true, 1);
NodeIterator res = query.execute().getNodes();
if (res.hasNext()) {
return res.nextNode();
}
return null;
}
/**
* Search authorizable nodes of the specified node type having the specified
* properties with the specified value.
*
* @param propertyNames
* @param value
* @param ntName NodeType the hits have to have
* @param exact if <code>true</code> match must be exact
* @return
* @throws javax.jcr.RepositoryException
*/
@Override
public NodeIterator findNodes(Set<Name> propertyNames, String value, Name ntName,
boolean exact, long maxSize) throws RepositoryException {
Query query = buildQuery(value, propertyNames, ntName, exact, maxSize);
return query.execute().getNodes();
}
@Override
public NodeIterator findNodes(Path relPath, String value, int authorizableType, boolean exact, long maxSize) throws RepositoryException {
Query query;
if (relPath.getLength() == 1) {
Set<Name> names = Collections.singleton(relPath.getName());
// search without nt-restriction in order not to limit the query to the
// authorizable nodes and filter non-matching results later.
query = buildQuery(value, names, null, exact, maxSize, getSearchRoot(authorizableType));
} else {
query = buildQuery(value, relPath, exact, maxSize, getSearchRoot(authorizableType));
}
return new ResultFilteringNodeIterator(query.execute().getNodes(), getAuthorizableTypePredicate(authorizableType, false));
}
//--------------------------------------------------------------------------
/**
*
* @param nodeName
* @param ntName
* @return
* @throws RepositoryException
*/
private Query buildQuery(Name nodeName, Name ntName) throws RepositoryException {
StringBuilder stmt = new StringBuilder("/jcr:root");
stmt.append(getSearchRoot(ntName));
stmt.append("//element(");
stmt.append(ISO9075.encode(getNamePathResolver().getJCRName(nodeName)));
stmt.append(",");
stmt.append(getNamePathResolver().getJCRName(ntName));
stmt.append(")");
return queryManager.createQuery(stmt.toString(), Query.XPATH);
}
/**
*
* @param value
* @param props
* @param ntName
* @param exact
* @param maxSize
* @return
* @throws RepositoryException
*/
private Query buildQuery(String value, Set<Name> props, Name ntName,
boolean exact, long maxSize) throws RepositoryException {
String searchRoot = getSearchRoot(ntName);
return buildQuery(value, props, ntName, exact, maxSize, searchRoot);
}
/**
*
* @param value
* @param props
* @param ntName
* @param exact
* @param maxSize
* @return
* @throws RepositoryException
*/
private Query buildQuery(String value, Set<Name> props, Name ntName,
boolean exact, long maxSize, String searchRoot) throws RepositoryException {
StringBuilder stmt = new StringBuilder("/jcr:root");
if (!"/".equals(searchRoot)) {
stmt.append(searchRoot);
}
if (ntName != null) {
stmt.append("//element(*,");
stmt.append(getNamePathResolver().getJCRName(ntName));
} else {
stmt.append("//element(*");
}
if (value == null) {
stmt.append(")");
} else {
stmt.append(")[");
int i = 0;
for (Name prop : props) {
stmt.append((exact) ? "@" : "jcr:like(@");
String pName = getNamePathResolver().getJCRName(prop);
stmt.append(ISO9075.encode(pName));
if (exact) {
stmt.append("='");
stmt.append(value.replaceAll("'", "''"));
stmt.append("'");
} else {
stmt.append(",'%");
stmt.append(escapeForQuery(value));
stmt.append("%')");
}
if (++i < props.size()) {
stmt.append(" or ");
}
}
stmt.append("]");
}
Query q = queryManager.createQuery(stmt.toString(), Query.XPATH);
q.setLimit(maxSize);
return q;
}
/**
*
* @param value
* @param relPath
* @param exact
* @param maxSize
* @return
* @throws RepositoryException
*/
private Query buildQuery(String value, Path relPath, boolean exact, long maxSize, String searchRoot)
throws RepositoryException {
StringBuilder stmt = new StringBuilder("/jcr:root");
if (!"/".equals(searchRoot)) {
stmt.append(searchRoot);
}
String p = getNamePathResolver().getJCRPath(relPath.getAncestor(1));
stmt.append("//").append(p);
if (value != null) {
stmt.append("[");
Name prop = relPath.getName();
stmt.append((exact) ? "@" : "jcr:like(@");
String pName = getNamePathResolver().getJCRName(prop);
stmt.append(ISO9075.encode(pName));
if (exact) {
stmt.append("='");
stmt.append(value.replaceAll("'", "''"));
stmt.append("'");
} else {
stmt.append(",'%");
stmt.append(escapeForQuery(value));
stmt.append("%')");
}
stmt.append("]");
}
Query q = queryManager.createQuery(stmt.toString(), Query.XPATH);
q.setLimit(maxSize);
return q;
}
private static String escapeForQuery(String value) {
StringBuilder ret = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '\\') {
ret.append("\\\\");
} else if (c == '\'') {
ret.append("''");
} else {
ret.append(c);
}
}
return ret.toString();
}
//--------------------------------------------------------------------------
/**
*
*/
private static class ResultFilteringNodeIterator extends FilteringNodeIterator {
private Set<String> authorizableIDs;
private ResultFilteringNodeIterator(NodeIterator base, AuthorizableTypePredicate filter) {
super(base, filter);
}
@Override
protected Node seekNext() {
if (authorizableIDs == null) {
authorizableIDs = new HashSet<String>();
}
Node n = null;
while (n == null && base.hasNext()) {
NodeImpl nextRes = (NodeImpl) base.nextNode();
Node authorizableNode = ((AuthorizableTypePredicate) filter).getAuthorizableNode(nextRes);
try {
if (authorizableNode != null && authorizableIDs.add(authorizableNode.getIdentifier())) {
n = authorizableNode;
}
} catch (RepositoryException e) {
log.warn(e.getMessage());
}
}
return n;
}
}
}