From 015863f3f5f8df5e294a213b1af337c032be0f33 Mon Sep 17 00:00:00 2001 From: Jukka Zitting Date: Tue, 21 Jan 2014 17:40:16 -0500 Subject: [PATCH 1/2] OAK-1285: QueryEngine#executeQuery takes NamePathMapper which is not part of oak-api Replace the NamePathMapper argument with the set of local namespace mappings, which is used to construct a NamePathMapper instance for the query. Pass that instance directly as a constructor argument to QueryImpl instead of having the setter in the Query interface. Add NO_BINDINGS and NO_MAPPINGS constants to QueryEngine to make null arguments more readable. --- .../org/apache/jackrabbit/oak/api/QueryEngine.java | 39 +++++++++++++----- .../oak/plugins/identifier/IdentifierManager.java | 7 +++- .../org/apache/jackrabbit/oak/query/Query.java | 3 -- .../jackrabbit/oak/query/QueryEngineImpl.java | 48 ++++++++++++++++------ .../org/apache/jackrabbit/oak/query/QueryImpl.java | 15 +++---- .../apache/jackrabbit/oak/query/SQL2Parser.java | 3 +- .../jackrabbit/oak/query/UnionQueryImpl.java | 7 ---- .../accesscontrol/AccessControlManagerImpl.java | 5 ++- .../jackrabbit/oak/security/user/UserProvider.java | 5 ++- .../org/apache/jackrabbit/oak/api/QueryTest.java | 8 +--- .../jackrabbit/oak/query/AbstractQueryTest.java | 7 +++- .../jackrabbit/oak/jcr/query/QueryManagerImpl.java | 9 ++-- .../jackrabbit/oak/jcr/session/SessionContext.java | 6 +++ 13 files changed, 101 insertions(+), 61 deletions(-) diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/api/QueryEngine.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/api/QueryEngine.java index 62dd200..297fafe 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/api/QueryEngine.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/api/QueryEngine.java @@ -16,13 +16,13 @@ */ package org.apache.jackrabbit.oak.api; +import static java.util.Collections.emptyMap; + import java.text.ParseException; import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.jackrabbit.oak.namepath.NamePathMapper; - /** * The query engine allows to parse and execute queries. *

@@ -40,14 +40,15 @@ public interface QueryEngine { /** * Parse the query (check if it's valid) and get the list of bind variable names. * - * @param statement - * @param language - * @param namePathMapper the name and path mapper to use + * @param statement query statement + * @param language query language + * @param mappings namespace prefix mappings * @return the list of bind variable names * @throws ParseException */ - List getBindVariableNames(String statement, String language, - NamePathMapper namePathMapper) throws ParseException; + List getBindVariableNames( + String statement, String language, Map mappings) + throws ParseException; /** * Execute a query and get the result. @@ -57,13 +58,29 @@ public interface QueryEngine { * @param limit the maximum result set size (may not be negative) * @param offset the number of rows to skip (may not be negative) * @param bindings the bind variable value bindings - * @param namePathMapper the name and path mapper to use + * @param mappings namespace prefix mappings * @return the result * @throws ParseException if the statement could not be parsed * @throws IllegalArgumentException if there was an error executing the query */ - Result executeQuery(String statement, String language, - long limit, long offset, Map bindings, - NamePathMapper namePathMapper) throws ParseException; + Result executeQuery( + String statement, String language, long limit, long offset, + Map bindings, + Map mappings) throws ParseException; + + /** + * Empty set of variables bindings. Useful as an argument to + * {@link #executeQuery(String, String, long, long, Map, Map)} when + * there are no variables in a query. + */ + Map NO_BINDINGS = emptyMap(); + + /** + * Empty set of namespace prefix mappings. Useful as an argument to + * {@link #getBindVariableNames(String, String, Map)} and + * {@link #executeQuery(String, String, long, long, Map, Map)} when + * there are no local namespace mappings. + */ + Map NO_MAPPINGS = emptyMap(); } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/IdentifierManager.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/IdentifierManager.java index 506204b..e0c90b1 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/IdentifierManager.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/identifier/IdentifierManager.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.UUID; + import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.jcr.PropertyType; @@ -29,6 +30,7 @@ import javax.jcr.query.Query; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.collect.Iterators; + import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.PropertyValue; @@ -52,6 +54,7 @@ import static com.google.common.collect.Iterators.emptyIterator; import static com.google.common.collect.Iterators.filter; import static com.google.common.collect.Iterators.singletonIterator; import static com.google.common.collect.Iterators.transform; +import static org.apache.jackrabbit.oak.api.QueryEngine.NO_MAPPINGS; /** * TODO document @@ -217,7 +220,7 @@ public class IdentifierManager { try { Result result = root.getQueryEngine().executeQuery( "SELECT * FROM [nt:base] WHERE PROPERTY([" + pName + "], '" + reference + "') = $uuid", - Query.JCR_SQL2, Long.MAX_VALUE, 0, bindings, new NamePathMapper.Default()); + Query.JCR_SQL2, Long.MAX_VALUE, 0, bindings, NO_MAPPINGS); return findPaths(result, uuid, propertyName, nodeTypeNames, weak ? Type.WEAKREFERENCE : Type.REFERENCE, weak ? Type.WEAKREFERENCES : Type.REFERENCES @@ -308,7 +311,7 @@ public class IdentifierManager { Map bindings = Collections.singletonMap("id", uuid); Result result = root.getQueryEngine().executeQuery( "SELECT * FROM [nt:base] WHERE [jcr:uuid] = $id", Query.JCR_SQL2, - Long.MAX_VALUE, 0, bindings, new NamePathMapper.Default()); + Long.MAX_VALUE, 0, bindings, NO_MAPPINGS); String path = null; for (ResultRow rr : result.getRows()) { diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java index d53f4d5..0d1594e 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/Query.java @@ -19,7 +19,6 @@ import java.util.List; import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.Result; import org.apache.jackrabbit.oak.api.Tree; -import org.apache.jackrabbit.oak.namepath.NamePathMapper; import org.apache.jackrabbit.oak.query.ast.ColumnImpl; import org.apache.jackrabbit.oak.query.ast.OrderingImpl; @@ -34,8 +33,6 @@ public interface Query { void setExecutionContext(ExecutionContext context); - void setNamePathMapper(NamePathMapper namePathMapper); - void setLimit(long limit); void setOffset(long offset); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java index d8dbc94..9535a4b 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryEngineImpl.java @@ -29,7 +29,9 @@ import java.util.Set; import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.QueryEngine; import org.apache.jackrabbit.oak.api.Result; +import org.apache.jackrabbit.oak.namepath.LocalNameMapper; import org.apache.jackrabbit.oak.namepath.NamePathMapper; +import org.apache.jackrabbit.oak.namepath.NamePathMapperImpl; import org.apache.jackrabbit.oak.query.xpath.XPathToSQL2Converter; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.slf4j.Logger; @@ -75,24 +77,38 @@ public abstract class QueryEngineImpl implements QueryEngine { /** * Parse the query (check if it's valid) and get the list of bind variable names. * - * @param statement - * @param language + * @param statement query statement + * @param language query language + * @param mappings namespace prefix mappings * @return the list of bind variable names * @throws ParseException */ @Override - public List getBindVariableNames(String statement, String language, NamePathMapper namePathMapper) throws ParseException { - Query q = parseQuery(statement, language, getExecutionContext(), namePathMapper); + public List getBindVariableNames( + String statement, String language, Map mappings) + throws ParseException { + Query q = parseQuery(statement, language, getExecutionContext(), mappings); return q.getBindVariableNames(); } - private static Query parseQuery(String statement, String language, - ExecutionContext context, NamePathMapper namePathMapper) throws ParseException { + private static Query parseQuery( + String statement, String language, ExecutionContext context, + final Map mappings) throws ParseException { LOG.debug("Parsing {} statement: {}", language, statement); + + NamePathMapper mapper = new NamePathMapperImpl( + new LocalNameMapper(context.getRootTree()) { + @Override + protected Map getSessionLocalMappings() { + return mappings; + } + }); + NodeState types = context.getBaseState() .getChildNode(JCR_SYSTEM) .getChildNode(JCR_NODE_TYPES); - SQL2Parser parser = new SQL2Parser(namePathMapper, types); + + SQL2Parser parser = new SQL2Parser(mapper, types); if (language.endsWith(NO_LITERALS)) { language = language.substring(0, language.length() - NO_LITERALS.length()); parser.setAllowNumberLiterals(false); @@ -123,9 +139,10 @@ public abstract class QueryEngineImpl implements QueryEngine { } @Override - public Result executeQuery(String statement, String language, long limit, - long offset, Map bindings, - NamePathMapper namePathMapper) throws ParseException { + public Result executeQuery( + String statement, String language, long limit, long offset, + Map bindings, + Map mappings) throws ParseException { if (limit < 0) { throw new IllegalArgumentException("Limit may not be negative, is: " + limit); } @@ -133,10 +150,17 @@ public abstract class QueryEngineImpl implements QueryEngine { throw new IllegalArgumentException("Offset may not be negative, is: " + offset); } + // avoid having to deal with null arguments + if (bindings == null) { + bindings = NO_BINDINGS; + } + if (mappings == null) { + mappings = NO_MAPPINGS; + } + ExecutionContext context = getExecutionContext(); - Query q = parseQuery(statement, language, context, namePathMapper); + Query q = parseQuery(statement, language, context, mappings); q.setExecutionContext(context); - q.setNamePathMapper(namePathMapper); q.setLimit(limit); q.setOffset(offset); if (bindings != null) { diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java index e77cd89..b9a1ffb 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java @@ -111,13 +111,16 @@ public class QueryImpl implements Query { private long size = -1; private boolean prepared; private ExecutionContext context; - private NamePathMapper namePathMapper; - QueryImpl(String statement, SourceImpl source, ConstraintImpl constraint, ColumnImpl[] columns) { + private final NamePathMapper namePathMapper; + + QueryImpl(String statement, SourceImpl source, ConstraintImpl constraint, + ColumnImpl[] columns, NamePathMapper mapper) { this.statement = statement; this.source = source; this.constraint = constraint; this.columns = columns; + this.namePathMapper = mapper; } @Override @@ -632,11 +635,6 @@ public class QueryImpl implements Query { this.orderings = orderings; } - @Override - public void setNamePathMapper(NamePathMapper namePathMapper) { - this.namePathMapper = namePathMapper; - } - public NamePathMapper getNamePathMapper() { return namePathMapper; } @@ -660,9 +658,6 @@ public class QueryImpl implements Query { if (!JcrPathParser.validate(path)) { throw new IllegalArgumentException("Invalid path: " + path); } - if (namePathMapper == null) { - return path; - } String p = namePathMapper.getOakPath(path); if (p == null) { throw new IllegalArgumentException("Invalid path or namespace prefix: " + path); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java index a14e09d..8f2d899 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/SQL2Parser.java @@ -182,7 +182,8 @@ public class SQL2Parser { if (readIf("WHERE")) { constraint = parseConstraint(); } - QueryImpl q = new QueryImpl(statement, source, constraint, columnArray); + QueryImpl q = new QueryImpl( + statement, source, constraint, columnArray, namePathMapper); q.setDistinct(distinct); return q; } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java index 76f2fac..08e5545 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/query/UnionQueryImpl.java @@ -23,7 +23,6 @@ import java.util.List; import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.Result; import org.apache.jackrabbit.oak.api.Tree; -import org.apache.jackrabbit.oak.namepath.NamePathMapper; import org.apache.jackrabbit.oak.query.ast.ColumnImpl; import org.apache.jackrabbit.oak.query.ast.OrderingImpl; import org.apache.jackrabbit.oak.spi.query.PropertyValues; @@ -81,12 +80,6 @@ public class UnionQueryImpl implements Query { } @Override - public void setNamePathMapper(NamePathMapper namePathMapper) { - left.setNamePathMapper(namePathMapper); - right.setNamePathMapper(namePathMapper); - } - - @Override public void setLimit(long limit) { this.limit = limit; } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImpl.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImpl.java index ad67ef2..513c9bd 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImpl.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/accesscontrol/AccessControlManagerImpl.java @@ -51,7 +51,6 @@ import org.apache.jackrabbit.api.security.principal.PrincipalManager; import org.apache.jackrabbit.commons.iterator.AccessControlPolicyIteratorAdapter; import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils; import org.apache.jackrabbit.oak.api.PropertyState; -import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.QueryEngine; import org.apache.jackrabbit.oak.api.Result; import org.apache.jackrabbit.oak.api.ResultRow; @@ -507,7 +506,9 @@ public class AccessControlManagerImpl extends AbstractAccessControlManager imple try { QueryEngine queryEngine = root.getQueryEngine(); - return queryEngine.executeQuery(stmt.toString(), Query.XPATH, Long.MAX_VALUE, 0, Collections.emptyMap(), NamePathMapper.DEFAULT); + return queryEngine.executeQuery( + stmt.toString(), Query.XPATH, Long.MAX_VALUE, 0, + QueryEngine.NO_BINDINGS, QueryEngine.NO_MAPPINGS); } catch (ParseException e) { String msg = "Error while collecting effective policies."; log.error(msg, e.getMessage()); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java index 410e439..d1e9de2 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java @@ -20,6 +20,7 @@ import java.security.Principal; import java.text.ParseException; import java.util.Collections; import java.util.Iterator; + import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.jcr.AccessDeniedException; @@ -33,7 +34,6 @@ import org.apache.jackrabbit.oak.api.ResultRow; import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.commons.PathUtils; -import org.apache.jackrabbit.oak.namepath.NamePathMapper; import org.apache.jackrabbit.oak.spi.query.PropertyValues; import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters; import org.apache.jackrabbit.oak.spi.security.user.AuthorizableNodeName; @@ -45,6 +45,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.jackrabbit.oak.api.QueryEngine.NO_MAPPINGS; /** * User provider implementation and manager for group memberships with the @@ -211,7 +212,7 @@ class UserProvider extends AuthorizableBaseProvider { Result result = root.getQueryEngine().executeQuery(stmt.toString(), Query.JCR_SQL2, 1, 0, Collections.singletonMap("principalName", PropertyValues.newString(principal.getName())), - new NamePathMapper.Default()); + NO_MAPPINGS); Iterator rows = result.getRows().iterator(); if (rows.hasNext()) { diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/api/QueryTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/api/QueryTest.java index 376f8e0..250668c 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/api/QueryTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/api/QueryTest.java @@ -21,14 +21,12 @@ package org.apache.jackrabbit.oak.api; import static junit.framework.Assert.assertEquals; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.Set; import javax.jcr.query.Query; import org.apache.jackrabbit.oak.Oak; -import org.apache.jackrabbit.oak.namepath.NamePathMapper; import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent; import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider; import org.junit.After; @@ -69,10 +67,8 @@ public class QueryTest { r.commit(); Result result = r2.getQueryEngine().executeQuery( - "test//element(*, nt:base)", - Query.XPATH, Long.MAX_VALUE, 0, - Collections.emptyMap(), - NamePathMapper.DEFAULT); + "test//element(*, nt:base)", Query.XPATH, Long.MAX_VALUE, 0, + QueryEngine.NO_BINDINGS, QueryEngine.NO_MAPPINGS); Set paths = new HashSet(); for (ResultRow rr : result.getRows()) { paths.add(rr.getPath()); diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java index bb27361..a601ea9 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/query/AbstractQueryTest.java @@ -16,6 +16,8 @@ */ package org.apache.jackrabbit.oak.query; +import static org.apache.jackrabbit.oak.api.QueryEngine.NO_BINDINGS; +import static org.apache.jackrabbit.oak.api.QueryEngine.NO_MAPPINGS; import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME; import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE; import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME; @@ -41,6 +43,7 @@ import java.util.Map; import javax.jcr.PropertyType; import com.google.common.collect.Lists; + import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.mk.json.JsopReader; import org.apache.jackrabbit.mk.json.JsopTokenizer; @@ -108,7 +111,7 @@ public abstract class AbstractQueryTest { protected Result executeQuery(String statement, String language, Map sv) throws ParseException { - return qe.executeQuery(statement, language, Long.MAX_VALUE, 0, sv, null); + return qe.executeQuery(statement, language, Long.MAX_VALUE, 0, sv, NO_MAPPINGS); } protected void test(String file) throws Exception { @@ -227,7 +230,7 @@ public abstract class AbstractQueryTest { long time = System.currentTimeMillis(); List lines = new ArrayList(); try { - Result result = executeQuery(query, language, null); + Result result = executeQuery(query, language, NO_BINDINGS); for (ResultRow row : result.getRows()) { lines.add(readRow(row, pathsOnly)); } diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryManagerImpl.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryManagerImpl.java index 84cf274..ed9dfb4 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryManagerImpl.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/query/QueryManagerImpl.java @@ -108,7 +108,9 @@ public class QueryManagerImpl implements QueryManager { */ public List parse(String statement, String language) throws InvalidQueryException { try { - return queryEngine.getBindVariableNames(statement, language, sessionContext); + return queryEngine.getBindVariableNames( + statement, language, + sessionContext.getSessionLocalMappings()); } catch (ParseException e) { throw new InvalidQueryException(e); } @@ -118,8 +120,9 @@ public class QueryManagerImpl implements QueryManager { long limit, long offset, HashMap bindVariableMap) throws RepositoryException { try { Map bindMap = convertMap(bindVariableMap); - Result r = queryEngine.executeQuery(statement, language, limit, offset, - bindMap, sessionContext); + Result r = queryEngine.executeQuery( + statement, language, limit, offset, bindMap, + sessionContext.getSessionLocalMappings()); return new QueryResultImpl(sessionContext, r); } catch (IllegalArgumentException e) { throw new InvalidQueryException(e); diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java index 660e362..079dc6d 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionContext.java @@ -186,6 +186,11 @@ public class SessionContext implements NamePathMapper { return namespaces; } + @Nonnull + public Map getSessionLocalMappings() { + return namespaces.getSessionLocalMappings(); + } + public ValueFactory getValueFactory() { return valueFactory; } @@ -420,4 +425,5 @@ public class SessionContext implements NamePathMapper { } return permissionProvider; } + } -- 1.8.3.msysgit.0