Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/ParametersParser.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/ParametersParser.java?rev=1709747&view=auto ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/ParametersParser.java (added) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/ParametersParser.java Wed Oct 21 08:23:09 2015 @@ -0,0 +1,159 @@ +/* + * 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.sling.resourceresolver.impl.params; + +import java.util.LinkedHashMap; +import java.util.Map; + +class ParametersParser { + + private enum ParamsState { + INIT, NAME, EQUALS, VALUE, QUOTED_VALUE, QUOTE_END + } + + private StringBuilder name; + + private StringBuilder value; + + private Map<String, String> parameters = new LinkedHashMap<String, String>(); + + private boolean invalid; + + /** + * Parses parameters string, eg.: {@code ;x=123;a='1.0'}. The result of the method is available in + * {@link #parameters} and {@link #invalid}. + * + * @param chars Array containing path with parameters. + * @param from Index of the first character of the parameters substring (it must be a semicolon). + * @param dotAllowed If true, the dot in parameter value won't stop parsing. + * @return Index of the first character not related to parameters. + */ + public int parseParameters(final char[] chars, final int from, final boolean dotAllowed) { + resetCurrentParameter(); + parameters.clear(); + invalid = false; + + ParamsState state = ParamsState.INIT; + for (int i = from; i <= chars.length; i++) { + final char c; + if (i == chars.length) { + c = 0; + } else { + c = chars[i]; + } + switch (state) { + case INIT: + if (c == ';') { + state = ParamsState.NAME; + } else if (c == '.' || c == '/' || c == 0) { + invalid = true; + return i; + } + break; + + case NAME: + if (c == '=') { + state = ParamsState.EQUALS; + } else if (c == '.' || c == '/' || c == 0) { + invalid = true; + return i; + } else if (c == ';') { + resetCurrentParameter(); + } else { + name.append(c); + } + break; + + case EQUALS: + if (c == '\'') { + state = ParamsState.QUOTED_VALUE; + } else if (c == '.' || c == '/' || c == 0) { + addParameter(); // empty one + return i; + } else if (c == ';') { + state = ParamsState.NAME; // empty one + addParameter(); + } else { + state = ParamsState.VALUE; + value.append(c); + } + break; + + case QUOTED_VALUE: + if (c == '\'') { + state = ParamsState.QUOTE_END; + addParameter(); + } else if (c == 0) { + invalid = true; + return i; + } else { + value.append(c); + } + break; + + case VALUE: + if (c == ';') { + state = ParamsState.NAME; + addParameter(); + } else if ((c == '.' && !dotAllowed) || c == '/' || c == 0) { + addParameter(); + return i; + } else { + value.append(c); + } + break; + + case QUOTE_END: + if (c == ';') { + state = ParamsState.NAME; + } else { + return i; + } + } + } + + return chars.length; + } + + /** + * @return Parsed parameters. + */ + public Map<String, String> getParameters() { + return parameters; + } + + /** + * @return True if the {@link #parseParameters(char[], int, boolean)} method failed. + */ + public boolean isInvalid() { + return invalid; + } + + private void resetCurrentParameter() { + name = new StringBuilder(); + value = new StringBuilder(); + } + + private void addParameter() { + parameters.put(name.toString(), value.toString()); + name = new StringBuilder(); + value = new StringBuilder(); + } +}
Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/ParsedParameters.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/ParsedParameters.java?rev=1709747&view=auto ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/ParsedParameters.java (added) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/ParsedParameters.java Wed Oct 21 08:23:09 2015 @@ -0,0 +1,75 @@ +/* + * 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 SF 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.sling.resourceresolver.impl.params; + +import java.util.Collections; +import java.util.Map; + +/** + * Parses path looking for semicolon-separated parameters. Parameters are extracted and exposed as an + * immutable map. The path without parameters is available as raw path. + * + * Parameters should be added immedietaly before or after selectors and extension: + * {@code /content/test;v='1.0'.html} or {@code /content/test.html;v=1.0}. Quotes can be used to escape the + * parameter value (it is necessary if the value contains dot and parameter is added before extension). + */ +public class ParsedParameters { + + private final Map<String, String> parameters; + + private final String parametersString; + + private final String path; + + /** + * Parse path and create parameters object. + * + * @param fullPath Path to parse. + */ + public ParsedParameters(final String fullPath) { + final PathParser parser = new PathParser(); + parser.parse(fullPath); + + parametersString = parser.getParametersString(); + parameters = Collections.unmodifiableMap(parser.getParameters()); + path = parser.getPath(); + } + + /** + * @return Path with no parameters. + */ + public String getRawPath() { + return path; + } + + /** + * @return Path's substring containing parameters + */ + public String getParametersString() { + return parametersString; + } + + /** + * @return Map of the parameters. + */ + public Map<String, String> getParameters() { + return parameters; + } + +} Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/PathParser.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/PathParser.java?rev=1709747&view=auto ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/PathParser.java (added) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/params/PathParser.java Wed Oct 21 08:23:09 2015 @@ -0,0 +1,171 @@ +/* + * 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.sling.resourceresolver.impl.params; + +import java.util.Collections; +import java.util.Map; + +class PathParser { + + /** + * List of states. V1 and V2 prefixes means variant 1 and 2. In V1, the parameters are added after + * selectors and extension: {@code /content/test.sel.html;v=1.0}. In V2 parameters are added before + * selectors and extension: {@code /content/test;v='1.0'.sel.html} + */ + private enum ParserState { + INIT, V1_EXTENSION, V1_PARAMS, V2_PARAMS, V2_EXTENSION, SUFFIX, INVALID + } + + private String rawPath; + + private String parametersString; + + private Map<String, String> parameters; + + /** + * @return Path with no parameters. + */ + public String getPath() { + return rawPath; + } + + /** + * @return Path's substring containing parameters + */ + public String getParametersString() { + return parametersString; + } + + /** + * @return Parsed parameters. + */ + public Map<String, String> getParameters() { + return parameters; + } + + /** + * Parses path containing parameters. Results will be available in {@link #rawPath} and {@link parameters}. + * + * @param path + */ + public void parse(String path) { + this.rawPath = path; + this.parameters = Collections.emptyMap(); + + if (path == null) { + return; + } + + // indexOf shortcut for the most common case + final int di = path.indexOf('.'); + final int si = path.indexOf(';'); + if (di == -1 && si == -1) { + return; + } + + final char[] chars = path.toCharArray(); + final ParametersParser parametersParser = new ParametersParser(); + + ParserState state = ParserState.INIT; + int paramsStart = -1, paramsEnd = -1; + + int i = (di != -1) ? ((si != -1) ? Math.min(di, si) : di) : si; + for (; i <= chars.length; i++) { + final char c; + if (i == chars.length) { + c = 0; + } else { + c = chars[i]; + } + + switch (state) { + case INIT: + if (c == '.') { + state = ParserState.V1_EXTENSION; + } else if (c == ';') { + paramsStart = i; + i = parametersParser.parseParameters(chars, i, false); + paramsEnd = i--; + state = parametersParser.isInvalid() ? ParserState.INVALID : ParserState.V2_PARAMS; + } + break; + + case V1_EXTENSION: + if (c == '/') { + state = ParserState.SUFFIX; + } else if (c == ';') { + paramsStart = i; + i = parametersParser.parseParameters(chars, i, true); + paramsEnd = i--; + state = parametersParser.isInvalid() ? ParserState.INVALID : ParserState.V1_PARAMS; + } + break; + + case V1_PARAMS: + if (c == '/') { + state = ParserState.SUFFIX; + } else if (c == '.') { + state = ParserState.INVALID; // no dots after params + } + break; + + case V2_PARAMS: + if (c == '/') { + state = ParserState.INVALID; // there was no extension, so no suffix is allowed + } else if (c == '.') { + state = ParserState.V2_EXTENSION; + } + break; + + case V2_EXTENSION: + if (c == '/') { + state = ParserState.SUFFIX; + } + break; + + case SUFFIX: + case INVALID: + break; + } + } + + if (state == ParserState.INVALID) { + paramsStart = paramsEnd = -1; + } else { + cutPath(path, paramsStart, paramsEnd); + parameters = parametersParser.getParameters(); + } + } + + private void cutPath(String path, int from, int to) { + if (from == -1) { + rawPath = path; + parametersString = null; + } else if (to == -1) { + rawPath = path.substring(0, from); + parametersString = path.substring(from); + } else { + rawPath = path.substring(0, from) + path.substring(to); + parametersString = path.substring(from, to); + } + } + + +} Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderHandler.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderHandler.java?rev=1709747&r1=1709746&r2=1709747&view=diff ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderHandler.java (original) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderHandler.java Wed Oct 21 08:23:09 2015 @@ -18,20 +18,32 @@ */ package org.apache.sling.resourceresolver.impl.providers; +import java.util.Dictionary; +import java.util.Hashtable; + +import org.apache.sling.api.SlingConstants; +import org.apache.sling.resourceresolver.impl.legacy.LegacyResourceProviderWhiteboard; +import org.apache.sling.resourceresolver.impl.providers.tree.Pathable; import org.apache.sling.spi.resource.provider.ResourceProvider; import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventAdmin; -public class ResourceProviderHandler implements Comparable<ResourceProviderHandler> { +public class ResourceProviderHandler implements Comparable<ResourceProviderHandler>, Pathable { private final ResourceProviderInfo info; private final BundleContext bundleContext; + private final EventAdmin eventAdmin; + private volatile ResourceProvider<?> provider; - public ResourceProviderHandler(final BundleContext bc, final ResourceProviderInfo info) { + public ResourceProviderHandler(final BundleContext bc, final ResourceProviderInfo info, final EventAdmin eventAdmin) { this.info = info; this.bundleContext = bc; + this.eventAdmin = eventAdmin; } public ResourceProviderInfo getInfo() { @@ -46,20 +58,40 @@ public class ResourceProviderHandler imp this.provider = (ResourceProvider<?>) this.bundleContext.getService(this.info.getServiceReference()); } rp = this.provider; + postEvent(SlingConstants.TOPIC_RESOURCE_PROVIDER_ADDED); } } return rp; } + private void postEvent(String topic) { + final Dictionary<String, Object> eventProps = new Hashtable<String, Object>(); + eventProps.put(SlingConstants.PROPERTY_PATH, info.getPath()); + String pid = (String) info.getServiceReference().getProperty(Constants.SERVICE_PID); + if (pid == null) { + pid = (String) info.getServiceReference().getProperty(LegacyResourceProviderWhiteboard.ORIGINAL_SERVICE_PID); + } + if (pid != null) { + eventProps.put(Constants.SERVICE_PID, pid); + } + eventAdmin.postEvent(new Event(topic, eventProps)); + } + public void deactivate() { if ( this.provider != null ) { this.provider = null; this.bundleContext.ungetService(this.info.getServiceReference()); + postEvent(SlingConstants.TOPIC_RESOURCE_PROVIDER_REMOVED); } } @Override public int compareTo(final ResourceProviderHandler o) { - return this.info.compareTo(o.info); + return this.getInfo().compareTo(o.getInfo()); + } + + @Override + public String getPath() { + return this.getInfo().getPath(); } } Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderInfo.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderInfo.java?rev=1709747&r1=1709746&r2=1709747&view=diff ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderInfo.java (original) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderInfo.java Wed Oct 21 08:23:09 2015 @@ -22,12 +22,16 @@ import org.apache.sling.api.resource.run import org.apache.sling.commons.osgi.PropertiesUtil; import org.apache.sling.spi.resource.provider.ResourceProvider; import org.osgi.framework.ServiceReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Information about a registered resource provider */ public class ResourceProviderInfo implements Comparable<ResourceProviderInfo> { + private static final Logger logger = LoggerFactory.getLogger(ResourceProviderInfo.class); + private final ServiceReference ref; private final String path; @@ -40,6 +44,12 @@ public class ResourceProviderInfo implem private final boolean modifiable; + private final boolean adaptable; + + private final boolean refreshable; + + private final boolean attributable; + public ResourceProviderInfo(final ServiceReference ref) { this.ref = ref; this.path = PropertiesUtil.toString(ref.getProperty(ResourceProvider.PROPERTY_ROOT), ""); @@ -50,10 +60,13 @@ public class ResourceProviderInfo implem try { aType = AuthType.valueOf(authType); } catch ( final IllegalArgumentException iae) { - // ignore + logger.error("Illegal auth type {} for resource provider {}", authType, name); } this.authType = aType; this.modifiable = PropertiesUtil.toBoolean(ref.getProperty(ResourceProvider.PROPERTY_MODIFIABLE), false); + this.adaptable = PropertiesUtil.toBoolean(ref.getProperty(ResourceProvider.PROPERTY_ADAPTABLE), false); + this.refreshable = PropertiesUtil.toBoolean(ref.getProperty(ResourceProvider.PROPERTY_REFRESHABLE), false); + this.attributable = PropertiesUtil.toBoolean(ref.getProperty(ResourceProvider.PROPERTY_ATTRIBUTABLE), false); } public boolean isValid() { @@ -98,6 +111,18 @@ public class ResourceProviderInfo implem return this.modifiable; } + public boolean isAdaptable() { + return adaptable; + } + + public boolean isRefreshable() { + return refreshable; + } + + public boolean isAttributable() { + return attributable; + } + public String getName() { return this.name; } Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderStorage.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderStorage.java?rev=1709747&view=auto ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderStorage.java (added) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderStorage.java Wed Oct 21 08:23:09 2015 @@ -0,0 +1,108 @@ +/* + * 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.sling.resourceresolver.impl.providers; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.sling.api.resource.runtime.dto.AuthType; +import org.apache.sling.resourceresolver.impl.providers.tree.PathTree; + +public class ResourceProviderStorage { + + private final List<ResourceProviderHandler> allHandlers; + + private final List<ResourceProviderHandler> authRequiredHandlers; + + private final List<ResourceProviderHandler> adaptableHandlers; + + private final List<ResourceProviderHandler> attributableHandlers; + + private final List<ResourceProviderHandler> refreshableHandlers; + + private final List<ResourceProviderHandler> jcrQuerableHandlers; + + private final List<ResourceProviderHandler> nativeQuerableHandlers; + + private final PathTree<ResourceProviderHandler> handlersTree; + + public ResourceProviderStorage(List<ResourceProviderHandler> handlers) { + this.allHandlers = handlers; + this.authRequiredHandlers = new ArrayList<ResourceProviderHandler>(); + this.adaptableHandlers = new ArrayList<ResourceProviderHandler>(); + this.attributableHandlers = new ArrayList<ResourceProviderHandler>(); + this.refreshableHandlers = new ArrayList<ResourceProviderHandler>(); + this.jcrQuerableHandlers = new ArrayList<ResourceProviderHandler>(); + this.nativeQuerableHandlers = new ArrayList<ResourceProviderHandler>(); + for (ResourceProviderHandler h : allHandlers) { + ResourceProviderInfo info = h.getInfo(); + if (info.getAuthType() == AuthType.required) { + this.authRequiredHandlers.add(h); + } + if (info.isAdaptable()) { + this.adaptableHandlers.add(h); + } + if (info.isAttributable()) { + this.attributableHandlers.add(h); + } + if (info.isRefreshable()) { + this.refreshableHandlers.add(h); + } + if (h.getResourceProvider().getJCRQueryProvider() != null) { + this.jcrQuerableHandlers.add(h); + } + if (h.getResourceProvider().getQueryProvider() != null) { + this.nativeQuerableHandlers.add(h); + } + } + this.handlersTree = new PathTree<ResourceProviderHandler>(handlers); + } + + public List<ResourceProviderHandler> getAllHandlers() { + return allHandlers; + } + + public List<ResourceProviderHandler> getAuthRequiredHandlers() { + return authRequiredHandlers; + } + + public List<ResourceProviderHandler> getAdaptableHandlers() { + return adaptableHandlers; + } + + public List<ResourceProviderHandler> getAttributableHandlers() { + return attributableHandlers; + } + + public List<ResourceProviderHandler> getRefreshableHandlers() { + return refreshableHandlers; + } + + public List<ResourceProviderHandler> getJcrQuerableHandlers() { + return jcrQuerableHandlers; + } + + public List<ResourceProviderHandler> getNativeQuerableHandlers() { + return nativeQuerableHandlers; + } + + public PathTree<ResourceProviderHandler> getTree() { + return handlersTree; + } +} Modified: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTracker.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTracker.java?rev=1709747&r1=1709746&r2=1709747&view=diff ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTracker.java (original) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/ResourceProviderTracker.java Wed Oct 21 08:23:09 2015 @@ -21,6 +21,7 @@ package org.apache.sling.resourceresolve import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -28,6 +29,7 @@ import java.util.concurrent.ConcurrentHa import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.sling.api.resource.runtime.dto.FailureReason; import org.apache.sling.api.resource.runtime.dto.ResourceProviderDTO; @@ -37,6 +39,7 @@ import org.apache.sling.spi.resource.pro import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; +import org.osgi.service.event.EventAdmin; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; @@ -58,6 +61,11 @@ public class ResourceProviderTracker { private final Map<ResourceProviderInfo, FailureReason> invalidProviders = new HashMap<ResourceProviderInfo, FailureReason>(); + @Reference + private EventAdmin eventAdmin; + + private volatile ResourceProviderStorage storage; + @Activate protected void activate(final BundleContext bundleContext) { this.bundleContext = bundleContext; @@ -88,6 +96,7 @@ public class ResourceProviderTracker { return reference; } }); + this.tracker.open(); } @Deactivate @@ -97,33 +106,38 @@ public class ResourceProviderTracker { this.tracker = null; } this.infos.clear(); + this.handlers.clear(); + this.invalidProviders.clear(); } private void register(final ResourceProviderInfo info) { if ( info.isValid() ) { logger.debug("Registering new resource provider {}", info); synchronized ( this.handlers ) { - List<ResourceProviderHandler> infos = this.handlers.get(info.getPath()); - if ( infos == null ) { - infos = new ArrayList<ResourceProviderHandler>(); - this.handlers.put(info.getPath(), infos); + List<ResourceProviderHandler> matchingHandlers = this.handlers.get(info.getPath()); + if ( matchingHandlers == null ) { + matchingHandlers = new ArrayList<ResourceProviderHandler>(); + this.handlers.put(info.getPath(), matchingHandlers); } - final ResourceProviderHandler handler = new ResourceProviderHandler(bundleContext, info); - infos.add(handler); - Collections.sort(infos); - if ( infos.get(0) == handler ) { + final ResourceProviderHandler handler = new ResourceProviderHandler(bundleContext, info, eventAdmin); + matchingHandlers.add(handler); + Collections.sort(matchingHandlers); + if ( matchingHandlers.get(0) == handler ) { if ( !this.activate(handler) ) { - infos.remove(handler); - if ( infos.isEmpty() ) { + matchingHandlers.remove(handler); + if ( matchingHandlers.isEmpty() ) { this.handlers.remove(info.getPath()); } } else { - if ( infos.size() > 1 ) { - this.deactivate(infos.get(1)); + if ( matchingHandlers.size() > 1 ) { + this.deactivate(matchingHandlers.get(1)); } } } } + synchronized(this) { + storage = null; + } } else { logger.debug("Ignoring invalid resource provider {}", info); synchronized ( this.invalidProviders ) { @@ -135,30 +149,31 @@ public class ResourceProviderTracker { private void unregister(final ResourceProviderInfo info) { if ( info.isValid() ) { logger.debug("Unregistering resource provider {}", info); - final List<ResourceProviderHandler> infos = this.handlers.get(info.getPath()); - if ( infos != null ) { - boolean activate = false; - if ( infos.get(0).getInfo() == info ) { - activate = true; - this.deactivate(infos.get(0)); - } - if ( infos.remove(info) ) { - if ( infos.isEmpty() ) { - this.handlers.remove(info.getPath()); - } else { - while ( activate ) { - if ( !this.activate(infos.get(0)) ) { - infos.remove(0); - activate = !this.handlers.isEmpty(); - if ( !activate ) { - this.handlers.remove(info.getPath()); - } + synchronized (this.handlers) { + final List<ResourceProviderHandler> matchingHandlers = this.handlers.get(info.getPath()); + if ( matchingHandlers != null ) { + boolean doActivateNext = false; + if ( matchingHandlers.get(0).getInfo() == info ) { + doActivateNext = true; + this.deactivate(matchingHandlers.get(0)); + } + if (removeHandlerByInfo(info, matchingHandlers)) { + while (doActivateNext && !matchingHandlers.isEmpty()) { + if (this.activate(matchingHandlers.get(0))) { + doActivateNext = false; + } else { + matchingHandlers.remove(0); } } } + if (matchingHandlers.isEmpty()) { + this.handlers.remove(info.getPath()); + } } } - + synchronized(this) { + storage = null; + } } else { logger.debug("Unregistering invalid resource provider {}", info); synchronized ( this.invalidProviders ) { @@ -167,6 +182,19 @@ public class ResourceProviderTracker { } } + private boolean removeHandlerByInfo(final ResourceProviderInfo info, final List<ResourceProviderHandler> infos) { + Iterator<ResourceProviderHandler> it = infos.iterator(); + boolean removed = false; + while (it.hasNext()) { + if (it.next().getInfo() == info) { + it.remove(); + removed = true; + break; + } + } + return removed; + } + private void deactivate(final ResourceProviderHandler handler) { handler.deactivate(); logger.debug("Deactivated resource provider {}", handler.getInfo()); @@ -217,6 +245,32 @@ public class ResourceProviderTracker { dto.failedProviders = failures.toArray(new ResourceProviderFailureDTO[failures.size()]); } + private List<ResourceProviderHandler> getHandlers() { + List<ResourceProviderHandler> result = new ArrayList<ResourceProviderHandler>(); + synchronized (this.handlers) { + for (List<ResourceProviderHandler> list : handlers.values()) { + ResourceProviderHandler h = list.get(0); + if (h != null) { + result.add(h); + } + } + } + return result; + } + + public ResourceProviderStorage getResourceProviderStorage() { + ResourceProviderStorage result = storage; + if (result == null) { + synchronized(this) { + if (storage == null) { + storage = new ResourceProviderStorage(getHandlers()); + } + result = storage; + } + } + return result; + } + private void fill(final ResourceProviderDTO d, final ResourceProviderInfo info) { d.authType = info.getAuthType(); d.modifiable = info.getModifiable(); Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AbstractIterator.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AbstractIterator.java?rev=1709747&view=auto ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AbstractIterator.java (added) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AbstractIterator.java Wed Oct 21 08:23:09 2015 @@ -0,0 +1,53 @@ +/* + * 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.sling.resourceresolver.impl.providers.stateful; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +public abstract class AbstractIterator<T> implements Iterator<T> { + + private T nextElement; + + protected abstract T seek(); + + @Override + public boolean hasNext() { + if (nextElement == null) { + nextElement = seek(); + } + return nextElement != null; + } + + @Override + public T next() { + if (nextElement == null && !hasNext()) { + throw new NoSuchElementException(); + } + final T result = nextElement; + nextElement = null; + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + +} Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AuthenticatedResourceProvider.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AuthenticatedResourceProvider.java?rev=1709747&view=auto ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AuthenticatedResourceProvider.java (added) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/AuthenticatedResourceProvider.java Wed Oct 21 08:23:09 2015 @@ -0,0 +1,384 @@ +/* + * 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.sling.resourceresolver.impl.providers.stateful; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.api.resource.query.Query; +import org.apache.sling.api.resource.query.QueryInstructions; +import org.apache.sling.api.resource.runtime.dto.AuthType; +import org.apache.sling.resourceresolver.impl.BasicResolveContext; +import org.apache.sling.resourceresolver.impl.providers.ResourceProviderInfo; +import org.apache.sling.spi.resource.provider.JCRQueryProvider; +import org.apache.sling.spi.resource.provider.QueryProvider; +import org.apache.sling.spi.resource.provider.QueryResult; +import org.apache.sling.spi.resource.provider.ResolveContext; +import org.apache.sling.spi.resource.provider.ResourceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This {@link StatefulResourceProvider} implementation authenticates the + * underlying {@link ResourceProvider}. The authentication can be done during + * creation of the object (for {@link AuthType#required}) or before invoking the + * first method (for {@link AuthType#lazy}). + */ +public class AuthenticatedResourceProvider implements StatefulResourceProvider { + + private static final Logger logger = LoggerFactory.getLogger(AuthenticatedResourceProvider.class); + + private static final String FORBIDDEN_ATTRIBUTE = ResourceResolverFactory.PASSWORD; + + private final ResourceProvider<Object> rp; + + private final ResourceProviderInfo info; + + private final Map<String, Object> authInfo; + + private final ResourceResolver resolver; + + private boolean authenticated; + + private Object contextData; + + private ResolveContext<Object> cachedContext; + + private QueryProvider<Object> cachedQueryProvider; + + private JCRQueryProvider<Object> cachedJcrQueryProvider; + + @SuppressWarnings("unchecked") + public AuthenticatedResourceProvider(ResourceProvider<?> rp, ResourceProviderInfo info, ResourceResolver resolver, + Map<String, Object> authInfo) throws LoginException { + this.rp = (ResourceProvider<Object>) rp; + this.info = info; + this.authInfo = authInfo; + this.resolver = resolver; + if (info.getAuthType() == AuthType.required) { + authenticate(); + } + } + + private Object authenticate() throws LoginException { + if (!authenticated && (info.getAuthType() == AuthType.required || info.getAuthType() == AuthType.lazy)) { + contextData = rp.authenticate(authInfo); + authenticated = true; + } + return contextData; + } + + private ResolveContext<Object> getBasicContext() throws LoginException { + if (cachedContext != null) { + return cachedContext; + } + return cachedContext = getContext(null, null); + } + + @Override + public ResolveContext<Object> getContext(Map<String, String> parameters) throws LoginException { + if (parameters == null || parameters.isEmpty()) { + return getBasicContext(); + } else { + return getContext(parameters, null); + } + } + + private ResolveContext<Object> getContext(Map<String, String> parameters, List<StatefulResourceProvider> parentProviders) throws LoginException { + ResourceProvider<Object> parentProvider = null; + ResolveContext<Object> parentContext = null; + try { + if (parentProviders != null && !parentProviders.isEmpty()) { + StatefulResourceProvider statefulParentProvider = parentProviders.get(0); + parentProvider = statefulParentProvider.getResourceProvider(); + parentContext = statefulParentProvider.getContext(parameters); + } + } catch (LoginException e) { + logger.warn("Can't authenticate the parent resource provider", e); + } + return new BasicResolveContext<Object>(resolver, parameters, authenticate(), parentProvider, parentContext); + } + + @Override + public void logout() { + if (authenticated) { + try { + rp.logout(getBasicContext().getProviderState()); + } catch (LoginException e) { + logger.error("Can't create context", e); + } + authenticated = false; + cachedContext = null; + } + } + + @Override + public void refresh() { + try { + rp.refresh(getBasicContext()); + } catch (LoginException e) { + logger.error("Can't create context", e); + } + } + + @Override + public boolean isLive() { + try { + return rp.isLive(getBasicContext()); + } catch (LoginException e) { + logger.error("Can't create context", e); + return false; + } + } + + @Override + public Resource getParent(Resource child, List<StatefulResourceProvider> parentProviders) { + try { + return rp.getParent(getContext(child.getResourceMetadata().getParameterMap(), parentProviders), child); + } catch (LoginException e) { + logger.error("Can't create context", e); + return null; + } + } + + @Override + public Resource getResource(String path, Resource parent, Map<String, String> parameters, boolean isResolve, List<StatefulResourceProvider> parentProviders) { + try { + return rp.getResource(getContext(parameters, parentProviders), path, parent); + } catch (LoginException e) { + logger.error("Can't create context", e); + return null; + } + + } + + @Override + public Iterator<Resource> listChildren(Resource parent, List<StatefulResourceProvider> parentProviders) { + try { + return rp.listChildren(getContext(parent.getResourceMetadata().getParameterMap(), parentProviders), parent); + } catch (LoginException e) { + logger.error("Can't create context", e); + return null; + } + } + + @Override + public Collection<String> getAttributeNames() { + Set<String> attributeNames = new LinkedHashSet<String>(); + Collection<String> rpAttributeNames = null; + try { + rpAttributeNames = rp.getAttributeNames(getBasicContext()); + } catch (LoginException e) { + logger.error("Can't create context", e); + } + if (rpAttributeNames != null) { + attributeNames.addAll(rpAttributeNames); + } + if (authInfo != null) { + attributeNames.addAll(authInfo.keySet()); + } + attributeNames.remove(FORBIDDEN_ATTRIBUTE); + return attributeNames; + } + + @Override + public Object getAttribute(String name) { + if (FORBIDDEN_ATTRIBUTE.equals(name)) { + return null; + } + Object attribute = null; + try { + attribute = rp.getAttribute(getBasicContext(), name); + } catch (LoginException e) { + logger.error("Can't create context", e); + } + if (attribute == null) { + attribute = authInfo.get(name); + } + return attribute; + } + + @Override + public Resource create(String path, Map<String, Object> properties, List<StatefulResourceProvider> parentProviders) throws PersistenceException { + try { + return rp.create(getContext(null, parentProviders), path, properties); + } catch (LoginException e) { + logger.error("Can't create context", e); + return null; + } + } + + @Override + public void delete(Resource resource, List<StatefulResourceProvider> parentProviders) throws PersistenceException { + try { + rp.delete(getContext(resource.getResourceMetadata().getParameterMap(), parentProviders), resource); + } catch (LoginException e) { + logger.error("Can't create context", e); + } + } + + @Override + public void revert() { + try { + rp.revert(getBasicContext()); + } catch (LoginException e) { + logger.error("Can't create context", e); + } + } + + @Override + public void commit() throws PersistenceException { + try { + rp.commit(getBasicContext()); + } catch (LoginException e) { + logger.error("Can't create context", e); + } + } + + @Override + public boolean hasChanges() { + try { + return rp.hasChanges(getBasicContext()); + } catch (LoginException e) { + logger.error("Can't create context", e); + return false; + } + } + + private QueryProvider<Object> getQueryProvider() { + if (cachedQueryProvider == null) { + cachedQueryProvider = rp.getQueryProvider(); + } + return cachedQueryProvider; + } + + private JCRQueryProvider<Object> getJcrQueryProvider() { + if (cachedJcrQueryProvider == null) { + cachedJcrQueryProvider = rp.getJCRQueryProvider(); + } + return cachedJcrQueryProvider; + } + + @Override + public QueryResult find(Query q, QueryInstructions qi) { + final QueryProvider<Object> provider = getQueryProvider(); + if (provider == null) { + return null; + } + try { + return provider.find(getBasicContext(), q, qi); + } catch (LoginException e) { + logger.error("Can't create context", e); + return null; + } + } + + @Override + public String[] getSupportedLanguages() { + final JCRQueryProvider<Object> jcrQueryProvider = getJcrQueryProvider(); + if (jcrQueryProvider == null) { + return null; + } + try { + return jcrQueryProvider.getSupportedLanguages(getBasicContext()); + } catch (LoginException e) { + logger.error("Can't create context", e); + return ArrayUtils.EMPTY_STRING_ARRAY; + } + } + + @Override + public Iterator<Resource> findResources(String query, String language) { + final JCRQueryProvider<Object> jcrQueryProvider = getJcrQueryProvider(); + if (jcrQueryProvider == null) { + return null; + } + try { + return jcrQueryProvider.findResources(getBasicContext(), query, language); + } catch (LoginException e) { + logger.error("Can't create context", e); + return null; + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Iterator<Map<String, Object>> queryResources(String query, String language) { + final JCRQueryProvider<Object> jcrQueryProvider = getJcrQueryProvider(); + if (jcrQueryProvider == null) { + return null; + } + try { + return (Iterator) jcrQueryProvider.queryResources(getBasicContext(), query, language); + } catch (LoginException e) { + logger.error("Can't create context", e); + return null; + } + } + + @Override + public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) { + try { + return rp.adaptTo(getBasicContext(), type); + } catch (LoginException e) { + logger.error("Can't create context", e); + return null; + } + } + + @Override + public boolean copy(String srcAbsPath, String destAbsPath, List<StatefulResourceProvider> parentProviders) throws PersistenceException { + try { + return rp.copy(getContext(null, parentProviders), srcAbsPath, destAbsPath); + } catch (LoginException e) { + logger.error("Can't create context", e); + return false; + } + } + + @Override + public boolean move(String srcAbsPath, String destAbsPath, List<StatefulResourceProvider> parentProviders) throws PersistenceException { + try { + return rp.move(getContext(null, parentProviders), srcAbsPath, destAbsPath); + } catch (LoginException e) { + logger.error("Can't create context", e); + return false; + } + } + + @Override + public ResourceResolver getResourceResolver() { + return resolver; + } + + @Override + public ResourceProvider<Object> getResourceProvider() { + return rp; + } +} Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/CombinedResourceProvider.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/CombinedResourceProvider.java?rev=1709747&view=auto ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/CombinedResourceProvider.java (added) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/CombinedResourceProvider.java Wed Oct 21 08:23:09 2015 @@ -0,0 +1,587 @@ +/* + * 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.sling.resourceresolver.impl.providers.stateful; + +import static org.apache.commons.collections.IteratorUtils.chainedIterator; +import static org.apache.commons.collections.IteratorUtils.transformedIterator; +import static org.apache.sling.api.resource.ResourceUtil.getName; +import static org.apache.sling.spi.resource.provider.ResourceProvider.RESOURCE_TYPE_SYNTHETIC; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.commons.collections.IteratorUtils; +import org.apache.commons.collections.ListUtils; +import org.apache.commons.collections.Transformer; +import org.apache.commons.lang.ArrayUtils; +import org.apache.sling.api.SlingException; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.SyntheticResource; +import org.apache.sling.api.resource.query.Query; +import org.apache.sling.api.resource.query.QueryInstructions; +import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler; +import org.apache.sling.resourceresolver.impl.providers.ResourceProviderInfo; +import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage; +import org.apache.sling.resourceresolver.impl.providers.tree.Node; +import org.apache.sling.spi.resource.provider.QueryResult; +import org.apache.sling.spi.resource.provider.ResourceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class takes a number of {@link StatefulResourceProvider} objects and + * exposes it as one such object. Provider appropriate for the given operation + * is chosen basing on its {@link ResourceProviderInfo#getPath()} (more specific + * first) and service ranking. + */ +public class CombinedResourceProvider { + + private static final Logger logger = LoggerFactory.getLogger(CombinedResourceProvider.class); + + private static final StatefulResourceProvider EMPTY_PROVIDER = new EmptyResourceProvider(); + + private final ResourceProviderStorage storage; + + private final ResourceResolver resolver; + + private final ResourceProviderAuthenticator authenticator; + + public CombinedResourceProvider(ResourceProviderStorage storage, ResourceResolver resolver, ResourceProviderAuthenticator authenticator) { + this.storage = storage; + this.resolver = resolver; + this.authenticator = authenticator; + } + + /** + * Logouts from all providers. + */ + public void logout() { + for (StatefulResourceProvider p : authenticator.getAllUsedAuthenticated()) { + p.logout(); + } + } + + /** + * Refreshes all providers. + */ + public void refresh() { + for (StatefulResourceProvider p : authenticator.getAll(storage.getRefreshableHandlers())) { + p.refresh(); + } + } + + /** + * Returns {@code true} if all providers are live. + */ + public boolean isLive() { + for (StatefulResourceProvider p : authenticator.getAllUsedAuthenticated()) { + if (!p.isLive()) { + return false; + } + } + return true; + } + + /** + * Returns parent from the most appropriate resource provider accepting the + * given children. + * + * In some cases the {@link SyntheticResource} can be returned if no + * resource provider returns parent for this child. See + * {@link #getResource(String, Resource, Map, boolean)} for more details + */ + public Resource getParent(Resource child) { + String path = child.getPath(); + List<StatefulResourceProvider> matching = getMatchingProviders(path); + Resource parentCandidate = head(matching).getParent(child, tail(matching)); + if (parentCandidate != null) { + return parentCandidate; + } + String parentPath = ResourceUtil.getParent(path); + if (parentPath != null && isIntermediatePath(parentPath)) { + return new SyntheticResource(resolver, parentPath, ResourceProvider.RESOURCE_TYPE_SYNTHETIC); + } + return null; + } + + /** + * Returns resource from the most appropriate resource provider. + * <br/><br/> + * If there's no such provider and the path is a part of some resource + * provider path, then the {@link SyntheticResource} will be returned. For + * instance, if we have resource provider under + * {@code /libs/sling/servlet/default/GET.servlet} and no resource provider + * returns a resource for {@code /libs/sling/servlet/default}, then the + * {@link SyntheticResource} will be returned to provide a consistent + * resource tree. + * <br/><br/> + * The same behaviour occurs in {@link #getParent(Resource)} and + * {@link #listChildren(Resource)}. + */ + public Resource getResource(String path, Resource parent, Map<String, String> parameters, boolean isResolve) { + if (path == null || path.length() == 0 || path.charAt(0) != '/') { + logger.debug("Not absolute {}", path); + return null; // path must be absolute + } + + try { + List<StatefulResourceProvider> matching = getMatchingProviders(path); + Resource resourceCandidate = head(matching).getResource(path, parent, parameters, isResolve, tail(matching)); + if (resourceCandidate != null) { + return resourceCandidate; + } + + // query: /libs/sling/servlet/default + // resource Provider: libs/sling/servlet/default/GET.servlet + // list will match libs, sling, servlet, default + // and there will be no resource provider at the end + // SLING-3482 : this is only done for getResource but not resolve + // as it is important e.g. for servlet resolution + // to get the parent resource for resource traversal. + if (!isResolve && isIntermediatePath(path)) { + logger.debug("Resolved Synthetic {}", path); + return new SyntheticResource(resolver, path, ResourceProvider.RESOURCE_TYPE_SYNTHETIC); + } + } catch (SlingException e) { + throw e; + } catch (Exception e) { + logger.warn("Unexpected exception while trying to get resource for " + path, e); + } + logger.debug("Resource null {} ", path); + return null; + } + + private boolean isIntermediatePath(final String fullPath) { + return storage.getTree().getNode(fullPath) != null; + } + + /** + * This method asks all matching resource providers for the children iterators, + * merges them, adds {@link SyntheticResource}s (see + * {@link #getResource(String, Resource, Map, boolean)} for more details), + * filters out the duplicates and returns the resulting iterator. All + * transformations are done lazily, during the {@link Iterator#hasNext()} + * invocation on the result. + */ + @SuppressWarnings("unchecked") + public Iterator<Resource> listChildren(final Resource parent) { + List<StatefulResourceProvider> matching = getMatchingProviders(parent.getPath()); + Iterator<Resource> realChildren = head(matching).listChildren(parent, tail(matching)); + Iterator<Resource> syntheticChildren = getSyntheticChildren(parent).iterator(); + Iterator<Resource> allChildren; + if (realChildren == null) { + allChildren = syntheticChildren; + } else { + allChildren = new UniqueIterator(chainedIterator(realChildren, syntheticChildren)); + } + return transformedIterator(allChildren, new Transformer() { + @Override + public Object transform(Object input) { + Resource resource = (Resource) input; + resource.getResourceMetadata().setResolutionPath(resource.getPath()); + return resource; + } + }); + } + + private List<Resource> getSyntheticChildren(Resource parent) { + Node<ResourceProviderHandler> node = storage.getTree().getNode(parent.getPath()); + if (node == null) { + return Collections.emptyList(); + } + List<Resource> children = new ArrayList<Resource>(); + for (Entry<String, Node<ResourceProviderHandler>> entry : node.getChildren().entrySet()) { + final String name = entry.getKey(); + final ResourceProviderHandler handler = entry.getValue().getValue(); + final String childPath = new StringBuilder(parent.getPath()).append('/').append(name).toString(); + final Resource child; + if (handler == null) { + child = new SyntheticResource(resolver, childPath, RESOURCE_TYPE_SYNTHETIC); + } else { + child = authenticator.getStateful(handler).getResource(childPath, parent, null, false, null); + } + if (child != null) { + children.add(child); + } + } + return children; + } + + /** + * Returns the union of all attribute names. + */ + public Collection<String> getAttributeNames() { + final Set<String> names = new LinkedHashSet<String>(); + for (StatefulResourceProvider p : authenticator.getAll(storage.getAttributableHandlers())) { + Collection<String> newNames = p.getAttributeNames(); + if (newNames != null) { + names.addAll(newNames); + } + } + return names; + } + + /** + * Returns the first non-null result of the + * {@link StatefulResourceProvider#getAttribute(String)} invocation on + * the providers. + */ + public Object getAttribute(String name) { + for (StatefulResourceProvider p : authenticator.getAll(storage.getAttributableHandlers())) { + Object attribute = p.getAttribute(name); + if (attribute != null) { + return attribute; + } + } + return null; + } + + /** + * Create a resource. Iterate over all modifiable ResourceProviders + * stopping at the first one which creates the resource and return the + * created resource. + * + * @throws UnsupportedOperationException + * If creation is not allowed/possible + * @throws PersistenceException + * If creation fails + * @return The new resource + */ + public Resource create(String path, Map<String, Object> properties) throws PersistenceException { + List<StatefulResourceProvider> matching = getMatchingModifiableProviders(path); + Resource creationResultResource = head(matching).create(path, properties, tail(matching)); + if (creationResultResource != null) { + return creationResultResource; + } + // If none of the viable handlers could create the resource or if the + // list of handlers was empty, throw an Exception + throw new UnsupportedOperationException("create '" + getName(path) + "' at " + ResourceUtil.getParent(path)); + } + + /** + * Delete the resource. Iterate over all modifiable ResourceProviders + * giving each an opportunity to delete the resource if they are able. + * + * @throws NullPointerException + * if resource is null + * @throws UnsupportedOperationException + * If deletion is not allowed/possible + * @throws PersistenceException + * If deletion fails + */ + public void delete(Resource resource) throws PersistenceException { + final String path = resource.getPath(); + final Map<String, String> parameters = resource.getResourceMetadata().getParameterMap(); + boolean anyProviderAttempted = false; + + // Give all viable handlers a chance to delete the resource + for (StatefulResourceProvider p : getMatchingModifiableProviders(path)) { + Resource providerResource = p.getResource(path, null, parameters, false, null); + if (providerResource != null) { + anyProviderAttempted = true; + p.delete(providerResource, null); + } + } + // If none of the viable handlers could delete the resource or if the + // list of handlers was empty, throw an Exception + if (!anyProviderAttempted) { + throw new UnsupportedOperationException("delete at '" + path + "'"); + } + } + + /** + * Revert changes on all modifiable ResourceProviders. + */ + public void revert() { + for (StatefulResourceProvider p : authenticator.getAllUsedModifiable()) { + p.revert(); + } + } + + /** + * Commit changes on all modifiable ResourceProviders. + */ + public void commit() throws PersistenceException { + for (StatefulResourceProvider p : authenticator.getAllUsedModifiable()) { + p.commit(); + } + } + + /** + * Check if any modifiable ResourceProvider has uncommited changes. + */ + public boolean hasChanges() { + for (StatefulResourceProvider p : authenticator.getAllUsedModifiable()) { + if (p.hasChanges()) { + return true; + } + } + return false; + } + + /** + * Queries all resource providers and combines the results. + */ + public QueryResult find(final Query q, final QueryInstructions qi) { + return new CombinedQueryResult(q, qi); + } + + /** + * Return the union of query languages supported by the providers. + */ + public String[] getSupportedLanguages() { + Set<String> supportedLanguages = new LinkedHashSet<String>(); + for (StatefulResourceProvider p : authenticator.getAll(storage.getJcrQuerableHandlers())) { + supportedLanguages.addAll(Arrays.asList(p.getSupportedLanguages())); + } + return supportedLanguages.toArray(new String[supportedLanguages.size()]); + } + + /** + * Queries all resource providers and combines the results. + */ + public Iterator<Resource> findResources(final String query, final String language) { + List<StatefulResourceProvider> querableRP = getQuerableProviders(language); + List<Iterator<Resource>> iterators = new ArrayList<Iterator<Resource>>(querableRP.size()); + for (StatefulResourceProvider p : querableRP) { + iterators.add(p.findResources(query, language)); + } + return new ChainedIterator<Resource>(iterators.iterator()); + } + + private List<StatefulResourceProvider> getQuerableProviders(String language) { + List<StatefulResourceProvider> querableProviders = new ArrayList<StatefulResourceProvider>(); + for (StatefulResourceProvider p : authenticator.getAll(storage.getJcrQuerableHandlers())) { + if (ArrayUtils.contains(p.getSupportedLanguages(), language)) { + querableProviders.add(p); + } + } + return querableProviders; + } + + /** + * Queries all resource providers and combines the results. + */ + public Iterator<Map<String, Object>> queryResources(final String query, final String language) { + List<StatefulResourceProvider> querableRP = getQuerableProviders(language); + List<Iterator<Map<String, Object>>> iterators = new ArrayList<Iterator<Map<String, Object>>>(querableRP.size()); + for (StatefulResourceProvider p : querableRP) { + iterators.add(p.queryResources(query, language)); + } + return new ChainedIterator<Map<String, Object>>(iterators.iterator()); + } + + /** + * Returns the first non-null result of the adaptTo() method invoked on the + * providers. + */ + @SuppressWarnings("unchecked") + public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) { + for (StatefulResourceProvider p : authenticator.getAll(storage.getAdaptableHandlers())) { + final Object adaptee = p.adaptTo(type); + if (adaptee != null) { + return (AdapterType) adaptee; + } + } + return null; + } + + /** + * Tries to find a resource provider accepting both paths and invokes + * {@link StatefulResourceProvider#copy(String, String)} method on it. + * Returns false if there's no such provider. + */ + public boolean copy(String srcAbsPath, String destAbsPath) throws PersistenceException { + List<StatefulResourceProvider> srcProviders = getMatchingProviders(srcAbsPath); + List<StatefulResourceProvider> dstProviders = getMatchingModifiableProviders(destAbsPath); + @SuppressWarnings("unchecked") + List<StatefulResourceProvider> intersection = ListUtils.intersection(srcProviders, dstProviders); + return head(intersection).copy(srcAbsPath, destAbsPath, tail(intersection)); + } + + /** + * Tries to find a resource provider accepting both paths and invokes + * {@link StatefulResourceProvider#move(String, String)} method on it. + * Returns false if there's no such provider. + */ + public boolean move(String srcAbsPath, String destAbsPath) throws PersistenceException { + List<StatefulResourceProvider> srcProviders = getMatchingModifiableProviders(srcAbsPath); + List<StatefulResourceProvider> dstProviders = getMatchingModifiableProviders(destAbsPath); + @SuppressWarnings("unchecked") + List<StatefulResourceProvider> intersection = ListUtils.intersection(srcProviders, dstProviders); + return head(intersection).move(srcAbsPath, destAbsPath, tail(intersection)); + } + + private List<StatefulResourceProvider> getMatchingProviders(String path) { + List<ResourceProviderHandler> handlers = storage.getTree().getMatchingNodes(path); + StatefulResourceProvider[] matching = new StatefulResourceProvider[handlers.size()]; + int i = matching.length - 1; + for (ResourceProviderHandler h : handlers) { + matching[i--] = authenticator.getStateful(h); // reverse order + } + return Arrays.asList(matching); + } + + private List<StatefulResourceProvider> getMatchingModifiableProviders(String path) { + List<ResourceProviderHandler> handlers = storage.getTree().getMatchingNodes(path); + List<StatefulResourceProvider> matching = new ArrayList<StatefulResourceProvider>(handlers.size()); + for (ResourceProviderHandler h : handlers) { + if (h.getInfo().getModifiable()) { + matching.add(authenticator.getStateful(h)); + } + } + Collections.reverse(matching); + return matching; + } + + private static StatefulResourceProvider head(List<StatefulResourceProvider> list) { + if (list.isEmpty()) { + return EMPTY_PROVIDER; + } else { + return list.get(0); + } + } + + private static <T> List<T> tail(List<T> list) { + if (list.isEmpty()) { + return Collections.emptyList(); + } else { + return list.subList(1, list.size()); + } + } + + private class CombinedQueryResult extends QueryResult implements Iterable<Resource> { + + private final Query q; + + private final QueryInstructions qi; + + public CombinedQueryResult(Query q, QueryInstructions qi) { + this.q = q; + this.qi = qi; + } + + @Override + public Iterable<Resource> getResources() { + return this; + } + + @Override + public Iterator<Resource> iterator() { + @SuppressWarnings("unchecked") + Iterator<Iterator<Resource>> iterators = IteratorUtils.transformedIterator(authenticator.getAll(storage.getNativeQuerableHandlers()).iterator(), + new Transformer() { + @Override + public Object transform(Object input) { + StatefulResourceProvider rp = (StatefulResourceProvider) input; + return rp.find(q, qi).getResources().iterator(); + } + }); + return new ChainedIterator<Resource>(iterators); + } + } + + private static class ChainedIterator<T> extends AbstractIterator<T> { + + private final Iterator<Iterator<T>> iterators; + + private Iterator<T> currentIterator; + + public ChainedIterator(Iterator<Iterator<T>> iterators) { + this.iterators = iterators; + } + + @Override + protected T seek() { + while (true) { + if (currentIterator == null) { + if (!iterators.hasNext()) { + return null; + } + currentIterator = iterators.next(); + continue; + } + if (currentIterator.hasNext()) { + return currentIterator.next(); + } else { + currentIterator = null; + } + } + } + } + + /** + * This iterator removes duplicated Resource entries. Regular resources + * overrides the synthetic ones. + */ + private static class UniqueIterator extends AbstractIterator<Resource> { + + private final Iterator<Resource> input; + + private final List<String> visited; + + private final Map<String, Resource> delayed; + + private Iterator<Resource> delayedIterator; + + public UniqueIterator(Iterator<Resource> input) { + this.input = input; + this.visited = new ArrayList<String>(); + this.delayed = new LinkedHashMap<String, Resource>(); + } + + @Override + protected Resource seek() { + while (input.hasNext()) { + Resource next = input.next(); + String path = next.getPath(); + + if (visited.contains(path)) { + continue; + } else if (next instanceof SyntheticResource) { + delayed.put(path, next); + } else { + visited.add(path); + delayed.remove(path); + return next; + } + } + + if (delayedIterator == null) { + delayedIterator = delayed.values().iterator(); + } + if (delayedIterator.hasNext()) { + return delayedIterator.next(); + } + return null; + } + } +} Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/EmptyResourceProvider.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/EmptyResourceProvider.java?rev=1709747&view=auto ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/EmptyResourceProvider.java (added) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/EmptyResourceProvider.java Wed Oct 21 08:23:09 2015 @@ -0,0 +1,151 @@ +/* + * 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.sling.resourceresolver.impl.providers.stateful; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.query.Query; +import org.apache.sling.api.resource.query.QueryInstructions; +import org.apache.sling.spi.resource.provider.QueryResult; +import org.apache.sling.spi.resource.provider.ResolveContext; +import org.apache.sling.spi.resource.provider.ResourceProvider; + +public class EmptyResourceProvider implements StatefulResourceProvider { + + @Override + public ResourceResolver getResourceResolver() { + return null; + } + + @Override + public void logout() { + } + + @Override + public void refresh() { + } + + @Override + public boolean isLive() { + return false; + } + + @Override + public Resource getParent(Resource child, List<StatefulResourceProvider> parentProviders) { + return null; + } + + @Override + public Resource getResource(String path, Resource parent, Map<String, String> parameters, boolean isResolve, + List<StatefulResourceProvider> parentProviders) { + return null; + } + + @Override + public Iterator<Resource> listChildren(Resource parent, List<StatefulResourceProvider> parentProviders) { + return null; + } + + @Override + public Collection<String> getAttributeNames() { + return null; + } + + @Override + public Object getAttribute(String name) { + return null; + } + + @Override + public Resource create(String path, Map<String, Object> properties, List<StatefulResourceProvider> parentProviders) + throws PersistenceException { + return null; + } + + @Override + public void delete(Resource resource, List<StatefulResourceProvider> parentProviders) throws PersistenceException { + } + + @Override + public void revert() { + } + + @Override + public void commit() throws PersistenceException { + } + + @Override + public boolean hasChanges() { + return false; + } + + @Override + public QueryResult find(Query q, QueryInstructions qi) { + return null; + } + + @Override + public String[] getSupportedLanguages() { + return null; + } + + @Override + public Iterator<Resource> findResources(String query, String language) { + return null; + } + + @Override + public Iterator<Map<String, Object>> queryResources(String query, String language) { + return null; + } + + @Override + public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) { + return null; + } + + @Override + public boolean copy(String srcAbsPath, String destAbsPath, List<StatefulResourceProvider> parentProviders) + throws PersistenceException { + return false; + } + + @Override + public boolean move(String srcAbsPath, String destAbsPath, List<StatefulResourceProvider> parentProviders) + throws PersistenceException { + return false; + } + + @Override + public ResourceProvider<Object> getResourceProvider() { + return null; + } + + @Override + public ResolveContext<Object> getContext(Map<String, String> parameters) { + return null; + } + +} Added: sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/ResourceProviderAuthenticator.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/ResourceProviderAuthenticator.java?rev=1709747&view=auto ============================================================================== --- sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/ResourceProviderAuthenticator.java (added) +++ sling/trunk/bundles/resourceresolver/src/main/java/org/apache/sling/resourceresolver/impl/providers/stateful/ResourceProviderAuthenticator.java Wed Oct 21 08:23:09 2015 @@ -0,0 +1,127 @@ +/* + * 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.sling.resourceresolver.impl.providers.stateful; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.sling.api.SlingException; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.runtime.dto.AuthType; +import org.apache.sling.resourceresolver.impl.ResourceAccessSecurityTracker; +import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler; +import org.apache.sling.spi.resource.provider.ResourceProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ResourceProviderAuthenticator { + + private static final Logger logger = LoggerFactory.getLogger(ResourceProviderAuthenticator.class); + + private final Map<ResourceProviderHandler, StatefulResourceProvider> stateful; + + private final List<StatefulResourceProvider> authenticated; + + private final List<StatefulResourceProvider> authenticatedModifiable; + + private final ResourceResolver resolver; + + private final Map<String, Object> authInfo; + + private final ResourceAccessSecurityTracker securityTracker; + + boolean allProvidersAuthenticated; + + public ResourceProviderAuthenticator(ResourceResolver resolver, Map<String, Object> authInfo, + ResourceAccessSecurityTracker securityTracker) throws LoginException { + this.stateful = new IdentityHashMap<ResourceProviderHandler, StatefulResourceProvider>(); + this.authenticated = new ArrayList<StatefulResourceProvider>(); + this.authenticatedModifiable = new ArrayList<StatefulResourceProvider>(); + this.resolver = resolver; + this.authInfo = authInfo; + this.securityTracker = securityTracker; + } + + public void authenticateAll(List<ResourceProviderHandler> handlers) throws LoginException { + for (ResourceProviderHandler h : handlers) { + authenticate(h); + } + } + + private StatefulResourceProvider authenticate(ResourceProviderHandler handler) throws LoginException { + StatefulResourceProvider rp = stateful.get(handler); + if (rp == null) { + rp = createStateful(handler); + if (rp == null) { + return null; + } + stateful.put(handler, rp); + if (handler.getInfo().getAuthType() != AuthType.no) { + authenticated.add(rp); + } + if (handler.getInfo().getModifiable()) { + authenticatedModifiable.add(rp); + } + } + return rp; + } + + public StatefulResourceProvider getStateful(ResourceProviderHandler handler) { + try { + return authenticate(handler); + } catch (LoginException e) { + throw new SlingException("Can't authenticate provider", e); + } + } + + public Collection<StatefulResourceProvider> getAllUsedAuthenticated() { + return authenticated; + } + + public Collection<StatefulResourceProvider> getAllUsedModifiable() { + return authenticatedModifiable; + } + + public Collection<StatefulResourceProvider> getAll(List<ResourceProviderHandler> handlers) { + List<StatefulResourceProvider> result = new ArrayList<StatefulResourceProvider>(handlers.size()); + for (ResourceProviderHandler h : handlers) { + result.add(getStateful(h)); + } + return result; + } + + private StatefulResourceProvider createStateful(ResourceProviderHandler handler) throws LoginException { + ResourceProvider<?> rp = handler.getResourceProvider(); + if (rp == null) { + logger.warn("Empty resource provider for {}", handler); + return null; + } + StatefulResourceProvider authenticated; + authenticated = new AuthenticatedResourceProvider(rp, handler.getInfo(), resolver, authInfo); + if (handler.getInfo().getUseResourceAccessSecurity()) { + authenticated = new SecureResourceProvider(authenticated, securityTracker); + } + return authenticated; + } + +} \ No newline at end of file