SLING-2938 - tag before changes
git-svn-id: https://svn.apache.org/repos/asf/sling/tags/whiteboard@1498303 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/SLING-2938-before-changes/extensions-adapter/README.txt b/SLING-2938-before-changes/extensions-adapter/README.txt
new file mode 100644
index 0000000..53eedd3
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/README.txt
@@ -0,0 +1,30 @@
+Apache Sling Adapter Manager
+
+Bundle implementing the AdapterManager and provides a convenience
+implementation of the Adaptable interface to make use of this
+AdapterManager.
+
+
+Getting Started
+===============
+
+This component uses a Maven 2 (http://maven.apache.org/) build
+environment. It requires a Java 5 JDK (or higher) and Maven (http://maven.apache.org/)
+2.0.7 or later. We recommend to use the latest Maven version.
+
+If you have Maven 2 installed, you can compile and
+package the jar using the following command:
+
+ mvn package
+
+See the Maven 2 documentation for other build features.
+
+The latest source code for this component is available in the
+Subversion (http://subversion.tigris.org/) source repository of
+the Apache Software Foundation. If you have Subversion installed,
+you can checkout the latest source using the following command:
+
+ svn checkout http://svn.apache.org/repos/asf/sling/trunk/extensions/adapter
+
+See the Subversion documentation for other source control features.
+
diff --git a/SLING-2938-before-changes/extensions-adapter/pom.xml b/SLING-2938-before-changes/extensions-adapter/pom.xml
new file mode 100644
index 0000000..4329b29
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/pom.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>16</version>
+ <relativePath>../../../parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.sling.adapter</artifactId>
+ <packaging>bundle</packaging>
+ <version>2.1.1-SNAPSHOT</version>
+
+ <name>Apache Sling Adapter Manager Implementation</name>
+ <description>
+ Bundle implementing the AdapterManager and provides a convenience
+ implementation of the Adaptable interface to make use of this
+ AdapterManager.
+ </description>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/adapter</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/adapter</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/adapter</url>
+ </scm>
+
+ <properties>
+ <site.jira.version.id>12314288</site.jira.version.id>
+ <site.javadoc.exclude>**.impl.**</site.javadoc.exclude>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ org.apache.sling.adapter;version=2.0.6
+ </Export-Package>
+ <Private-Package>
+ org.apache.sling.adapter.internal
+ </Private-Package>
+ <Import-Package>
+ org.apache.sling.api.adapter;version="[2.2,2.3)",
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <excludePackageNames>
+ org.apache.sling.adapter.internal
+ </excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.2.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.osgi</artifactId>
+ <version>2.1.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.json</artifactId>
+ <version>2.0.2-incubator</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jmock</groupId>
+ <artifactId>jmock-junit4</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>1.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.5</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/SlingAdaptable.java b/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/SlingAdaptable.java
new file mode 100644
index 0000000..1d01ac1
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/SlingAdaptable.java
@@ -0,0 +1,36 @@
+/*
+ * 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.adapter;
+
+
+/**
+ * The <code>SlingAdaptable</code> class is an (abstract) default
+ * implementation of the <code>Adaptable</code> interface. It just uses the
+ * default {@link org.apache.sling.api.adapter.AdapterManager} implemented
+ * in this bundle to adapt the itself to the requested type.
+ * <p>
+ * Extensions of this class may overwrite the {@link #adaptTo(Class)} method
+ * using their own knowledge of adapters and may call this base class
+ * implementation to fall back to an extended adapters.
+ * @deprecated Use the {@link org.apache.sling.api.adapter.SlingAdaptable} instead
+ */
+@Deprecated
+public abstract class SlingAdaptable extends org.apache.sling.api.adapter.SlingAdaptable {
+
+}
diff --git a/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterFactoryDescriptor.java b/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterFactoryDescriptor.java
new file mode 100644
index 0000000..efb215a
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterFactoryDescriptor.java
@@ -0,0 +1,60 @@
+/*
+ * 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.adapter.internal;
+
+import org.apache.sling.api.adapter.AdapterFactory;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+
+/**
+ * The <code>AdapterFactoryDescriptor</code> is an entry in the
+ * {@link AdapterFactoryDescriptorMap} conveying the list of adapter (target)
+ * types and the respective {@link AdapterFactory}.
+ */
+public class AdapterFactoryDescriptor {
+
+ private volatile AdapterFactory factory;
+
+ private final String[] adapters;
+
+ private final ServiceReference reference;
+
+ private final ComponentContext context;
+
+ public AdapterFactoryDescriptor(
+ final ComponentContext context,
+ final ServiceReference reference,
+ final String[] adapters) {
+ this.reference = reference;
+ this.context = context;
+ this.adapters = adapters;
+ }
+
+ public AdapterFactory getFactory() {
+ if ( factory == null ) {
+ factory = (AdapterFactory) context.locateService(
+ "AdapterFactory", reference);
+ }
+ return factory;
+ }
+
+ public String[] getAdapters() {
+ return adapters;
+ }
+}
diff --git a/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterFactoryDescriptorMap.java b/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterFactoryDescriptorMap.java
new file mode 100644
index 0000000..9286a74
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterFactoryDescriptorMap.java
@@ -0,0 +1,43 @@
+/*
+ * 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.adapter.internal;
+
+import java.util.TreeMap;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * The <code>AdapterFactoryDescriptorMap</code> is a sorted map of
+ * {@link AdapterFactoryDescriptor} instances indexed (and ordered) by their
+ * {@link ServiceReference}. This map is used to organize the
+ * registered {@link org.apache.sling.api.adapter.AdapterFactory} services for
+ * a given adaptable type.
+ * <p>
+ * Each entry in the map is a {@link AdapterFactoryDescriptor} thus enabling the
+ * registration of multiple factories for the same (adaptable, adapter) type
+ * tuple. Of course only the first entry (this is the reason for having a sorted
+ * map) for such a given tuple is actually being used. If that first instance is
+ * removed the eventual second instance may actually be used instead.
+ */
+public class AdapterFactoryDescriptorMap extends
+ TreeMap<ServiceReference, AdapterFactoryDescriptor> {
+
+ private static final long serialVersionUID = 2L;
+
+}
diff --git a/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterManagerImpl.java b/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterManagerImpl.java
new file mode 100644
index 0000000..42a7e66
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterManagerImpl.java
@@ -0,0 +1,436 @@
+/*
+ * 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.adapter.internal;
+
+import static org.apache.sling.api.adapter.AdapterFactory.ADAPTABLE_CLASSES;
+import static org.apache.sling.api.adapter.AdapterFactory.ADAPTER_CLASSES;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.adapter.AdapterFactory;
+import org.apache.sling.api.adapter.AdapterManager;
+import org.apache.sling.api.resource.SyntheticResource;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>AdapterManagerImpl</code> class implements the
+ * {@link AdapterManager} interface and is registered as a service for that
+ * interface to be used by any clients.
+ *
+ */
+@Component(immediate=true)
+@Service
+@Properties({
+ @Property(name=Constants.SERVICE_DESCRIPTION, value="Sling Adapter Manager"),
+ @Property(name=Constants.SERVICE_VENDOR, value="The Apache Software Foundation")
+
+})
+@Reference(name="AdapterFactory", referenceInterface=AdapterFactory.class,
+cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE, policy=ReferencePolicy.DYNAMIC)
+public class AdapterManagerImpl implements AdapterManager {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ /**
+ * The OSGi <code>ComponentContext</code> to retrieve
+ * {@link AdapterFactory} service instances.
+ */
+ private volatile ComponentContext context;
+
+ /**
+ * A list of {@link AdapterFactory} services bound to this manager before
+ * the manager has been activated. These bound services will be accessed as
+ * soon as the manager is being activated.
+ */
+ private final List<ServiceReference> boundAdapterFactories = new LinkedList<ServiceReference>();
+
+ /**
+ * A map of {@link AdapterFactoryDescriptorMap} instances. The map is
+ * indexed by the fully qualified class names listed in the
+ * {@link AdapterFactory#ADAPTABLE_CLASSES} property of the
+ * {@link AdapterFactory} services.
+ *
+ * @see AdapterFactoryDescriptorMap
+ */
+ private final Map<String, AdapterFactoryDescriptorMap> descriptors = new HashMap<String, AdapterFactoryDescriptorMap>();
+
+ /**
+ * Matrix of {@link AdapterFactoryDescriptor} instances primarily indexed by the fully
+ * qualified name of the class to be adapted and secondarily indexed by the
+ * fully qualified name of the class to adapt to (the target class).
+ * <p>
+ * This cache is built on demand by calling the
+ * {@link #getAdapterFactories(Class)} method. It is cleared
+ * whenever an adapter factory is registered on unregistered.
+ */
+ private final ConcurrentMap<String, Map<String, List<AdapterFactoryDescriptor>>> factoryCache
+ = new ConcurrentHashMap<String, Map<String, List<AdapterFactoryDescriptor>>>();
+
+ /**
+ * The service tracker for the event admin
+ */
+ @Reference(cardinality=ReferenceCardinality.OPTIONAL_UNARY, policy=ReferencePolicy.DYNAMIC)
+ private EventAdmin eventAdmin;
+
+ // ---------- AdapterManager interface -------------------------------------
+
+ /**
+ * Returns the adapted <code>adaptable</code> or <code>null</code> if
+ * the object cannot be adapted.
+ *
+ * @see org.apache.sling.api.adapter.AdapterManager#getAdapter(java.lang.Object, java.lang.Class)
+ */
+ public <AdapterType> AdapterType getAdapter(final Object adaptable,
+ final Class<AdapterType> type) {
+
+ // get the adapter factories for the type of adaptable object
+ final Map<String, List<AdapterFactoryDescriptor>> factories = getAdapterFactories(adaptable.getClass());
+
+ // get the factory for the target type
+ final List<AdapterFactoryDescriptor> descList = factories.get(type.getName());
+
+ if (descList != null && descList.size() > 0) {
+ for (AdapterFactoryDescriptor desc : descList) {
+ final AdapterFactory factory = desc == null ? null : desc.getFactory();
+
+ // have the factory adapt the adaptable if the factory exists
+ if (factory != null) {
+ log.debug("Trying adapter factory {} to map {} to {}",
+ new Object [] { factory, adaptable, type });
+
+ AdapterType adaptedObject = factory.getAdapter(adaptable, type);
+ if (adaptedObject != null) {
+ log.debug("Using adapter factory {} to map {} to {}",
+ new Object [] { factory, adaptable, type });
+ return adaptedObject;
+ }
+ }
+ }
+ }
+
+ // no factory has been found, so we cannot adapt
+ log.debug("No adapter factory found to map {} to {}", adaptable, type);
+
+ return null;
+ }
+
+ // ----------- SCR integration ---------------------------------------------
+
+ /**
+ * Activate the manager.
+ * Bind all already registered factories
+ * @param context Component context
+ */
+ protected void activate(final ComponentContext context) {
+ this.context = context;
+
+ // register all adapter factories bound before activation
+ final List<ServiceReference> refs;
+ synchronized ( this.boundAdapterFactories ) {
+ refs = new ArrayList<ServiceReference>(this.boundAdapterFactories);
+ boundAdapterFactories.clear();
+ }
+ for (final ServiceReference reference : refs) {
+ registerAdapterFactory(context, reference);
+ }
+
+ // final "enable" this manager by setting the instance
+ SyntheticResource.setAdapterManager(this);
+ }
+
+ /**
+ * Deactivate
+ * @param context Not used
+ */
+ protected void deactivate(final ComponentContext context) {
+ SyntheticResource.unsetAdapterManager(this);
+ this.context = null;
+ }
+
+ /**
+ * Bind a new adapter factory.
+ */
+ protected void bindAdapterFactory(final ServiceReference reference) {
+ boolean create = true;
+ if (context == null) {
+ synchronized ( this.boundAdapterFactories ) {
+ if (context == null) {
+ boundAdapterFactories.add(reference);
+ create = false;
+ }
+ }
+ }
+ if ( create ) {
+ registerAdapterFactory(context, reference);
+ }
+ }
+
+ /**
+ * Unbind a adapter factory.
+ */
+ protected void unbindAdapterFactory(final ServiceReference reference) {
+ unregisterAdapterFactory(reference);
+ }
+
+ // ---------- unit testing stuff only --------------------------------------
+
+ /**
+ * Returns the active adapter factories of this manager.
+ * <p>
+ * <strong><em>THIS METHOD IS FOR UNIT TESTING ONLY. IT MAY BE REMOVED OR
+ * MODIFIED WITHOUT NOTICE.</em></strong>
+ */
+ Map<String, AdapterFactoryDescriptorMap> getFactories() {
+ return descriptors;
+ }
+
+ /**
+ * Returns the current adapter factory cache.
+ * <p>
+ * <strong><em>THIS METHOD IS FOR UNIT TESTING ONLY. IT MAY BE REMOVED OR
+ * MODIFIED WITHOUT NOTICE.</em></strong>
+ */
+ Map<String, Map<String, List<AdapterFactoryDescriptor>>> getFactoryCache() {
+ return factoryCache;
+ }
+
+ /**
+ * Unregisters the {@link AdapterFactory} referred to by the service
+ * <code>reference</code> from the registry.
+ */
+ private void registerAdapterFactory(final ComponentContext context,
+ final ServiceReference reference) {
+ final String[] adaptables = PropertiesUtil.toStringArray(reference.getProperty(ADAPTABLE_CLASSES));
+ final String[] adapters = PropertiesUtil.toStringArray(reference.getProperty(ADAPTER_CLASSES));
+
+ if (adaptables == null || adaptables.length == 0 || adapters == null
+ || adapters.length == 0) {
+ return;
+ }
+
+ final AdapterFactoryDescriptor factoryDesc = new AdapterFactoryDescriptor(context,
+ reference, adapters);
+
+ for (final String adaptable : adaptables) {
+ AdapterFactoryDescriptorMap adfMap = null;
+ synchronized ( this.descriptors ) {
+ adfMap = descriptors.get(adaptable);
+ if (adfMap == null) {
+ adfMap = new AdapterFactoryDescriptorMap();
+ descriptors.put(adaptable, adfMap);
+ }
+ }
+ synchronized ( adfMap ) {
+ adfMap.put(reference, factoryDesc);
+ }
+ }
+
+ // clear the factory cache to force rebuild on next access
+ this.factoryCache.clear();
+
+ // send event
+ final EventAdmin localEA = this.eventAdmin;
+ if ( localEA != null ) {
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(SlingConstants.PROPERTY_ADAPTABLE_CLASSES, adaptables);
+ props.put(SlingConstants.PROPERTY_ADAPTER_CLASSES, adapters);
+ localEA.postEvent(new Event(SlingConstants.TOPIC_ADAPTER_FACTORY_ADDED,
+ props));
+ }
+ }
+
+ /**
+ * Unregisters the {@link AdapterFactory} referred to by the service
+ * <code>reference</code> from the registry.
+ */
+ private void unregisterAdapterFactory(final ServiceReference reference) {
+ synchronized ( this.boundAdapterFactories ) {
+ boundAdapterFactories.remove(reference);
+ }
+ final String[] adaptables = PropertiesUtil.toStringArray(reference.getProperty(ADAPTABLE_CLASSES));
+ final String[] adapters = PropertiesUtil.toStringArray(reference.getProperty(ADAPTER_CLASSES));
+
+ if (adaptables == null || adaptables.length == 0 || adapters == null
+ || adapters.length == 0) {
+ return;
+ }
+
+ boolean factoriesModified = false;
+ AdapterFactoryDescriptorMap adfMap = null;
+ for (final String adaptable : adaptables) {
+ synchronized ( this.descriptors ) {
+ adfMap = this.descriptors.get(adaptable);
+ }
+ if (adfMap != null) {
+ synchronized ( adfMap ) {
+ factoriesModified |= (adfMap.remove(reference) != null);
+ }
+ }
+ }
+
+ // only remove cache if some adapter factories have actually been
+ // removed
+ if (factoriesModified) {
+ this.factoryCache.clear();
+ }
+
+ // send event
+ final EventAdmin localEA = this.eventAdmin;
+ if ( localEA != null ) {
+ final Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(SlingConstants.PROPERTY_ADAPTABLE_CLASSES, adaptables);
+ props.put(SlingConstants.PROPERTY_ADAPTER_CLASSES, adapters);
+ localEA.postEvent(new Event(SlingConstants.TOPIC_ADAPTER_FACTORY_REMOVED,
+ props));
+ }
+ }
+
+ /**
+ * Returns the map of adapter factories index by adapter (target) class name
+ * for the given adaptable <code>clazz</code>. If no adapter exists for
+ * the <code>clazz</code> and empty map is returned.
+ *
+ * @param clazz The adaptable <code>Class</code> for which to return the
+ * adapter factory map by target class name.
+ * @return The map of adapter factories by target class name. The map may be
+ * empty if there is no adapter factory for the adaptable
+ * <code>clazz</code>.
+ */
+ private Map<String, List<AdapterFactoryDescriptor>> getAdapterFactories(final Class<?> clazz) {
+ final String className = clazz.getName();
+ Map<String, List<AdapterFactoryDescriptor>> entry = this.factoryCache.get(className);
+ if (entry == null) {
+ // create entry
+ entry = createAdapterFactoryMap(clazz);
+ this.factoryCache.put(className, entry);
+ }
+
+ return entry;
+ }
+
+ /**
+ * Creates a new target adapter factory map for the given <code>clazz</code>.
+ * First all factories defined to support the adaptable class by
+ * registration are taken. Next all factories for the implemented interfaces
+ * and finally all base class factories are copied.
+ *
+ * @param clazz The adaptable <code>Class</code> for which to build the
+ * adapter factory map by target class name.
+ * @return The map of adapter factories by target class name. The map may be
+ * empty if there is no adapter factory for the adaptable
+ * <code>clazz</code>.
+ */
+ private Map<String, List<AdapterFactoryDescriptor>> createAdapterFactoryMap(final Class<?> clazz) {
+ final Map<String, List<AdapterFactoryDescriptor>> afm = new HashMap<String, List<AdapterFactoryDescriptor>>();
+
+ // AdapterFactories for this class
+ AdapterFactoryDescriptorMap afdMap = null;
+ synchronized ( this.descriptors ) {
+ afdMap = this.descriptors.get(clazz.getName());
+ }
+ if (afdMap != null) {
+ final List<AdapterFactoryDescriptor> afdSet;
+ synchronized ( afdMap ) {
+ afdSet = new ArrayList<AdapterFactoryDescriptor>(afdMap.values());
+ }
+ for (final AdapterFactoryDescriptor afd : afdSet) {
+ final String[] adapters = afd.getAdapters();
+ for (final String adapter : adapters) {
+ // to handle service ranking, we add to the end of the list or create a new list
+ List<AdapterFactoryDescriptor> factoryDescriptors = afm.get(adapter);
+ if (factoryDescriptors == null) {
+ factoryDescriptors = new ArrayList<AdapterFactoryDescriptor>();
+ afm.put(adapter, factoryDescriptors);
+ }
+ factoryDescriptors.add(afd);
+ }
+ }
+ }
+
+ // AdapterFactories for the interfaces
+ final Class<?>[] interfaces = clazz.getInterfaces();
+ for (final Class<?> iFace : interfaces) {
+ copyAdapterFactories(afm, iFace);
+ }
+
+ // AdapterFactories for the super class
+ final Class<?> superClazz = clazz.getSuperclass();
+ if (superClazz != null) {
+ copyAdapterFactories(afm, superClazz);
+ }
+
+ return afm;
+ }
+
+ /**
+ * Copies all adapter factories for the given <code>clazz</code> from the
+ * <code>cache</code> to the <code>dest</code> map except for those
+ * factories whose target class already exists in the <code>dest</code>
+ * map.
+ *
+ * @param dest The map of target class name to adapter factory into which
+ * additional factories are copied. Existing factories are not
+ * replaced.
+ * @param clazz The adaptable class whose adapter factories are considered
+ * for adding into <code>dest</code>.
+ */
+ private void copyAdapterFactories(final Map<String, List<AdapterFactoryDescriptor>> dest,
+ final Class<?> clazz) {
+
+ // get the adapter factories for the adaptable clazz
+ final Map<String, List<AdapterFactoryDescriptor>> scMap = getAdapterFactories(clazz);
+
+ // for each target class copy the entry to dest and put it in the list or create the list
+ for (Map.Entry<String, List<AdapterFactoryDescriptor>> entry : scMap.entrySet()) {
+
+ List<AdapterFactoryDescriptor> factoryDescriptors = dest.get(entry.getKey());
+
+ if (factoryDescriptors == null) {
+ factoryDescriptors = new ArrayList<AdapterFactoryDescriptor>();
+ dest.put(entry.getKey(), factoryDescriptors);
+ }
+ for (AdapterFactoryDescriptor descriptor : entry.getValue()) {
+ factoryDescriptors.add(descriptor);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterWebConsolePlugin.java b/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterWebConsolePlugin.java
new file mode 100644
index 0000000..cd1a906
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/src/main/java/org/apache/sling/adapter/internal/AdapterWebConsolePlugin.java
@@ -0,0 +1,346 @@
+/*
+ * 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.adapter.internal;
+
+import static org.apache.sling.api.adapter.AdapterFactory.ADAPTABLE_CLASSES;
+import static org.apache.sling.api.adapter.AdapterFactory.ADAPTER_CLASSES;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.builder.CompareToBuilder;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.adapter.AdapterFactory;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component
+@Service(Servlet.class)
+@Properties({ @Property(name = Constants.SERVICE_DESCRIPTION, value = "Adapter Web Console Plugin"),
+ @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
+ @Property(name = "felix.webconsole.label", value = "adapters"),
+ @Property(name = "felix.webconsole.title", value = "Sling Adapters"),
+ @Property(name = "felix.webconsole.css", value = "/adapters/res/ui/adapters.css"),
+ @Property(name = "felix.webconsole.configprinter.modes", value = "always") })
+@SuppressWarnings("serial")
+public class AdapterWebConsolePlugin extends HttpServlet implements ServiceTrackerCustomizer, BundleListener {
+
+ private static final int INDENT = 4;
+
+ private static final String ADAPTER_CONDITION = "adapter.condition";
+
+ private final Logger logger = LoggerFactory.getLogger(AdapterWebConsolePlugin.class);
+
+ private List<AdaptableDescription> allAdaptables;
+ private Map<Object, List<AdaptableDescription>> adapterServices;
+ private Map<Bundle, List<AdaptableDescription>> adapterBundles;
+
+ private ServiceTracker adapterTracker;
+
+ private BundleContext bundleContext;
+
+ public Object addingService(final ServiceReference reference) {
+ final Object service = this.bundleContext.getService(reference);
+ addServiceMetadata(reference, service);
+ return service;
+ }
+
+ private void addServiceMetadata(final ServiceReference reference, final Object service) {
+ final String[] adapters = PropertiesUtil.toStringArray(reference.getProperty(ADAPTER_CLASSES));
+ final String condition = PropertiesUtil.toString(reference.getProperty(ADAPTER_CONDITION), null);
+ final String[] adaptables = PropertiesUtil.toStringArray(reference.getProperty(ADAPTABLE_CLASSES));
+ final List<AdaptableDescription> descriptions = new ArrayList<AdaptableDescription>(adaptables.length);
+ for (final String adaptable : adaptables) {
+ descriptions.add(new AdaptableDescription(reference.getBundle(), adaptable, adapters, condition));
+ }
+ synchronized (this) {
+ adapterServices.put(service, descriptions);
+ update();
+ }
+ }
+
+ public void bundleChanged(final BundleEvent event) {
+ if (event.getType() == BundleEvent.STOPPED) {
+ removeBundle(event.getBundle());
+ } else if (event.getType() == BundleEvent.STARTED) {
+ addBundle(event.getBundle());
+ }
+ }
+
+ public void modifiedService(final ServiceReference reference, final Object service) {
+ addServiceMetadata(reference, service);
+ }
+
+ public void removedService(final ServiceReference reference, final Object service) {
+ synchronized (this) {
+ adapterServices.remove(service);
+ update();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void addBundle(final Bundle bundle) {
+ final List<AdaptableDescription> descs = new ArrayList<AdaptableDescription>();
+ try {
+ final Enumeration<URL> files = bundle.getResources("SLING-INF/adapters.json");
+ if (files != null) {
+ while (files.hasMoreElements()) {
+ final InputStream stream = files.nextElement().openStream();
+ final String contents = IOUtils.toString(stream);
+ IOUtils.closeQuietly(stream);
+ final JSONObject obj = new JSONObject(contents);
+ for (final Iterator<String> adaptableNames = obj.keys(); adaptableNames.hasNext();) {
+ final String adaptableName = adaptableNames.next();
+ final JSONObject adaptable = obj.getJSONObject(adaptableName);
+ for (final Iterator<String> conditions = adaptable.keys(); conditions.hasNext();) {
+ final String condition = conditions.next();
+ String[] adapters;
+ final Object value = adaptable.get(condition);
+ if (value instanceof JSONArray) {
+ adapters = toStringArray((JSONArray) value);
+ } else {
+ adapters = new String[] { value.toString() };
+ }
+ descs.add(new AdaptableDescription(bundle, adaptableName, adapters, condition));
+ }
+ }
+ }
+ }
+ if (!descs.isEmpty()) {
+ synchronized ( this ) {
+ adapterBundles.put(bundle, descs);
+ update();
+ }
+ }
+ } catch (final IOException e) {
+ logger.error("Unable to load adapter descriptors for bundle " + bundle, e);
+ } catch (final JSONException e) {
+ logger.error("Unable to load adapter descriptors for bundle " + bundle, e);
+ }
+
+ }
+
+ private String[] toStringArray(final JSONArray value) throws JSONException {
+ final List<String> result = new ArrayList<String>(value.length());
+ for (int i = 0; i < value.length(); i++) {
+ result.add(value.getString(i));
+ }
+ return result.toArray(new String[value.length()]);
+ }
+
+ private void removeBundle(final Bundle bundle) {
+ synchronized ( this ) {
+ adapterBundles.remove(bundle);
+ update();
+ }
+ }
+
+ private void update() {
+ final List<AdaptableDescription> newList = new ArrayList<AdaptableDescription>();
+ for (final List<AdaptableDescription> descriptions : adapterServices.values()) {
+ newList.addAll(descriptions);
+ }
+ for (final List<AdaptableDescription> list : adapterBundles.values()) {
+ newList.addAll(list);
+ }
+ Collections.sort(newList);
+ allAdaptables = newList;
+ }
+
+ protected void activate(final ComponentContext ctx) throws InvalidSyntaxException {
+ this.bundleContext = ctx.getBundleContext();
+ this.adapterServices = new HashMap<Object, List<AdaptableDescription>>();
+ this.adapterBundles = new HashMap<Bundle, List<AdaptableDescription>>();
+ for (final Bundle bundle : this.bundleContext.getBundles()) {
+ if (bundle.getState() == Bundle.ACTIVE) {
+ addBundle(bundle);
+ }
+ }
+
+ this.bundleContext.addBundleListener(this);
+ final Filter filter = this.bundleContext.createFilter("(&(adaptables=*)(adapters=*)(" + Constants.OBJECTCLASS + "=" + AdapterFactory.SERVICE_NAME + "))");
+ this.adapterTracker = new ServiceTracker(this.bundleContext, filter, this);
+ this.adapterTracker.open();
+ }
+
+ protected void deactivate(final ComponentContext ctx) {
+ this.bundleContext.removeBundleListener(this);
+ this.adapterTracker.close();
+ this.adapterServices = null;
+ this.adapterBundles = null;
+ }
+
+ @Override
+ protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+ if (req.getPathInfo().endsWith("/data.json")) {
+ getJson(resp);
+ } else {
+ getHtml(resp);
+ }
+
+ }
+
+ private void getJson(final HttpServletResponse resp) throws ServletException, IOException {
+ resp.setContentType("application/json");
+ try {
+ final JSONObject obj = new JSONObject();
+ for (final AdaptableDescription desc : allAdaptables) {
+ final JSONObject adaptableObj;
+ if (obj.has(desc.adaptable)) {
+ adaptableObj = obj.getJSONObject(desc.adaptable);
+ } else {
+ adaptableObj = new JSONObject();
+ obj.put(desc.adaptable, adaptableObj);
+ }
+ for (final String adapter : desc.adapters) {
+ adaptableObj.accumulate(desc.condition == null ? "" : desc.condition, adapter);
+ }
+
+ }
+ resp.getWriter().println(obj.toString(INDENT));
+ } catch (final JSONException e) {
+ throw new ServletException("Unable to produce JSON", e);
+ }
+ }
+
+ private void getHtml(final HttpServletResponse resp) throws IOException {
+ final PrintWriter writer = resp.getWriter();
+ writer.println("<p class=\"statline ui-state-highlight\">${Introduction}</p>");
+ writer.println("<p>${intro}</p>");
+ writer.println("<p class=\"statline ui-state-highlight\">${How to Use This Information}</p>");
+ writer.println("<p>${usage}</p>");
+ writer.println("<table class=\"adapters nicetable\">");
+ writer.println("<thead><tr><th class=\"header\">${Adaptable Class}</th><th class=\"header\">${Adapter Class}</th><th class=\"header\">${Condition}</th><th class=\"header\">${Providing Bundle}</th></tr></thead>");
+ String rowClass = "odd";
+ for (final AdaptableDescription desc : allAdaptables) {
+ writer.printf("<tr class=\"%s ui-state-default\"><td>%s</td>", rowClass, desc.adaptable);
+ writer.print("<td>");
+ for (final String adapter : desc.adapters) {
+ writer.print(adapter);
+ writer.print("<br/>");
+ }
+ writer.print("</td>");
+ if (desc.condition == null) {
+ writer.print("<td> </td>");
+ } else {
+ writer.printf("<td>%s</td>", desc.condition);
+ }
+ writer.printf("<td><a href=\"${pluginRoot}/../bundles/%s\">%s (%s)</a></td>", desc.bundle.getBundleId(),
+ desc.bundle.getSymbolicName(), desc.bundle.getBundleId());
+ writer.println("</tr>");
+
+ if (rowClass.equals("odd")) {
+ rowClass = "even";
+ } else {
+ rowClass = "odd";
+ }
+ }
+ writer.println("</table>");
+ }
+
+ public void printConfiguration(final PrintWriter pw) {
+ pw.println("Current Apache Sling Adaptables:");
+ for (final AdaptableDescription desc : allAdaptables) {
+ pw.printf("Adaptable: %s\n", desc.adaptable);
+ if (desc.condition != null) {
+ pw.printf("Condition: %s\n", desc.condition);
+ }
+ pw.printf("Providing Bundle: %s\n", desc.bundle.getSymbolicName());
+ pw.printf("Available Adapters:\n");
+ for (final String adapter : desc.adapters) {
+ pw.print(" * ");
+ pw.println(adapter);
+ }
+ pw.println();
+ }
+ }
+
+ /**
+ * Method to retreive static resources from this bundle.
+ */
+ @SuppressWarnings("unused")
+ private URL getResource(final String path) {
+ if (path.startsWith("/adapters/res/ui/")) {
+ return this.getClass().getResource(path.substring(9));
+ }
+ return null;
+ }
+
+ class AdaptableDescription implements Comparable<AdaptableDescription> {
+ private final String adaptable;
+ private final String[] adapters;
+ private final String condition;
+ private final Bundle bundle;
+
+ public AdaptableDescription(final Bundle bundle, final String adaptable, final String[] adapters,
+ final String condition) {
+ this.adaptable = adaptable;
+ this.adapters = adapters;
+ this.condition = condition;
+ this.bundle = bundle;
+ }
+
+ @Override
+ public String toString() {
+ return "AdapterDescription [adaptable=" + this.adaptable + ", adapters=" + Arrays.toString(this.adapters)
+ + ", condition=" + this.condition + ", bundle=" + this.bundle + "]";
+ }
+
+ public int compareTo(final AdaptableDescription o) {
+ return new CompareToBuilder().append(this.adaptable, o.adaptable).append(this.condition, o.condition)
+ .append(this.adapters.length, o.adapters.length)
+ .append(this.bundle.getBundleId(), o.bundle.getBundleId()).toComparison();
+ }
+
+ }
+
+}
diff --git a/SLING-2938-before-changes/extensions-adapter/src/main/resources/OSGI-INF/l10n/bundle.properties b/SLING-2938-before-changes/extensions-adapter/src/main/resources/OSGI-INF/l10n/bundle.properties
new file mode 100644
index 0000000..84d7a84
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -0,0 +1,20 @@
+#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.
+intro=The following table lists a set of possible adaptations which can be done using the adaptTo() method. \
+ Because this table relies on additional metadata, it may not be exhaustive. If you know of an adaptation which \
+ is not listed here, please contact the provider of the adaptable.
+usage=The first column represents the adaptable, i.e. the object which you have. The second column \
+ lists the possible classes to which you can adapt that object. The third column lists any conditions \
+ which restrict when this adaptation can be made.
\ No newline at end of file
diff --git a/SLING-2938-before-changes/extensions-adapter/src/main/resources/res/ui/adapters.css b/SLING-2938-before-changes/extensions-adapter/src/main/resources/res/ui/adapters.css
new file mode 100644
index 0000000..e94c463
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/src/main/resources/res/ui/adapters.css
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+table.adapters tr .header {
+ background-image: none;
+}
\ No newline at end of file
diff --git a/SLING-2938-before-changes/extensions-adapter/src/test/java/org/apache/sling/adapter/internal/AdapterManagerTest.java b/SLING-2938-before-changes/extensions-adapter/src/test/java/org/apache/sling/adapter/internal/AdapterManagerTest.java
new file mode 100644
index 0000000..535a650
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/src/test/java/org/apache/sling/adapter/internal/AdapterManagerTest.java
@@ -0,0 +1,551 @@
+/*
+ * 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.adapter.internal;
+
+import org.apache.sling.adapter.mock.MockAdapterFactory;
+import org.apache.sling.api.adapter.AdapterFactory;
+import org.apache.sling.api.adapter.SlingAdaptable;
+import org.hamcrest.Matcher;
+import org.hamcrest.core.IsAnything;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.runner.RunWith;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(JMock.class)
+public class AdapterManagerTest {
+
+ private AdapterManagerImpl am;
+
+ protected final Mockery context = new JUnit4Mockery();
+
+ @org.junit.Before public void setUp() {
+ am = new AdapterManagerImpl();
+ }
+
+ @org.junit.After public void tearDown() {
+ am.deactivate(null); // not correct, but argument unused
+ }
+
+ /**
+ * Helper method to create a mock bundle
+ */
+ protected Bundle createBundle(String name) {
+ final Bundle bundle = this.context.mock(Bundle.class, name);
+ this.context.checking(new Expectations() {{
+ allowing(bundle).getBundleId();
+ will(returnValue(1L));
+ }});
+ return bundle;
+ }
+
+ /**
+ * Helper method to create a mock component context
+ */
+ protected ComponentContext createComponentContext() throws Exception {
+ final BundleContext bundleCtx = this.context.mock(BundleContext.class);
+ final Filter filter = this.context.mock(Filter.class);
+ final ComponentContext ctx = this.context.mock(ComponentContext.class);
+ this.context.checking(new Expectations() {{
+ allowing(ctx).locateService(with(any(String.class)), with(any(ServiceReference.class)));
+ will(returnValue(new MockAdapterFactory()));
+ allowing(ctx).getBundleContext();
+ will(returnValue(bundleCtx));
+ allowing(bundleCtx).createFilter(with(any(String.class)));
+ will(returnValue(filter));
+ allowing(bundleCtx).addServiceListener(with(any(ServiceListener.class)), with(any(String.class)));
+ allowing(bundleCtx).getServiceReferences(with(any(String.class)), with(any(String.class)));
+ will(returnValue(null));
+ allowing(bundleCtx).removeServiceListener(with(any(ServiceListener.class)));
+ }});
+ return ctx;
+ }
+
+ /**
+ * Helper method to create a mock component context
+ */
+ protected ComponentContext createMultipleAdaptersComponentContext(final ServiceReference firstServiceReference, final ServiceReference secondServiceReference) throws Exception {
+ final BundleContext bundleCtx = this.context.mock(BundleContext.class);
+ final Filter filter = this.context.mock(Filter.class);
+ final ComponentContext ctx = this.context.mock(ComponentContext.class);
+ this.context.checking(new Expectations() {{
+ allowing(ctx).locateService(with(any(String.class)), with(firstServiceReference));
+ will(returnValue(new FirstImplementationAdapterFactory()));
+ allowing(ctx).locateService(with(any(String.class)), with(secondServiceReference));
+ will(returnValue(new SecondImplementationAdapterFactory()));
+ allowing(ctx).getBundleContext();
+ will(returnValue(bundleCtx));
+ allowing(bundleCtx).createFilter(with(any(String.class)));
+ will(returnValue(filter));
+ allowing(bundleCtx).addServiceListener(with(any(ServiceListener.class)), with(any(String.class)));
+ allowing(bundleCtx).getServiceReferences(with(any(String.class)), with(any(String.class)));
+ will(returnValue(null));
+ allowing(bundleCtx).removeServiceListener(with(any(ServiceListener.class)));
+ }});
+ return ctx;
+ }
+
+ public static <T> Matcher<T> any(@SuppressWarnings("unused") Class<T> type) {
+ return new IsAnything<T>();
+ }
+
+ /**
+ * Helper method to create a mock service reference
+ */
+ protected ServiceReference createServiceReference() {
+ final ServiceReference ref = new ServiceReferenceImpl(1, new String[]{ TestSlingAdaptable.class.getName() }, new String[]{ITestAdapter.class.getName()});
+ return ref;
+ }
+
+ private static final class ServiceReferenceImpl implements ServiceReference {
+
+ private int ranking;
+ private String[] adapters;
+ private String[] classes;
+
+ public ServiceReferenceImpl(final int order, final String[] adapters, final String[] classes) {
+ this.ranking = order;
+ this.adapters = adapters;
+ this.classes = classes;
+ }
+
+ public boolean isAssignableTo(Bundle bundle, String className) {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public Bundle[] getUsingBundles() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public String[] getPropertyKeys() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public Object getProperty(String key) {
+ if ( key.equals(Constants.SERVICE_RANKING) ) {
+ return ranking;
+ }
+ if ( key.equals(AdapterFactory.ADAPTABLE_CLASSES) ) {
+ return adapters;
+ }
+ if ( key.equals(AdapterFactory.ADAPTER_CLASSES) ) {
+ return classes;
+ }
+
+ return null;
+ }
+
+ public Bundle getBundle() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public int compareTo(Object reference) {
+ Integer ranking1 = (Integer)getProperty(Constants.SERVICE_RANKING);
+ Integer ranking2 = (Integer)((ServiceReference)reference).getProperty(Constants.SERVICE_RANKING);
+ return ranking1.compareTo(ranking2);
+ }
+ };
+ /**
+ * Helper method to create a mock service reference
+ */
+ protected ServiceReference createServiceReference2() {
+ final ServiceReference ref = new ServiceReferenceImpl(2, new String[]{ TestSlingAdaptable2.class.getName() }, new String[]{TestAdapter.class.getName()});
+ return ref;
+ }
+
+ @org.junit.Test public void testUnitialized() {
+ assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
+ assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
+ }
+
+ @org.junit.Test public void testInitialized() throws Exception {
+ am.activate(this.createComponentContext());
+
+ assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
+ assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
+ }
+
+ @org.junit.Test public void testBindBeforeActivate() throws Exception {
+ final ServiceReference ref = createServiceReference();
+ am.bindAdapterFactory(ref);
+
+ // no cache and no factories yet
+ assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
+ assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
+
+ am.activate(this.createComponentContext());
+
+ // expect the factory, but cache is empty
+ assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
+ assertEquals("AdapterFactoryDescriptors must contain one entry", 1, am.getFactories().size());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
+ }
+
+ @org.junit.Test public void testBindAfterActivate() throws Exception {
+ am.activate(this.createComponentContext());
+
+ // no cache and no factories yet
+ assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
+ assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
+
+ final ServiceReference ref = createServiceReference();
+ am.bindAdapterFactory(ref);
+
+ // expect the factory, but cache is empty
+ assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories());
+ assertEquals("AdapterFactoryDescriptors must contain one entry", 1, am.getFactories().size());
+ assertTrue("AdapterFactory cache must be empty", am.getFactoryCache().isEmpty());
+
+ Map<String, AdapterFactoryDescriptorMap> f = am.getFactories();
+ AdapterFactoryDescriptorMap afdm = f.get(TestSlingAdaptable.class.getName());
+ assertNotNull(afdm);
+
+ AdapterFactoryDescriptor afd = afdm.get(ref);
+ assertNotNull(afd);
+ assertNotNull(afd.getFactory());
+ assertNotNull(afd.getAdapters());
+ assertEquals(1, afd.getAdapters().length);
+ assertEquals(ITestAdapter.class.getName(), afd.getAdapters()[0]);
+
+ assertNull(f.get(TestSlingAdaptable2.class.getName()));
+ }
+
+ @org.junit.Test public void testAdaptBase() throws Exception {
+ am.activate(this.createComponentContext());
+
+ TestSlingAdaptable data = new TestSlingAdaptable();
+ assertNull("Expect no adapter", am.getAdapter(data, ITestAdapter.class));
+
+ final ServiceReference ref = createServiceReference();
+ am.bindAdapterFactory(ref);
+
+ Object adapter = am.getAdapter(data, ITestAdapter.class);
+ assertNotNull(adapter);
+ assertTrue(adapter instanceof ITestAdapter);
+ }
+
+ @org.junit.Test public void testAdaptExtended() throws Exception {
+ am.activate(this.createComponentContext());
+
+ TestSlingAdaptable2 data = new TestSlingAdaptable2();
+ assertNull("Expect no adapter", am.getAdapter(data, ITestAdapter.class));
+
+ final ServiceReference ref = createServiceReference();
+ am.bindAdapterFactory(ref);
+
+ Object adapter = am.getAdapter(data, ITestAdapter.class);
+ assertNotNull(adapter);
+ assertTrue(adapter instanceof ITestAdapter);
+ }
+
+ @org.junit.Test public void testAdaptBase2() throws Exception {
+ am.activate(this.createComponentContext());
+
+ TestSlingAdaptable data = new TestSlingAdaptable();
+ assertNull("Expect no adapter", am.getAdapter(data, ITestAdapter.class));
+
+ final ServiceReference ref = createServiceReference();
+ am.bindAdapterFactory(ref);
+
+ final ServiceReference ref2 = createServiceReference2();
+ am.bindAdapterFactory(ref2);
+
+ Object adapter = am.getAdapter(data, ITestAdapter.class);
+ assertNotNull(adapter);
+ assertTrue(adapter instanceof ITestAdapter);
+ }
+
+ @org.junit.Test public void testAdaptExtended2() throws Exception {
+ am.activate(this.createComponentContext());
+
+ final ServiceReference ref = createServiceReference();
+ am.bindAdapterFactory(ref);
+
+ final ServiceReference ref2 = createServiceReference2();
+ am.bindAdapterFactory(ref2);
+
+ TestSlingAdaptable data = new TestSlingAdaptable();
+ Object adapter = am.getAdapter(data, ITestAdapter.class);
+ assertNotNull(adapter);
+ assertTrue(adapter instanceof ITestAdapter);
+ adapter = am.getAdapter(data, TestAdapter.class);
+ assertNull(adapter);
+
+ TestSlingAdaptable2 data2 = new TestSlingAdaptable2();
+ adapter = am.getAdapter(data2, ITestAdapter.class);
+ assertNotNull(adapter);
+ assertTrue(adapter instanceof ITestAdapter);
+ adapter = am.getAdapter(data2, TestAdapter.class);
+ assertNotNull(adapter);
+ assertTrue(adapter instanceof TestAdapter);
+ }
+
+ @org.junit.Test public void testAdaptMultipleAdapterFactories() throws Exception {
+ final ServiceReference firstAdaptable = new ServiceReferenceImpl(1, new String[]{AdapterObject.class.getName()}, new String[]{ ParentInterface.class.getName(), FirstImplementation.class.getName()});
+ final ServiceReference secondAdaptable = new ServiceReferenceImpl(2, new String[]{ AdapterObject.class.getName() }, new String[]{ParentInterface.class.getName(), SecondImplementation.class.getName()});
+
+ am.activate(this.createMultipleAdaptersComponentContext(firstAdaptable, secondAdaptable));
+
+ AdapterObject first = new AdapterObject(Want.FIRST_IMPL);
+ assertNull("Expect no adapter", am.getAdapter(first, ParentInterface.class));
+
+ AdapterObject second = new AdapterObject(Want.SECOND_IMPL);
+ assertNull("Expect no adapter", am.getAdapter(second, ParentInterface.class));
+
+ am.bindAdapterFactory(firstAdaptable);
+ am.bindAdapterFactory(secondAdaptable);
+
+ Object adapter = am.getAdapter(first, ParentInterface.class);
+ assertNotNull("Did not get an adapter back for first implementation, service ranking 1", adapter);
+ assertTrue("Did not get the correct adaptable back for first implementation, service ranking 1, ", adapter instanceof FirstImplementation);
+
+ adapter = am.getAdapter(second, ParentInterface.class);
+ assertNotNull("Did not get an adapter back for second implementation, service ranking 2", adapter);
+ assertTrue("Did not get the correct adaptable back for second implementation, service ranking 2, ", adapter instanceof SecondImplementation);
+
+ adapter = am.getAdapter(first, FirstImplementation.class);
+ assertNotNull("Did not get an adapter back for first implementation, service ranking 1", adapter);
+ assertTrue("Did not get the correct adaptable back for first implementation, service ranking 1, ", adapter instanceof FirstImplementation);
+
+ adapter = am.getAdapter(second, SecondImplementation.class);
+ assertNotNull("Did not get an adapter back for second implementation, service ranking 2", adapter);
+ assertTrue("Did not get the correct adaptable back for second implementation, service ranking 2, ", adapter instanceof SecondImplementation);
+ }
+
+ @org.junit.Test public void testAdaptMultipleAdapterFactoriesReverseOrder() throws Exception {
+ final ServiceReference firstAdaptable = new ServiceReferenceImpl(2, new String[]{AdapterObject.class.getName()}, new String[]{ ParentInterface.class.getName()});
+ final ServiceReference secondAdaptable = new ServiceReferenceImpl(1, new String[]{AdapterObject.class.getName()}, new String[]{ ParentInterface.class.getName()});
+ am.activate(this.createMultipleAdaptersComponentContext(firstAdaptable, secondAdaptable));
+
+ AdapterObject first = new AdapterObject(Want.FIRST_IMPL);
+ assertNull("Expect no adapter", am.getAdapter(first, ParentInterface.class));
+
+ AdapterObject second = new AdapterObject(Want.SECOND_IMPL);
+ assertNull("Expect no adapter", am.getAdapter(second, ParentInterface.class));
+
+ am.bindAdapterFactory(firstAdaptable);
+ am.bindAdapterFactory(secondAdaptable);
+
+ Object adapter = am.getAdapter(first, ParentInterface.class);
+ assertNotNull("Did not get an adapter back for first implementation, service ranking 2", adapter);
+ assertTrue("Did not get the correct adaptable back for first implementation, service ranking 2, ", adapter instanceof FirstImplementation);
+
+ }
+
+ @org.junit.Test public void testAdaptMultipleAdapterFactoriesServiceRanking() throws Exception {
+ final ServiceReference firstAdaptable = new ServiceReferenceImpl(1, new String[]{AdapterObject.class.getName()}, new String[]{ ParentInterface.class.getName(), FirstImplementation.class.getName()});
+ final ServiceReference secondAdaptable = new ServiceReferenceImpl(2, new String[]{ AdapterObject.class.getName() }, new String[]{ParentInterface.class.getName(), SecondImplementation.class.getName()});
+
+ am.activate(this.createMultipleAdaptersComponentContext(firstAdaptable, secondAdaptable));
+
+ AdapterObject first = new AdapterObject(Want.INDIFFERENT);
+ assertNull("Expect no adapter", am.getAdapter(first, ParentInterface.class));
+
+ AdapterObject second = new AdapterObject(Want.INDIFFERENT);
+ assertNull("Expect no adapter", am.getAdapter(second, ParentInterface.class));
+
+ am.bindAdapterFactory(firstAdaptable);
+ am.bindAdapterFactory(secondAdaptable);
+
+ Object adapter = am.getAdapter(first, ParentInterface.class);
+ assertNotNull("Did not get an adapter back for first implementation (from ParentInterface), service ranking 1", adapter);
+ assertTrue("Did not get the correct adaptable back for first implementation, service ranking 1, ", adapter instanceof FirstImplementation);
+
+ adapter = am.getAdapter(first, FirstImplementation.class);
+ assertNotNull("Did not get an adapter back for first implementation, service ranking 1", adapter);
+ assertTrue("Did not get the correct adaptable back for first implementation, service ranking 1, ", adapter instanceof FirstImplementation);
+
+ adapter = am.getAdapter(second, SecondImplementation.class);
+ assertNotNull("Did not get an adapter back for second implementation, service ranking 2", adapter);
+ assertTrue("Did not get the correct adaptable back for second implementation, service ranking 2, ", adapter instanceof SecondImplementation);
+ }
+
+ @org.junit.Test public void testAdaptMultipleAdapterFactoriesServiceRankingSecondHigherOrder() throws Exception {
+ final ServiceReference firstAdaptable = new ServiceReferenceImpl(2, new String[]{AdapterObject.class.getName()}, new String[]{ ParentInterface.class.getName(), FirstImplementation.class.getName()});
+ final ServiceReference secondAdaptable = new ServiceReferenceImpl(1, new String[]{ AdapterObject.class.getName() }, new String[]{ParentInterface.class.getName(), SecondImplementation.class.getName()});
+
+ am.activate(this.createMultipleAdaptersComponentContext(firstAdaptable, secondAdaptable));
+
+ AdapterObject first = new AdapterObject(Want.INDIFFERENT);
+ assertNull("Expect no adapter", am.getAdapter(first, ParentInterface.class));
+
+ AdapterObject second = new AdapterObject(Want.INDIFFERENT);
+ assertNull("Expect no adapter", am.getAdapter(second, ParentInterface.class));
+
+ am.bindAdapterFactory(firstAdaptable);
+ am.bindAdapterFactory(secondAdaptable);
+
+ Object adapter = am.getAdapter(first, ParentInterface.class);
+ assertNotNull("Did not get an adapter back for second implementation (from ParentInterface), service ranking 1", adapter);
+ assertTrue("Did not get the correct adaptable back for second implementation, service ranking 1, ", adapter instanceof SecondImplementation);
+
+ adapter = am.getAdapter(first, FirstImplementation.class);
+ assertNotNull("Did not get an adapter back for first implementation, service ranking 1", adapter);
+ assertTrue("Did not get the correct adaptable back for first implementation, service ranking 1, ", adapter instanceof FirstImplementation);
+
+ adapter = am.getAdapter(second, SecondImplementation.class);
+ assertNotNull("Did not get an adapter back for second implementation, service ranking 2", adapter);
+ assertTrue("Did not get the correct adaptable back for second implementation, service ranking 2, ", adapter instanceof SecondImplementation);
+ }
+
+ @org.junit.Test public void testAdaptMultipleAdapterFactoriesServiceRankingReverse() throws Exception {
+ final ServiceReference firstAdaptable = new ServiceReferenceImpl(1, new String[]{AdapterObject.class.getName()}, new String[]{ ParentInterface.class.getName(), FirstImplementation.class.getName()});
+ final ServiceReference secondAdaptable = new ServiceReferenceImpl(2, new String[]{ AdapterObject.class.getName() }, new String[]{ParentInterface.class.getName(), SecondImplementation.class.getName()});
+
+ am.activate(this.createMultipleAdaptersComponentContext(firstAdaptable, secondAdaptable));
+
+ AdapterObject first = new AdapterObject(Want.INDIFFERENT);
+ assertNull("Expect no adapter", am.getAdapter(first, ParentInterface.class));
+
+ AdapterObject second = new AdapterObject(Want.INDIFFERENT);
+ assertNull("Expect no adapter", am.getAdapter(second, ParentInterface.class));
+
+ // bind these in reverse order from the non-reverse test
+ am.bindAdapterFactory(secondAdaptable);
+ am.bindAdapterFactory(firstAdaptable);
+
+ Object adapter = am.getAdapter(first, ParentInterface.class);
+ assertNotNull("Did not get an adapter back for first implementation (from ParentInterface), service ranking 1", adapter);
+ assertTrue("Did not get the correct adaptable back for first implementation, service ranking 1, ", adapter instanceof FirstImplementation);
+
+ adapter = am.getAdapter(first, FirstImplementation.class);
+ assertNotNull("Did not get an adapter back for first implementation, service ranking 1", adapter);
+ assertTrue("Did not get the correct adaptable back for first implementation, service ranking 1, ", adapter instanceof FirstImplementation);
+
+ adapter = am.getAdapter(second, SecondImplementation.class);
+ assertNotNull("Did not get an adapter back for second implementation, service ranking 2", adapter);
+ assertTrue("Did not get the correct adaptable back for second implementation, service ranking 2, ", adapter instanceof SecondImplementation);
+ }
+
+
+ //---------- Test Adaptable and Adapter Classes ---------------------------
+
+ public static class TestSlingAdaptable extends SlingAdaptable {
+
+ }
+
+ public static class TestSlingAdaptable2 extends TestSlingAdaptable {
+
+ }
+
+ public static interface ITestAdapter {
+
+ }
+
+ public static class TestAdapter {
+
+ }
+
+ public class FirstImplementationAdapterFactory implements AdapterFactory {
+
+ public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) {
+ if (adaptable instanceof AdapterObject) {
+ AdapterObject adapterObject = (AdapterObject) adaptable;
+ switch (adapterObject.getWhatWeWant()) {
+ case FIRST_IMPL:
+ case INDIFFERENT:
+ return (AdapterType) new FirstImplementation();
+ case SECOND_IMPL:
+ return null;
+ }
+ }
+ throw new RuntimeException("Must pass the correct adaptable");
+ }
+ }
+
+ public class SecondImplementationAdapterFactory implements AdapterFactory {
+
+ public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) {
+ if (adaptable instanceof AdapterObject) {
+ AdapterObject adapterObject = (AdapterObject) adaptable;
+ switch (adapterObject.getWhatWeWant()) {
+ case SECOND_IMPL:
+ case INDIFFERENT:
+ return (AdapterType) new SecondImplementation();
+ case FIRST_IMPL:
+ return null;
+ }
+ }
+ throw new RuntimeException("Must pass the correct adaptable");
+ }
+ }
+
+ public static interface ParentInterface {
+ }
+
+ public static class FirstImplementation implements ParentInterface {
+ }
+
+ public static class SecondImplementation implements ParentInterface {
+ }
+
+ public enum Want {
+
+ /**
+ * Indicates we definitively want the "first implementation" adapter factory to execute the adapt
+ */
+ FIRST_IMPL,
+
+ /**
+ * Indicates we definitively want the "second implementation" adapter factory to execute the adapt
+ */
+ SECOND_IMPL,
+
+ /**
+ * Indicates we are indifferent to which factory is used to execute the adapt, used for testing service ranking
+ */
+ INDIFFERENT
+ }
+
+ public static class AdapterObject {
+ private Want whatWeWant;
+
+ public AdapterObject(Want whatWeWant) {
+ this.whatWeWant = whatWeWant;
+ }
+
+ public Want getWhatWeWant() {
+ return whatWeWant;
+ }
+ }
+
+}
diff --git a/SLING-2938-before-changes/extensions-adapter/src/test/java/org/apache/sling/adapter/mock/MockAdapterFactory.java b/SLING-2938-before-changes/extensions-adapter/src/test/java/org/apache/sling/adapter/mock/MockAdapterFactory.java
new file mode 100644
index 0000000..f4f403c
--- /dev/null
+++ b/SLING-2938-before-changes/extensions-adapter/src/test/java/org/apache/sling/adapter/mock/MockAdapterFactory.java
@@ -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.adapter.mock;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import org.apache.sling.api.adapter.AdapterFactory;
+
+public class MockAdapterFactory implements AdapterFactory {
+
+ private static final InvocationHandler NOP_INVOCATION_HANDLER = new InvocationHandler() {
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ return null;
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ public <AdapterType> AdapterType getAdapter(Object adaptable,
+ Class<AdapterType> type) {
+
+ try {
+ if (type.isInterface()) {
+ return (AdapterType) Proxy.newProxyInstance(type.getClassLoader(),
+ new Class[] { type }, NOP_INVOCATION_HANDLER);
+ }
+
+ return type.newInstance();
+ } catch (Exception e) {
+ // ignore
+ }
+
+ return null;
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/README.txt b/SLING-2938-before-changes/sling-api/README.txt
new file mode 100644
index 0000000..66f206b
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/README.txt
@@ -0,0 +1,33 @@
+Apache Sling API
+
+The Sling API defines an extension to the Servlet API 2.4 to
+provide access to content and unified access to request
+parameters hiding the differences between the different methods
+of transferring parameters from client to server. Note that the
+Sling API bundle does not include the Servlet API but instead
+requires the API to be provided by the Servlet container in
+which the Sling framework is running or by another bundle.
+
+Getting Started
+===============
+
+This component uses a Maven 2 (http://maven.apache.org/) build
+environment. It requires a Java 5 JDK (or higher) and Maven (http://maven.apache.org/)
+2.0.7 or later. We recommend to use the latest Maven version.
+
+If you have Maven 2 installed, you can compile and
+package the jar using the following command:
+
+ mvn package
+
+See the Maven 2 documentation for other build features.
+
+The latest source code for this component is available in the
+Subversion (http://subversion.tigris.org/) source repository of
+the Apache Software Foundation. If you have Subversion installed,
+you can checkout the latest source using the following command:
+
+ svn checkout http://svn.apache.org/repos/asf/sling/trunk/api
+
+See the Subversion documentation for other source control features.
+
diff --git a/SLING-2938-before-changes/sling-api/pom.xml b/SLING-2938-before-changes/sling-api/pom.xml
new file mode 100644
index 0000000..c7f035c
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/pom.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>16</version>
+ <relativePath>../../parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.sling.api</artifactId>
+ <version>2.4.3-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Sling API</name>
+ <description>
+ The Apache Sling API defines an extension to the Servlet
+ API 2.4 to provide access to content and unified access
+ to request parameters hiding the differences between the
+ different methods of transferring parameters from client
+ to server. Note that the Apache Sling API bundle does not
+ include the Servlet API but instead requires the API to
+ be provided by the Servlet container in which the Apache
+ Sling framework is running or by another bundle.
+ </description>
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/api</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/api</developerConnection>
+ <url>http://svn.apache.org/viewvc/sling/trunk/bundles/api</url>
+ </scm>
+
+ <properties>
+ <site.jira.version.id>12314252</site.jira.version.id>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>biz.aQute</groupId>
+ <artifactId>bndlib</artifactId>
+ <version>1.50.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jmock</groupId>
+ <artifactId>jmock-junit4</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.testing</artifactId>
+ <version>2.0.6</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Create the bundle of the Sling API -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.3.7</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-DocURL>
+ http://sling.apache.org/site/sling-api.html
+ </Bundle-DocURL>
+ <!-- Require explicit version of the servlet API -->
+ <Import-Package>
+ javax.servlet.*;version=2.4,*
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <!-- See SLING-1521 -->
+ <exclude>src/main/resources/org/apache/sling/api/servlets/HtmlResponse.html</exclude>
+ <!-- Used by maven-remote-resources-plugin -->
+ <exclude>src/main/appended-resources/META-INF/*</exclude>
+ <!-- Generated by maven-remote-resources-plugin -->
+ <exclude>velocity.log</exclude>
+ <!-- don't check anything in target -->
+ <exclude>target/*</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingConstants.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingConstants.java
new file mode 100644
index 0000000..4ab3d6b
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingConstants.java
@@ -0,0 +1,401 @@
+/*
+ * 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.api;
+
+/**
+ * The <code>SlingConstants</code> interface provides some symbolic constants
+ * for well known constant strings in Sling. Even though these constants will
+ * never change, it is recommended that applications refer to the symbolic
+ * constants instead of code the strings themselves.
+ */
+public class SlingConstants {
+
+ /**
+ * The namespace prefix used throughout Sling (value is "sling").
+ * <p>
+ * The actual use depends on the environment. For example a
+ * {@link org.apache.sling.api.resource.ResourceResolver} using a JCR
+ * repository may name Sling node types and items using namespaces mapped to
+ * this prefix. A JSP tag library for Sling may use this prefix as the
+ * namespace prefix for its tags.
+ */
+ public static final String NAMESPACE_PREFIX = "sling";
+
+ /**
+ * The namespace URI prefix to be used by Sling projects to define
+ * namespaces (value is "http://sling.apache.org/").
+ * <p>
+ * The actual namespace URI depends on the environment. For example a
+ * {@link org.apache.sling.api.resource.ResourceResolver} using a JCR
+ * repository may define its namespace as
+ * <code><em>NAMESPACE_URI_ROOT + "jcr/sling/1.0"</em></code>. A JSP
+ * tag library for Sling may define its namespace as
+ * <code><em>NAMESPACE_URI_ROOT + "taglib/sling/1.0"</em></code>.
+ */
+ public static final String NAMESPACE_URI_ROOT = "http://sling.apache.org/";
+
+ /**
+ * The name of the request attribute containing the <code>Servlet</code>
+ * which included the servlet currently being active (value is
+ * "org.apache.sling.api.include.servlet"). This attribute is only set if
+ * the serlvet or script is included via
+ * <code>RequestDispatcher.include</code> from another servlet or script.
+ * <p>
+ * The type of the attribute value is <code>javax.servlet.Servlet</code>.
+ */
+ public static final String ATTR_REQUEST_SERVLET = "org.apache.sling.api.include.servlet";
+
+ /**
+ * The name of the request attribute containing the <code>Resource</code>
+ * underlying the <code>Servlet</code> which included the servlet currently
+ * being active (value is "org.apache.sling.api.include.resource"). This
+ * attribute is only set if the serlvet or script is included via
+ * <code>RequestDispatcher.include</code> from another servlet or script.
+ * <p>
+ * The type of the attribute value is
+ * <code>org.apache.sling.api.resource.Resource</code>.
+ */
+ public static final String ATTR_REQUEST_CONTENT = "org.apache.sling.api.include.resource";
+
+ /**
+ * The name of the request attribute containing the
+ * <code>RequestPathInfo</code> underlying the <code>Servlet</code> which
+ * included the servlet currently being active (value is
+ * "org.apache.sling.api.include.request_path_info"). This attribute is only
+ * set if the serlvet or script is included via
+ * <code>RequestDispatcher.include</code> from another servlet or script.
+ * <p>
+ * The type of the attribute value is
+ * <code>org.apache.sling.api.request.RequestPathInfo</code>.
+ */
+ public static final String ATTR_REQUEST_PATH_INFO = "org.apache.sling.api.include.request_path_info";
+
+ /**
+ * The name of the request attribute containing the
+ * <code>HttpServletRequest.getRequestURI()</code> of the request which
+ * included the servlet currently being active underlying the
+ * <code>Servlet</code> which included the servlet currently being active
+ * (value is "javax.servlet.include.request_uri"). This attribute is only
+ * set if the serlvet or script is included via
+ * <code>RequestDispatcher.include</code> from another servlet or script.
+ * <p>
+ * The type of the attribute value is <code>String</code>.
+ * <p>
+ * <b>Note:</b> In Sling, the
+ * <code>HttpServletRequest.getRequestURI()</code> method will always return
+ * the same result regardless of whether it is called from the client
+ * request processing servlet or script or from an included servlet or
+ * script. This request attribute is set for compatibility with the Servlet
+ * API specification.
+ */
+ public static final String ATTR_INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
+
+ /**
+ * The name of the request attribute containing the
+ * <code>HttpServletRequest.getContextPath()</code> of the request which
+ * included the servlet currently being active underlying the
+ * <code>Servlet</code> which included the servlet currently being active
+ * (value is "javax.servlet.include.context_path"). This attribute is only
+ * set if the serlvet or script is included via
+ * <code>RequestDispatcher.include</code> from another servlet or script.
+ * <p>
+ * The type of the attribute value is <code>String</code>.
+ * <p>
+ * <b>Note:</b> In Sling, the
+ * <code>HttpServletRequest.getContextPath()</code> method will always
+ * return the same result regardless of whether it is called from the client
+ * request processing servlet or script or from an included servlet or
+ * script. This request attribute is set for compatibility with the Servlet
+ * API specification.
+ */
+ public static final String ATTR_INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
+
+ /**
+ * The name of the request attribute containing the
+ * <code>HttpServletRequest.getServletPath()</code> of the request which
+ * included the servlet currently being active underlying the
+ * <code>Servlet</code> which included the servlet currently being active
+ * (value is "javax.servlet.include.servlet_path"). This attribute is only
+ * set if the serlvet or script is included via
+ * <code>RequestDispatcher.include</code> from another servlet or script.
+ * <p>
+ * The type of the attribute value is <code>String</code>.
+ * <p>
+ * <b>Note:</b> In Sling, the
+ * <code>HttpServletRequest.getServletPath()</code> method will always
+ * return the same result regardless of whether it is called from the client
+ * request processing servlet or script or from an included servlet or
+ * script. This request attribute is set for compatibility with the Servlet
+ * API specification.
+ */
+ public static final String ATTR_INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
+
+ /**
+ * The name of the request attribute containing the
+ * <code>HttpServletRequest.getPathInfo()</code> of the request which
+ * included the servlet currently being active underlying the
+ * <code>Servlet</code> which included the servlet currently being active
+ * (value is "javax.servlet.include.path_info"). This attribute is only set
+ * if the serlvet or script is included via
+ * <code>RequestDispatcher.include</code> from another servlet or script.
+ * <p>
+ * The type of the attribute value is <code>String</code>.
+ * <p>
+ * <b>Note:</b> In Sling, the <code>HttpServletRequest.getPathInfo()</code>
+ * method will always return the same result regardless of whether it is
+ * called from the client request processing servlet or script or from an
+ * included servlet or script. This request attribute is set for
+ * compatibility with the Servlet API specification.
+ */
+ public static final String ATTR_INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
+
+ /**
+ * The name of the request attribute containing the
+ * <code>HttpServletRequest.getQueryString()</code> of the request which
+ * included the servlet currently being active underlying the
+ * <code>Servlet</code> which included the servlet currently being active
+ * (value is "javax.servlet.include.query_string"). This attribute is only
+ * set if the serlvet or script is included via
+ * <code>RequestDispatcher.include</code> from another servlet or script.
+ * <p>
+ * The type of the attribute value is <code>String</code>.
+ * <p>
+ * <b>Note:</b> In Sling, the
+ * <code>HttpServletRequest.getQueryString()</code> method will always
+ * return the same result regardless of whether it is called from the client
+ * request processing servlet or script or from an included servlet or
+ * script. This request attribute is set for compatibility with the Servlet
+ * API specification.
+ */
+ public static final String ATTR_INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
+
+ // ---------- Error handling -----------------------------------------------
+
+ /**
+ * The name of the request attribute containing the exception thrown causing
+ * the error handler to be called (value is
+ * "javax.servlet.error.exception"). This attribute is only available to
+ * error handling servlets and only if an exception has been thrown causing
+ * error handling.
+ * <p>
+ * The type of the attribute value is <code>java.lang.Throwable</code>.
+ */
+ public static final String ERROR_EXCEPTION = "javax.servlet.error.exception";
+
+ /**
+ * The name of the request attribute containing the fully qualified class
+ * name of the exception thrown causing the error handler to be called
+ * (value is "javax.servlet.error.exception_type"). This attribute is only
+ * available to error handling servlets and only if an exception has been
+ * thrown causing error handling. This attribute is present for backwards
+ * compatibility only. Error handling servlet implementors are advised to
+ * use the {@link #ERROR_EXCEPTION Throwable} itself.
+ * <p>
+ * The type of the attribute value is <code>java.lang.String</code>.
+ */
+ public static final String ERROR_EXCEPTION_TYPE = "javax.servlet.error.exception_type";
+
+ /**
+ * The name of the request attribute containing the message of the error
+ * situation (value is "javax.servlet.error.message"). If an exception
+ * caused error handling, this is the exceptions message from
+ * <code>Throwable.getMessage()</code>. If error handling is caused by a
+ * call to one of the <code>SlingHttpServletResponse.sendError</code>
+ * methods, this attribute contains the optional message.
+ * <p>
+ * The type of the attribute value is <code>java.lang.String</code>.
+ */
+ public static final String ERROR_MESSAGE = "javax.servlet.error.message";
+
+ /**
+ * The name of the request attribute containing the URL requested by the
+ * client during whose processing the error handling was caused (value is
+ * "javax.servlet.error.request_uri"). This property is retrieved calling
+ * the <code>SlingHttpServletRequest.getRequestURI()</code> method.
+ * <p>
+ * The type of the attribute value is <code>java.lang.String</code>.
+ */
+ public static final String ERROR_REQUEST_URI = "javax.servlet.error.request_uri";
+
+ /**
+ * The name of the request attribute containing the name of the servlet
+ * which caused the error handling (value is
+ * "javax.servlet.error.servlet_name").
+ * <p>
+ * The type of the attribute value is <code>java.lang.String</code>.
+ */
+ public static final String ERROR_SERVLET_NAME = "javax.servlet.error.servlet_name";
+
+ /**
+ * The name of the request attribute containing the status code sent to the
+ * client (value is "javax.servlet.error.status_code"). Error handling
+ * servlets may set this status code on their response to the client or they
+ * may choose to set another status code. For example a handler for
+ * NOT_FOUND status (404) may opt to redirect to a new location and thus not
+ * set the 404 status but a MOVED_PERMANENTLY (301) status. If this
+ * attribute is not set and the error handler is not configured to set its
+ * own status code anyway, a default value of INTERNAL_SERVER_ERROR (500)
+ * should be sent.
+ * <p>
+ * The type of the attribute value is <code>java.lang.Integer</code>.
+ */
+ public static final String ERROR_STATUS = "javax.servlet.error.status_code";
+
+ /**
+ * The topic for the OSGi event which is sent when a resource has been added
+ * to the resource tree.
+ * The event contains at least the {@link #PROPERTY_PATH}, {@link #PROPERTY_RESOURCE_SUPER_TYPE}
+ * and {@link #PROPERTY_RESOURCE_TYPE} properties.
+ * @since 2.0.6
+ */
+ public static final String TOPIC_RESOURCE_ADDED = "org/apache/sling/api/resource/Resource/ADDED";
+
+ /**
+ * The topic for the OSGi event which is sent when a resource has been removed
+ * from the resource tree.
+ * The event contains at least the {@link #PROPERTY_PATH}. As the resource has already been removed
+ * no further information like resource type etc. might be available.
+ * @since 2.0.6
+ */
+ public static final String TOPIC_RESOURCE_REMOVED = "org/apache/sling/api/resource/Resource/REMOVED";
+
+ /**
+ * The topic for the OSGi event which is sent when a resource has been changed
+ * in the resource tree.
+ * The event contains at least the {@link #PROPERTY_PATH}, {@link #PROPERTY_RESOURCE_SUPER_TYPE}
+ * and {@link #PROPERTY_RESOURCE_TYPE} properties.
+ * Since 2.2.0 the event might contain these properties {@link #PROPERTY_ADDED_ATTRIBUTES},
+ * {@link #PROPERTY_REMOVED_ATTRIBUTES}, {@link #PROPERTY_CHANGED_ATTRIBUTES}. All of them are
+ * optional.
+ * @since 2.0.6
+ */
+ public static final String TOPIC_RESOURCE_CHANGED = "org/apache/sling/api/resource/Resource/CHANGED";
+
+ /**
+ * The topic for the OSGi event which is sent when a resource provider has been
+ * added to the resource tree.
+ * The event contains at least the {@link #PROPERTY_PATH} property.
+ * @since 2.0.6
+ */
+ public static final String TOPIC_RESOURCE_PROVIDER_ADDED = "org/apache/sling/api/resource/ResourceProvider/ADDED";
+
+ /**
+ * The topic for the OSGi event which is sent when a resource provider has been
+ * removed from the resource tree.
+ * The event contains at least the {@link #PROPERTY_PATH} property.
+ * @since 2.0.6
+ */
+ public static final String TOPIC_RESOURCE_PROVIDER_REMOVED = "org/apache/sling/api/resource/ResourceProvider/REMOVED";
+
+ /**
+ * The topic for the OSGi event which is sent when the resource mapping changes.
+ * @since 2.2.0
+ */
+ public static final String TOPIC_RESOURCE_RESOLVER_MAPPING_CHANGED = "org/apache/sling/api/resource/ResourceResolverMapping/CHANGED";
+
+ /**
+ * The name of the event property holding the resource path.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_PATH = "path";
+
+ /**
+ * The name of the event property holding the userid. This property is optional.
+ * @since 2.1.0
+ */
+ public static final String PROPERTY_USERID = "userid";
+
+ /**
+ * The name of the event property holding the resource type.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_RESOURCE_TYPE = "resourceType";
+
+ /**
+ * The name of the event property holding the resource super type.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_RESOURCE_SUPER_TYPE = "resourceSuperType";
+
+ /**
+ * The name of the event property holding the changed attribute names
+ * of a resource for an {@link #TOPIC_RESOURCE_CHANGED} event.
+ * The value of the property is a string array.
+ * @since 2.2.0
+ */
+ public static final String PROPERTY_CHANGED_ATTRIBUTES = "resourceChangedAttributes";
+
+ /**
+ * The name of the event property holding the added attribute names
+ * of a resource for an {@link #TOPIC_RESOURCE_CHANGED} event.
+ * The value of the property is a string array.
+ * @since 2.2.0
+ */
+ public static final String PROPERTY_ADDED_ATTRIBUTES = "resourceAddedAttributes";
+
+ /**
+ * The name of the event property holding the removed attribute names
+ * of a resource for an {@link #TOPIC_RESOURCE_CHANGED} event.
+ * The value of the property is a string array.
+ * @since 2.2.0
+ */
+ public static final String PROPERTY_REMOVED_ATTRIBUTES = "resourceRemovedAttributes";
+
+ /**
+ * The topic for the OSGi event which is sent when an adapter factory has been added.
+ * The event contains at least the {@link #PROPERTY_ADAPTABLE_CLASSES},
+ * and {@link #PROPERTY_ADAPTER_CLASSES} poperties.
+ * @since 2.0.6
+ */
+ public static final String TOPIC_ADAPTER_FACTORY_ADDED = "org/apache/sling/api/adapter/AdapterFactory/ADDED";
+
+ /**
+ * The topic for the OSGi event which is sent when an adapter factory has been removed.
+ * The event contains at least the {@link #PROPERTY_ADAPTABLE_CLASSES},
+ * and {@link #PROPERTY_ADAPTER_CLASSES} poperties.
+ * @since 2.0.6
+ */
+ public static final String TOPIC_ADAPTER_FACTORY_REMOVED = "org/apache/sling/api/adapter/AdapterFactory/REMOVED";
+
+ /**
+ * The event property listing the fully qualified names of
+ * classes which can be adapted by this adapter factory (value is
+ * "adaptables"). The type of the value is a string array.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_ADAPTABLE_CLASSES = "adaptables";
+
+ /**
+ * The event property listing the fully qualified names of
+ * classes to which this factory can adapt adaptables (value is "adapters").
+ * The type of the value is a string array.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_ADAPTER_CLASSES = "adapters";
+
+ /**
+ * The name of the request attribute providing the name of the currently
+ * executing servlet (value is "sling.core.current.servletName"). This
+ * attribute is set immediately before calling the
+ * <code>Servlet.service()</code> method and reset to any previously
+ * stored value after the service method returns.
+ * @since 2.1
+ */
+ public static final String SLING_CURRENT_SERVLET_NAME = "sling.core.current.servletName";
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingException.java
new file mode 100644
index 0000000..bf632bf
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingException.java
@@ -0,0 +1,92 @@
+/*
+ * 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.api;
+
+/**
+ * The <code>SlingException</code> is the base exception used throughout the
+ * Sling API. This exception should only be thrown if there is no more specific
+ * exception defined in the Sling API for the cause and if a cause can be
+ * supplied. Otherwise the more specific exception must be used.
+ * <p>
+ * The <code>SlingException</code> is a <code>RuntimeException</code>
+ * because this exception is not intended to be caught by client code. Rather
+ * this exception (and extensions thereof) should be passed through up to the
+ * actual Sling error and exception handling.
+ */
+public class SlingException extends RuntimeException {
+
+ /**
+ * Serial Version ID for pre Java2 RMI
+ */
+ private static final long serialVersionUID = -1243027389278210618L;
+
+ /**
+ * Constructs a new Sling exception.
+ */
+ protected SlingException() {
+ super();
+ }
+
+ /**
+ * Constructs a new Sling exception with the given text. The Sling framework
+ * may use the text to write it to a log.
+ *
+ * @param text the exception text
+ */
+ protected SlingException(String text) {
+ super(text);
+ }
+
+ /**
+ * Constructs a new Sling exception when the Servlet needs to do the
+ * following:
+ * <ul>
+ * <li>throw an exception
+ * <li>include the "root cause" exception
+ * <li>include a description message
+ * </ul>
+ *
+ * @param text the exception text
+ * @param cause the root cause
+ */
+ public SlingException(String text, Throwable cause) {
+ super(text, cause);
+
+ // ensure proper JDK 1.4 exception chaining
+ if (getCause() == null) {
+ initCause(cause);
+ }
+ }
+
+ /**
+ * Constructs a new Sling exception when the Servlet needs to throw an
+ * exception. The exception's message is based on the localized message of
+ * the underlying exception.
+ *
+ * @param cause the root cause
+ */
+ protected SlingException(Throwable cause) {
+ super(cause);
+
+ // ensure proper JDK 1.4 exception chaining
+ if (getCause() == null) {
+ initCause(cause);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingHttpServletRequest.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingHttpServletRequest.java
new file mode 100644
index 0000000..d03ac88
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingHttpServletRequest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.api;
+
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.adapter.Adaptable;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.request.RequestParameterMap;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.request.RequestProgressTracker;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+/**
+ * The <code>SlingHttpServletRequest</code> defines the interface to provide
+ * client request information to a servlet.
+ * <p>
+ * <b>Request Parameters</b> Generally request parameters are transmitted as
+ * part of the URL string such as <code>GET /some/path?<b>param=value</b></code>
+ * or as request data of content type <i>application/x-www-form-urlencoded</i>
+ * or <i>multipart/form-data</i>. The Sling Framework must decode parameters
+ * transferred as request data and make them available through the various
+ * parameter accessor methods. Generally parameters transferred as
+ * <i>multipart/form-data</i> will be accessed by one of the methods returning
+ * {@link RequestParameter} instances.
+ * <p>
+ * In any case, the {@link #getReader()} and {@link #getInputStream()} methods
+ * will throw an <code>IllegalStateException</code> if called after any methods
+ * returning request parameters if the request content type is either
+ * <i>application/x-www-form-urlencoded</i> or <i>multipart/form-data</i>
+ * because the request data has already been processed.
+ * <p>
+ * Starting with Sling API 2.0.6, this interface als extends the
+ * {@link Adaptable} interface.
+ */
+public interface SlingHttpServletRequest extends HttpServletRequest, Adaptable {
+
+ /**
+ * Returns the {@link Resource} object on whose behalf the servlet acts.
+ *
+ * @return The <code>Resource</code> object of this request.
+ */
+ Resource getResource();
+
+ /**
+ * Returns the {@link ResourceResolver} which resolved the
+ * {@link #getResource() resource} of this request.
+ *
+ * @return The resource resolver
+ */
+ ResourceResolver getResourceResolver();
+
+ /**
+ * Returns the {@link RequestPathInfo} pertaining to this request.
+ *
+ * @return the request path info.
+ */
+ RequestPathInfo getRequestPathInfo();
+
+ /**
+ * Returns the value of a request parameter as a {@link RequestParameter},
+ * or <code>null</code> if the parameter does not exist.
+ * <p>
+ * This method should only be used if the parameter has only one value. If
+ * the parameter might have more than one value, use
+ * {@link #getRequestParameters(String)}.
+ * <p>
+ * If this method is used with a multivalued parameter, the value returned
+ * is equal to the first value in the array returned by
+ * <code>getRequestParameters</code>.
+ * <p>
+ * This method is a shortcut for
+ * <code>getRequestParameterMap().getValue(String)</code>.
+ *
+ * @param name a <code>String</code> specifying the name of the parameter
+ * @return a {@link RequestParameter} representing the single value of the
+ * parameter
+ * @see #getRequestParameters(String)
+ * @see RequestParameterMap#getValue(String)
+ * @throws IllegalArgumentException if name is <code>null</code>.
+ */
+ RequestParameter getRequestParameter(String name);
+
+ /**
+ * Returns an array of {@link RequestParameter} objects containing all of
+ * the values the given request parameter has, or <code>null</code> if the
+ * parameter does not exist.
+ * <p>
+ * If the parameter has a single value, the array has a length of 1.
+ * <p>
+ * This method is a shortcut for
+ * <code>getRequestParameterMap().getValues(String)</code>.
+ *
+ * @param name a <code>String</code> containing the name of the parameter
+ * the value of which is requested
+ * @return an array of {@link RequestParameter} objects containing the
+ * parameter values.
+ * @see #getRequestParameter(String)
+ * @see RequestParameterMap#getValues(String)
+ * @throws IllegalArgumentException if name is <code>null</code>.
+ */
+ RequestParameter[] getRequestParameters(String name);
+
+ /**
+ * Returns a <code>Map</code> of the parameters of this request.
+ * <p>
+ * The values in the returned <code>Map</code> are from type
+ * {@link RequestParameter} array (<code>RequestParameter[]</code>).
+ * <p>
+ * If no parameters exist this method returns an empty <code>Map</code>.
+ *
+ * @return an immutable <code>Map</code> containing parameter names as
+ * keys and parameter values as map values, or an empty
+ * <code>Map</code> if no parameters exist. The keys in the
+ * parameter map are of type String. The values in the parameter map
+ * are of type {@link RequestParameter} array (<code>RequestParameter[]</code>).
+ */
+ RequestParameterMap getRequestParameterMap();
+
+ /**
+ * Returns a <code>RequestDispatcher</code> object that acts as a wrapper
+ * for the resource located at the given path. A
+ * <code>RequestDispatcher</code> object can be used to include the
+ * resource in a response.
+ * <p>
+ * Returns <code>null</code> if a <code>RequestDispatcher</code> cannot
+ * be returned for any reason.
+ *
+ * @param path a <code>String</code> specifying the pathname to the
+ * resource. If it is relative, it must be relative against the
+ * current servlet.
+ * @param options influence the rendering of the included Resource
+ * @return a <code>RequestDispatcher</code> object that acts as a wrapper
+ * for the <code>resource</code> or <code>null</code> if an
+ * error occurs preparing the dispatcher.
+ */
+ RequestDispatcher getRequestDispatcher(String path,
+ RequestDispatcherOptions options);
+
+ /**
+ * Returns a <code>RequestDispatcher</code> object that acts as a wrapper
+ * for the resource located at the given resource. A
+ * <code>RequestDispatcher</code> object can be used to include the
+ * resource in a response.
+ * <p>
+ * Returns <code>null</code> if a <code>RequestDispatcher</code> cannot
+ * be returned for any reason.
+ *
+ * @param resource The {@link Resource} instance whose response content may
+ * be included by the returned dispatcher.
+ * @param options influence the rendering of the included Resource
+ * @return a <code>RequestDispatcher</code> object that acts as a wrapper
+ * for the <code>resource</code> or <code>null</code> if an
+ * error occurs preparing the dispatcher.
+ */
+ RequestDispatcher getRequestDispatcher(Resource resource,
+ RequestDispatcherOptions options);
+
+ /**
+ * Same as {@link #getRequestDispatcher(Resource,RequestDispatcherOptions)}
+ * but using empty options.
+ */
+ RequestDispatcher getRequestDispatcher(Resource resource);
+
+ /**
+ * Returns the named cookie from the HTTP request or <code>null</code> if
+ * no such cookie exists in the request.
+ *
+ * @param name The name of the cookie to return.
+ * @return The named cookie or <code>null</code> if no such cookie exists.
+ */
+ Cookie getCookie(String name);
+
+ /**
+ * Returns the framework preferred content type for the response. The
+ * content type only includes the MIME type, not the character set.
+ * <p>
+ * For included resources this method will returned the same string as
+ * returned by the <code>ServletResponse.getContentType()</code> without
+ * the character set.
+ *
+ * @return preferred MIME type of the response
+ */
+ String getResponseContentType();
+
+ /**
+ * Gets a list of content types which the framework accepts for the
+ * response. This list is ordered with the most preferable types listed
+ * first. The content type only includes the MIME type, not the character
+ * set.
+ * <p>
+ * For included resources this method will returned an enumeration
+ * containing a single entry which is the same string as returned by the
+ * <code>ServletResponse.getContentType()</code> without the character
+ * set.
+ *
+ * @return ordered list of MIME types for the response
+ */
+ Enumeration<String> getResponseContentTypes();
+
+ /**
+ * Returns the resource bundle for the given locale.
+ *
+ * @param locale the locale for which to retrieve the resource bundle. If
+ * this is <code>null</code>, the locale returned by
+ * {@link #getLocale()} is used to select the resource bundle.
+ * @return the resource bundle for the given locale
+ */
+ ResourceBundle getResourceBundle(Locale locale);
+
+ /**
+ * Returns the resource bundle of the given base name for the given locale.
+ *
+ * @param baseName The base name of the resource bundle to returned. If this
+ * parameter is <code>null</code>, the same resource bundle
+ * must be returned as if the {@link #getResourceBundle(Locale)}
+ * method is called.
+ * @param locale the locale for which to retrieve the resource bundle. If
+ * this is <code>null</code>, the locale returned by
+ * {@link #getLocale()} is used to select the resource bundle.
+ * @return the resource bundle for the given locale
+ */
+ ResourceBundle getResourceBundle(String baseName, Locale locale);
+
+ /**
+ * Returns the {@link RequestProgressTracker} of this request.
+ */
+ RequestProgressTracker getRequestProgressTracker();
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingHttpServletResponse.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingHttpServletResponse.java
new file mode 100644
index 0000000..f2db3bf
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingHttpServletResponse.java
@@ -0,0 +1,38 @@
+/*
+ * 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.api;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.adapter.Adaptable;
+
+/**
+ * The <code>SlingHttpServletResponse</code> defines the interface to assist a
+ * servlet in creating and sending a response to the client.
+ * <p>
+ * This interface is currently empty and merely exists to paralell the
+ * {@link SlingHttpServletRequest} interface.
+ * <p>
+ * Starting with Sling API 2.0.6, this interface als extends the
+ * {@link Adaptable} interface.
+ */
+public interface SlingHttpServletResponse extends HttpServletResponse,
+ Adaptable {
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingIOException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingIOException.java
new file mode 100644
index 0000000..13f9ee1
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingIOException.java
@@ -0,0 +1,37 @@
+/*
+ * 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.api;
+
+import java.io.IOException;
+
+/**
+ * The <code>SlingIOException</code> is a runtime exception wrapper for the
+ * Java <code>IOException</code>. This exception is used to catch an
+ * <code>IOException</code> and forward it as a runtime exception to be
+ * handled at the outermost level.
+ */
+public class SlingIOException extends SlingException {
+
+ private static final long serialVersionUID = 5278814329174799608L;
+
+ public SlingIOException(IOException cause) {
+ super(cause);
+ }
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingServletException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingServletException.java
new file mode 100644
index 0000000..834c6d0
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/SlingServletException.java
@@ -0,0 +1,37 @@
+/*
+ * 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.api;
+
+import javax.servlet.ServletException;
+
+/**
+ * The <code>SlingServletException</code> is a runtime exception wrapper for
+ * the Servlet API <code>ServletException</code>. This exception is used to
+ * catch a <code>ServletException</code> and forward it as a runtime exception
+ * to be handled at the outermost level.
+ */
+public class SlingServletException extends SlingException {
+
+ private static final long serialVersionUID = 8666411662509951915L;
+
+ public SlingServletException(ServletException cause) {
+ super(cause);
+ }
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/Adaptable.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/Adaptable.java
new file mode 100644
index 0000000..6f3f92f
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/Adaptable.java
@@ -0,0 +1,51 @@
+/*
+ * 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.api.adapter;
+
+/**
+ * The <code>Adaptable</code> interface identifies objects which can be adapted
+ * to other types or representations of the same object. For example a JCR Node
+ * based {@link org.apache.sling.api.resource.Resource} can adapt to the
+ * underlying JCR Node or a file based resource could adapt to the underlying
+ * <code>java.io.File</code>.
+ */
+public interface Adaptable {
+
+ /**
+ * Adapts the adaptable to another type.
+ * <p>
+ * Please not that it is explicitly left as an implementation detail whether
+ * each call to this method with the same <code>type</code> yields the same
+ * object or a new object on each call.
+ * <p>
+ * Implementations of this method should document their adapted types as
+ * well as their behaviour with respect to returning newly created or not
+ * instance on each call.
+ *
+ * @param <AdapterType> The generic type to which this resource is adapted
+ * to
+ * @param type The Class object of the target type, such as
+ * <code>javax.jcr.Node.class</code> or
+ * <code>java.io.File.class</code>
+ * @return The adapter target or <code>null</code> if the resource cannot
+ * adapt to the requested type
+ */
+ <AdapterType> AdapterType adaptTo(Class<AdapterType> type);
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/AdapterFactory.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/AdapterFactory.java
new file mode 100644
index 0000000..e4d5938
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/AdapterFactory.java
@@ -0,0 +1,85 @@
+/*
+ * 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.api.adapter;
+
+/**
+ * The <code>AdapterFactory</code> interface defines the API for helpers which
+ * may be provided to enhance the adaptability of adaptable objects.
+ * <p>
+ * Implementations of this interface are registered as OSGi services and are
+ * used by the {@link AdapterManager} to adapt objects on demand. The
+ * <code>AdapterFactory</code> services are not really intended to be used by
+ * clients directly.
+ * <p>
+ * The {@link AdapterManager} implementations ensures that the
+ * {@link #getAdapter(Object, Class)} method is only called for the combination
+ * of adaptable and adapter type which is allowed as per the service
+ * registration properties {@link #ADAPTABLE_CLASSES} and
+ * {@link #ADAPTER_CLASSES}.
+ */
+public interface AdapterFactory {
+
+ /**
+ * The service name to use when registering implementations of this
+ * interface as services.
+ */
+ String SERVICE_NAME = "org.apache.sling.api.adapter.AdapterFactory";
+
+ /**
+ * The service registration property listing the fully qualified names of
+ * classes which can be adapted by this adapter factory (value is
+ * "adaptables"). The "adaptable" parameters of the
+ * {@link #getAdapter(Object, Class)} method must be an instance of one of
+ * these classes for this factory to be able to adapt the object.
+ */
+ String ADAPTABLE_CLASSES = "adaptables";
+
+ /**
+ * The service registration property listing the fully qualified names of
+ * classes to which this factory can adapt adaptables (value is "adapters").
+ */
+ String ADAPTER_CLASSES = "adapters";
+
+ /**
+ * Adapt the given object to the adaptable type. The adaptable object is
+ * guaranteed to be an instance of one of the classes listed in the
+ * {@link #ADAPTABLE_CLASSES} services registration property. The type
+ * parameter is one of the classes listed in the {@link #ADAPTER_CLASSES}
+ * service registration properties.
+ * <p>
+ * This method may return <code>null</code> if the adaptable object cannot
+ * be adapted to the adapter (target) type for any reason. In this case, the
+ * implementation should log a message to the log facility noting the cause
+ * for not being able to adapt.
+ * <p>
+ * Note that the <code>adaptable</code> object is not required to implement
+ * the <code>Adaptable</code> interface, though most of the time this method
+ * is called by means of calling the {@link Adaptable#adaptTo(Class)}
+ * method.
+ *
+ * @param <AdapterType> The generic type of the adapter (target) type.
+ * @param adaptable The object to adapt to the adapter type.
+ * @param type The type to which the object is to be adapted.
+ * @return The adapted object or <code>null</code> if this factory instance
+ * cannot adapt the object.
+ */
+ <AdapterType> AdapterType getAdapter(Object adaptable,
+ Class<AdapterType> type);
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/AdapterManager.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/AdapterManager.java
new file mode 100644
index 0000000..75e911d
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/AdapterManager.java
@@ -0,0 +1,60 @@
+/*
+ * 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.api.adapter;
+
+/**
+ * The <code>AdapterManager</code> defines the service interface for a manager
+ * for object adaption. The adapter manager coordinates the registered
+ * {@link AdapterFactory} services on behalf of clients wishing to adapt objects
+ * to other types. One such client is the {@link SlingAdaptable} class, which
+ * uses the implementation of this bundle to adapt "itself".
+ * <p>
+ * Clients may either extend from the {@link SlingAdaptable} class or access the
+ * <code>AdapterManager</code> service from the OSGi service registry to adapt
+ * objects to other types.
+ * <p>
+ * This interface is not intended to be implemented by clients.
+ */
+public interface AdapterManager {
+
+ /**
+ * The name under which this service is registered with the OSGi service
+ * registry.
+ */
+ String SERVICE_NAME = "org.apache.sling.api.adapter.AdapterManager";
+
+ /**
+ * Returns an adapter object of the requested <code>AdapterType</code> for
+ * the given <code>adaptable</code> object.
+ * <p>
+ * The <code>adaptable</code> object may be any non-<code>null</code> object
+ * and is not required to implement the <code>Adaptable</code> interface.
+ *
+ * @param <AdapterType> The generic type of the adapter (target) type.
+ * @param adaptable The object to adapt to the adapter type.
+ * @param type The type to which the object is to be adapted.
+ * @return The adapted object or <code>null</code> if no factory exists to
+ * adapt the <code>adaptable</code> to the <code>AdapterType</code>
+ * or if the <code>adaptable</code> cannot be adapted for any other
+ * reason.
+ */
+ <AdapterType> AdapterType getAdapter(Object adaptable,
+ Class<AdapterType> type);
+
+}
\ No newline at end of file
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/SlingAdaptable.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/SlingAdaptable.java
new file mode 100644
index 0000000..31bc4cd
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/SlingAdaptable.java
@@ -0,0 +1,115 @@
+/*
+ * 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.api.adapter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The <code>SlingAdaptable</code> class is an (abstract) default implementation
+ * of the <code>Adaptable</code> interface. It just uses the default
+ * {@link AdapterManager} implemented to adapt itself to the requested type.
+ * <p>
+ * Extensions of this class may overwrite the {@link #adaptTo(Class)} method
+ * using their own knowledge of adapters and should call this base class
+ * implementation to fall back for other types.
+ *
+ * @since 2.2
+ */
+public abstract class SlingAdaptable implements Adaptable {
+
+ /** The adapter manager used for adapting the synthetic resource. */
+ private static volatile AdapterManager ADAPTER_MANAGER;
+
+ /**
+ * Sets the global {@link AdapterManager} to be used by this class.
+ * <p>
+ * This method is intended to only be called by the {@link AdapterManager}
+ * wanting to register itself for use. Currently only a single adapter
+ * manager is supported by this class.
+ *
+ * @param adapterMgr The {@link AdapterManager} to be globally used.
+ */
+ public static void setAdapterManager(final AdapterManager adapterMgr) {
+ ADAPTER_MANAGER = adapterMgr;
+ }
+
+ /**
+ * Unsets the global {@link AdapterManager}.
+ * <p>
+ * This method is intended to only be called by the {@link AdapterManager}
+ * wanting to unregister itself. Currently only a single adapter manager is
+ * supported by this class.
+ *
+ * @param adapterMgr The {@link AdapterManager} to be unset. If this is not
+ * the same as currently registered this method has no effect.
+ */
+ public static void unsetAdapterManager(final AdapterManager adapterMgr) {
+ if (ADAPTER_MANAGER == adapterMgr) {
+ ADAPTER_MANAGER = null;
+ }
+ }
+
+ /**
+ * Cached adapters per type.
+ * <p>
+ * This map is created on demand by the {@link #adaptTo(Class)} method as a
+ * regular <code>HashMap</code>. This means, that extensions of this class
+ * are intended to be short-lived to not hold on to objects and classes for
+ * too long.
+ */
+ private Map<Class<?>, Object> adaptersCache;
+
+ /**
+ * Calls into the registered {@link AdapterManager} to adapt this object to
+ * the desired <code>type</code>.
+ * <p>
+ * This method implements a cache of adapters to improve performance. That
+ * is repeated calls to this method with the same class will result in the
+ * same object to be returned.
+ *
+ * @param <AdapterType> The generic type to which this resource is adapted
+ * to
+ * @param type The Class object of the target type, such as
+ * <code>javax.jcr.Node.class</code> or
+ * <code>java.io.File.class</code>
+ * @return The adapter target or <code>null</code> if the resource cannot
+ * adapt to the requested type
+ */
+ @SuppressWarnings("unchecked")
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ AdapterType result = null;
+ synchronized ( this ) {
+ if ( adaptersCache != null ) {
+ result = (AdapterType) adaptersCache.get(type);
+ }
+ if ( result == null ) {
+ final AdapterManager mgr = ADAPTER_MANAGER;
+ result = (mgr == null ? null : mgr.getAdapter(this, type));
+ if ( result != null ) {
+ if ( adaptersCache == null ) {
+ adaptersCache = new HashMap<Class<?>, Object>();
+ }
+ adaptersCache.put(type, result);
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/package-info.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/package-info.java
new file mode 100644
index 0000000..4ef8c98
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/adapter/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("2.2")
+package org.apache.sling.api.adapter;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/auth/Authenticator.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/auth/Authenticator.java
new file mode 100644
index 0000000..4d6a5b0
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/auth/Authenticator.java
@@ -0,0 +1,133 @@
+/*
+ * 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.api.auth;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * The <code>Authenticator</code> interface defines the service interface which
+ * may be used by applications to enforce requests to be authenticated (see
+ * {@link #login(HttpServletRequest, HttpServletResponse)}) or to end enforced
+ * authentication (see {@link #logout(HttpServletRequest, HttpServletResponse)}
+ * ). As such this service may be looked at as the functionality to enable
+ * applications to log users in and out.
+ * <p>
+ * A very simple login script (using ESP here) could be implemented like this:
+ *
+ * <pre>
+ * var auth = sling.getService(org.apache.sling.commons.auth.Authenticator);
+ * if (auth != null) {
+ * try {
+ * auth.login(request, response);
+ * return; // we are done here
+ * } catch (e) {
+ * // probably NoAuthenticationHandler exception
+ * }
+ * }
+ * // Authenticator service is missing or no AuthenticationHandler
+ * ... do whatever you want to for error handling ...
+ * </pre>
+ * <p>
+ * Likewise implementing a logout script (ESP, too) is equally simple:
+ *
+ * <pre>
+ * if (request.authType) {
+ * // not logged in at all, no need to logout
+ * } else {
+ * var auth = sling.getService(org.apache.sling.commons.auth.Authenticator);
+ * if (auth != null) {
+ * auth.logout(request, response);
+ * } else {
+ * // handle the case of no Authenticator service to logout with
+ * }
+ * }
+ * </pre>
+ * <p>
+ * This interface is not intended to be implemented by applications but may be
+ * used to initiate the authentication process form a request processing servlet
+ * or script.
+ *
+ * @since 1.0 (Sling API Bundle 2.1.0)
+ */
+public interface Authenticator {
+
+ /**
+ * The name under which this service is registered.
+ */
+ static final String SERVICE_NAME = Authenticator.class.getName();
+
+ /**
+ * Name of the request attribute which may be set by the application to
+ * indicate to the {@link #login(HttpServletRequest, HttpServletResponse)}
+ * method to which resource access should actually be authenticated. If this
+ * request attribute is not set or is the empty string, the
+ * {@link #login(HttpServletRequest, HttpServletResponse)} method uses the
+ * request path info (<code>HttpServletRequest.getPathInfo()</code>) method
+ * to find the resource to which to authenticate access.
+ * <p>
+ * This request attribute can be used by frontend servlets/scripts which
+ * call into {@link #login(HttpServletRequest, HttpServletResponse)} on
+ * behalf of users.
+ */
+ static final String LOGIN_RESOURCE = "resource";
+
+ /**
+ * Tries to login a request user for the current request.
+ * <p>
+ * To identify the resource to which access should be authenticated the
+ * <code>{@link #LOGIN_RESOURCE resource}</code> request attribute is
+ * considered. If the request attribute is not set the request path info (
+ * <code>HttpServletRequest.getPathInfo()</code>) is used.
+ * <p>
+ * This method must be called on an uncommitted response since the
+ * implementation may want to reset the response to start the authentication
+ * process with a clean response. If the response is already committed an
+ * <code>IllegalStateException</code> is thrown.
+ * <p>
+ * After this method has finished, request processing should be terminated
+ * and the response be considered committed and finished unless the
+ * {@link NoAuthenticationHandlerException} exception is thrown in which
+ * case no response has been sent to the client.
+ *
+ * @param request The object representing the client request.
+ * @param response The object representing the response to the client.
+ * @throws NoAuthenticationHandlerException If the service cannot find a way
+ * to authenticate a request user.
+ * @throws IllegalStateException If the response has already been committed.
+ */
+ void login(HttpServletRequest request, HttpServletResponse response);
+
+ /**
+ * Logs out if the current request is authenticated.
+ * <p>
+ * This method must be called on an uncommitted response since the
+ * implementation may want to reset the response to restart the
+ * authentication process with a clean response. If the response is already
+ * committed an <code>IllegalStateException</code> is thrown.
+ * <p>
+ * After this method has finished, request processing should be terminated
+ * and the response be considered committed and finished.
+ *
+ * @param request The object representing the client request.
+ * @param response The object representing the response to the client.
+ * @throws IllegalStateException If the response has already been committed.
+ */
+ void logout(HttpServletRequest request, HttpServletResponse response);
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/auth/NoAuthenticationHandlerException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/auth/NoAuthenticationHandlerException.java
new file mode 100644
index 0000000..6acd9f3
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/auth/NoAuthenticationHandlerException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.api.auth;
+
+import org.apache.sling.api.SlingException;
+
+/**
+ * The <code>NoAuthenticationHandlerException</code> is thrown to indicate that
+ * the
+ * {@link Authenticator#login(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+ * method could not find a way to authenticate the request user.
+ * <p>
+ * This exception is thrown without a message. The caller of the
+ * {@link Authenticator#login(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+ * method called is expected to immediately handle this exception and not to
+ * forward it up the call chain.
+ * <p>
+ * This exception is not intended to be thrown by client code but is used by the
+ * {@link Authenticator} implementation.
+ *
+ * @since 1.0 (Sling API Bundle 2.1.0)
+ */
+@SuppressWarnings("serial")
+public class NoAuthenticationHandlerException extends SlingException {
+
+ public NoAuthenticationHandlerException() {
+ super();
+ }
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/auth/package-info.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/auth/package-info.java
new file mode 100644
index 0000000..10128c4
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/auth/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("1.0")
+package org.apache.sling.api.auth;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/package-info.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/package-info.java
new file mode 100644
index 0000000..bca1562
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("2.2")
+package org.apache.sling.api;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RecursionTooDeepException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RecursionTooDeepException.java
new file mode 100644
index 0000000..0df98a0
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RecursionTooDeepException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.api.request;
+
+import org.apache.sling.api.SlingException;
+
+/**
+ * The <code>RecursionTooDeepException</code> is thrown by the Sling
+ * implementation if to many recursive content inclusions take place. The limit
+ * of recursive inclusions is implementation dependent.
+ */
+public class RecursionTooDeepException extends SlingException {
+
+ private static final long serialVersionUID = 776668636261012142L;
+
+ /**
+ * Creates a new instance of this class reporting the exception occurred
+ * while trying to include the output for rendering the resource at the
+ * given path.
+ * <p>
+ * The resource path is the actual message of the exception.
+ *
+ * @param resourcePath The path of the resource whose output inclusion
+ * causes this exception.
+ */
+ public RecursionTooDeepException(String resourcePath) {
+ super(resourcePath);
+ }
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestDispatcherOptions.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestDispatcherOptions.java
new file mode 100644
index 0000000..63705f4
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestDispatcherOptions.java
@@ -0,0 +1,186 @@
+/*
+ * 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.api.request;
+
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+/**
+ * <code>RequestDispatcherOptions</code> are used in the
+ * {@link org.apache.sling.api.SlingHttpServletRequest#getRequestDispatcher(org.apache.sling.api.resource.Resource, RequestDispatcherOptions)}
+ * method, to give more control on some aspects of the include/forward
+ * mechanism. Typical use cases include:
+ * <ul>
+ * <li> Forcing a resource type, to render a Resource in a specific way, like
+ * for example <em>render myself in a suitable way for a navigation box</em>.
+ * </li>
+ * <li> Adding selectors when including a Resource, like for example <em>add
+ * a "teaser" selector to the request that I'm including here</em>.
+ * </li>
+ * </ul>
+ * This class currently only inherits from Map, and defines some constants for
+ * well-known options.
+ */
+public class RequestDispatcherOptions extends HashMap<String, String> {
+
+ private static final long serialVersionUID = -9081782403304877746L;
+
+ /**
+ * When dispatching, use the value provided by this option as the resource
+ * type, instead of the one defined by the
+ * {@link org.apache.sling.api.resource.Resource}.
+ */
+ public static final String OPT_FORCE_RESOURCE_TYPE = "forceResourceType";
+
+ /**
+ * When dispatching, replace {@link RequestPathInfo} selectors by the value
+ * provided by this option. If this value contains an empty string, all
+ * original selectors are removed.
+ */
+ public static final String OPT_REPLACE_SELECTORS = "replaceSelectors";
+
+ /**
+ * When dispatching, add the value provided by this option to the
+ * {@link RequestPathInfo} selectors.
+ */
+ public static final String OPT_ADD_SELECTORS = "addSelectors";
+
+ /**
+ * When dispatching, replace the {@link RequestPathInfo} suffix by the value
+ * provided by this option
+ */
+ public static final String OPT_REPLACE_SUFFIX = "replaceSuffix";
+
+ /**
+ * Creates an instance with no options set.
+ */
+ public RequestDispatcherOptions() {
+ }
+
+ /**
+ * Creates a new instances setting options by parsing the given
+ * <code>options</code> string as follows:
+ * <ul>
+ * <li>If the string is empty or <code>null</code> no options are set.</li>
+ * <li>If the string neither contains a comma nor an equals sign, the
+ * string is assumed to be a resource type. Hence a
+ * <code>RequestDispatcherOptions</code> object is created with the
+ * {@link RequestDispatcherOptions#OPT_FORCE_RESOURCE_TYPE} field set to the
+ * string.</li>
+ * <li>Otherwise the string is assumed to be a comma separated list of name
+ * value pairs where the equals sign is used to separate the name from its
+ * value. Hence a <code>RequestDispatcherOptions</code> object is created
+ * from the name value pair list.</li>
+ * </ul>
+ *
+ * @param options The options to set.
+ */
+ public RequestDispatcherOptions(String options) {
+
+ if (options != null && options.length() > 0) {
+ if (options.indexOf(',') < 0 && options.indexOf('=') < 0) {
+ setForceResourceType(options.trim());
+ } else {
+ final StringTokenizer tk = new StringTokenizer(options, ",");
+ while (tk.hasMoreTokens()) {
+ String entry = tk.nextToken();
+ int equals = entry.indexOf('=');
+ if (equals > 0 && equals < entry.length() - 1) {
+ put(entry.substring(0, equals).trim(), entry.substring(
+ equals + 1).trim());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the {@link #OPT_FORCE_RESOURCE_TYPE} option to the given
+ * <code>resourceType</code> if not <code>null</code>.
+ */
+ public void setForceResourceType(String resourceType) {
+ if (resourceType != null) {
+ put(OPT_FORCE_RESOURCE_TYPE, resourceType);
+ }
+ }
+
+ /**
+ * Returns the {@link #OPT_FORCE_RESOURCE_TYPE} option or <code>null</code>
+ * if not set.
+ */
+ public String getForceResourceType() {
+ return get(OPT_FORCE_RESOURCE_TYPE);
+ }
+
+ /**
+ * Sets the {@link #OPT_ADD_SELECTORS} option to the given
+ * <code>additionalSelectors</code> if not <code>null</code>.
+ */
+ public void setAddSelectors(String additionalSelectors) {
+ if (additionalSelectors != null) {
+ put(OPT_ADD_SELECTORS, additionalSelectors);
+ }
+ }
+
+ /**
+ * Returns the {@link #OPT_ADD_SELECTORS} option or <code>null</code> if
+ * not set.
+ */
+ public String getAddSelectors() {
+ return get(OPT_ADD_SELECTORS);
+ }
+
+ /**
+ * Sets the {@link #OPT_REPLACE_SELECTORS} option to the given
+ * <code>replaceSelectors</code> if not <code>null</code>.
+ * If this value contains an empty string, all
+ * original selectors are removed.
+ */
+ public void setReplaceSelectors(String replaceSelectors) {
+ if (replaceSelectors != null) {
+ put(OPT_REPLACE_SELECTORS, replaceSelectors);
+ }
+ }
+
+ /**
+ * Returns the {@link #OPT_REPLACE_SELECTORS} option or <code>null</code>
+ * if not set.
+ */
+ public String getReplaceSelectors() {
+ return get(OPT_REPLACE_SELECTORS);
+ }
+
+ /**
+ * Sets the {@link #OPT_REPLACE_SUFFIX} option to the given
+ * <code>replaceSuffix</code> if not <code>null</code>.
+ */
+ public void setReplaceSuffix(String replaceSuffix) {
+ if (replaceSuffix != null) {
+ put(OPT_REPLACE_SUFFIX, replaceSuffix);
+ }
+ }
+
+ /**
+ * Returns the {@link #OPT_REPLACE_SUFFIX} option or <code>null</code> if
+ * not set.
+ */
+ public String getReplaceSuffix() {
+ return get(OPT_REPLACE_SUFFIX);
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestParameter.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestParameter.java
new file mode 100644
index 0000000..6cd43d5
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestParameter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.api.request;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * The <code>RequestParameter</code> class represents a single parameter sent
+ * with the client request. Instances of this class are returned by the
+ * {@link org.apache.sling.api.SlingHttpServletRequest#getRequestParameter(String)},
+ * {@link org.apache.sling.api.SlingHttpServletRequest#getRequestParameters(String)} and
+ * {@link org.apache.sling.api.SlingHttpServletRequest#getRequestParameterMap()} method.
+ *
+ * @see org.apache.sling.api.SlingHttpServletRequest#getRequestParameter(String)
+ * @see org.apache.sling.api.SlingHttpServletRequest#getRequestParameters(String)
+ * @see org.apache.sling.api.SlingHttpServletRequest#getRequestParameterMap()
+ */
+public interface RequestParameter {
+
+ /**
+ * Determines whether or not this instance represents a simple form field or
+ * an uploaded file.
+ *
+ * @return <code>true</code> if the instance represents a simple form
+ * field; <code>false</code> if it represents an uploaded file.
+ */
+ boolean isFormField();
+
+ /**
+ * Returns the content type passed by the browser or <code>null</code> if
+ * not defined.
+ *
+ * @return The content type passed by the browser or <code>null</code> if
+ * not defined.
+ */
+ String getContentType();
+
+ /**
+ * Returns the size in bytes of the parameter.
+ *
+ * @return The size in bytes of the parameter.
+ */
+ long getSize();
+
+ /**
+ * Returns the contents of the parameter as an array of bytes.
+ *
+ * @return The contents of the parameter as an array of bytes.
+ */
+ byte[] get();
+
+ /**
+ * Returns an InputStream that can be used to retrieve the contents of the
+ * file.
+ *
+ * @return An InputStream that can be used to retrieve the contents of the
+ * file.
+ * @throws IOException if an error occurs.
+ */
+ InputStream getInputStream() throws IOException;
+
+ /**
+ * Returns the original filename in the client's filesystem, as provided by
+ * the browser (or other client software). In most cases, this will be the
+ * base file name, without path information. However, some clients, such as
+ * the Opera browser, do include path information.
+ *
+ * @return The original filename in the client's filesystem.
+ */
+ String getFileName();
+
+ /**
+ * Returns the contents of the parameter as a String, using the default
+ * character encoding. This method uses {@link #get()} to retrieve the
+ * contents of the item.
+ *
+ * @return The contents of the parameter, as a string.
+ */
+ String getString();
+
+ /**
+ * Returns the contents of the parameter as a String, using the specified
+ * encoding. This method uses link {@link #get()} to retrieve the contents
+ * of the item.
+ *
+ * @param encoding The character encoding to use.
+ * @return The contents of the parameter, as a string.
+ * @throws UnsupportedEncodingException if the requested character encoding
+ * is not available.
+ */
+ String getString(String encoding) throws UnsupportedEncodingException;
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestParameterMap.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestParameterMap.java
new file mode 100644
index 0000000..71fd76c
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestParameterMap.java
@@ -0,0 +1,38 @@
+/*
+ * 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.api.request;
+
+import java.util.Map;
+
+
+/**
+ * The <code>RequestParameterMap</code> encapsulates all request parameters of
+ * a request.
+ */
+public interface RequestParameterMap extends Map<String, RequestParameter[]> {
+
+ /** Returns all values for the named parameter or null if none
+ */
+ RequestParameter[] getValues(String name);
+
+ /** Returns the first value for the named parameter or null if none
+ */
+ RequestParameter getValue(String name);
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestPathInfo.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestPathInfo.java
new file mode 100644
index 0000000..ff21cdd
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestPathInfo.java
@@ -0,0 +1,218 @@
+/*
+ * 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.api.request;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * Sling breaks the request URI into four parts: the path itself, optional
+ * dot-separated selectors and extension that follow it, and an optional path
+ * suffix.
+ * <p id="decomp">
+ * <b>Decomposition of a Request URL</b>
+ * <ol>
+ * <li>{@link #getResourcePath() content path} - The longest substring of the request
+ * URI resolving to a {@link org.apache.sling.api.resource.Resource} object such
+ * that the content path is either the complete request URI or the next
+ * character in the request URI after the content path is either a dot (<code>.</code>)
+ * or a slash (<code>/</code>).
+ * <li>{@link #getSelectors() selectors} - If the first character in the
+ * request URI after the content path is a dot, the string after the dot upto
+ * but not including the last dot before the next slash character or the end of
+ * the request URI. If the content path spans the complete request URI or if a
+ * slash follows the content path in the request, then no seletors exist. If
+ * only one dot follows the content path before the end of the request URI or
+ * the next slash, no selectors exist. The selectors are available as
+ * {@link #getSelectorString() a single string} and as an
+ * {@link #getSelectors() array of strings}, which is the selector string
+ * splitted on dots.
+ * <li>{@link #getExtension() extension} - The string after the last dot after
+ * the content path in the request uri but before the end of the request uri or
+ * the next slash after the content path in the request uri. If the content path
+ * spans the complete request URI or a slash follows the content path in the
+ * request URI, the extension is empty.
+ * <li>{@link #getSuffix() suffix path} - If the request URI contains a slash
+ * character after the content path and optional selectors and extension, the
+ * path starting with the slash upto the end of the request URI is the suffix
+ * path.
+ * </ol>
+ * <p>
+ * Examples: <table>
+ * <tr>
+ * <th>URI</th>
+ * <th>Content Path</th>
+ * <th>Selectors</th>
+ * <th>Extension</th>
+ * <th>Suffix</th>
+ * </tr>
+ * <tr>
+ * <td>/a/b</td>
+ * <td>/a/b</td>
+ * <td>null</td>
+ * <td>null</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b.html</td>
+ * <td>/a/b</td>
+ * <td>null</td>
+ * <td>html</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b.s1.html</td>
+ * <td>/a/b</td>
+ * <td>s1</td>
+ * <td>html</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b.s1.s2.html</td>
+ * <td>/a/b</td>
+ * <td>s1.s2</td>
+ * <td>html</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b/c/d</td>
+ * <td>/a/b</td>
+ * <td>null</td>
+ * <td>null</td>
+ * <td>/c/d</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b.html/c/d</td>
+ * <td>/a/b</td>
+ * <td>null</td>
+ * <td>html</td>
+ * <td>/c/d</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b.s1.html/c/d</td>
+ * <td>/a/b</td>
+ * <td>s1</td>
+ * <td>html</td>
+ * <td>/c/d</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b.s1.s2.html/c/d</td>
+ * <td>/a/b</td>
+ * <td>s1.s2</td>
+ * <td>html</td>
+ * <td>/c/d</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b/c/d.s.txt</td>
+ * <td>/a/b</td>
+ * <td>null</td>
+ * <td>null</td>
+ * <td>/c/d.s.txt</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b.html/c/d.s.txt</td>
+ * <td>/a/b</td>
+ * <td>null</td>
+ * <td>html</td>
+ * <td>/c/d.s.txt</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b.s1.html/c/d.s.txt</td>
+ * <td>/a/b</td>
+ * <td>s1</td>
+ * <td>html</td>
+ * <td>/c/d.s.txt</td>
+ * </tr>
+ * <tr>
+ * <td>/a/b.s1.s2.html/c/d.s.txt</td>
+ * <td>/a/b</td>
+ * <td>s1.s2</td>
+ * <td>html</td>
+ * <td>/c/d.s.txt</td>
+ * </tr>
+ * </table>
+ */
+public interface RequestPathInfo {
+
+ /**
+ * Return the "resource path" part of the URL, what comes before selectors,
+ * extension and suffix. This string is part of the request URL and need not
+ * be equal to the {@link org.apache.sling.api.resource.Resource#getPath()}.
+ * Rather it is equal to the
+ * {@link org.apache.sling.api.resource.ResourceMetadata#RESOLUTION_PATH resolution path metadata property}
+ * of the resource.
+ */
+ String getResourcePath();
+
+ /**
+ * Returns the extension from the URL or <code>null</code> if the request
+ * URL does not contain an extension.
+ * <p>
+ * Decomposition of the request URL is defined in the <a
+ * href="#decomp">Decomposition of a Request URL</a> above.
+ *
+ * @return The extension from the request URL.
+ */
+ String getExtension();
+
+ /**
+ * Returns the selectors decoded from the request URL as string. Returns
+ * <code>null</code> if the request has no selectors.
+ * <p>
+ * Decomposition of the request URL is defined in the <a
+ * href="#decomp">Decomposition of a Request URL</a> above.
+ *
+ * @see #getSelectors()
+ */
+ String getSelectorString();
+
+ /**
+ * Returns the selectors decoded from the request URL as an array of
+ * strings. This array is derived from the
+ * {@link #getSelectorString() selector string} by splitting the string on
+ * dots. Returns an empty array if the request has no selectors.
+ * <p>
+ * Decomposition of the request URL is defined in the <a
+ * href="#decomp">Decomposition of a Request URL</a> above.
+ *
+ * @see #getSelectorString()
+ */
+ String[] getSelectors();
+
+ /**
+ * Returns the suffix part of the URL or <code>null</code> if the request
+ * URL does not contain a suffix.
+ * <p>
+ * Decomposition of the request URL is defined in the <a
+ * href="#decomp">Decomposition of a Request URL</a> above.
+ *
+ * @return The suffix part of the request URL.
+ */
+ String getSuffix();
+
+ /**
+ * Returns the resource addressed by the suffix or null if the request does
+ * not have a suffix or the suffix does not address an accessible resource.
+ * <p>
+ * The suffix is expected to be the absolute path to the resource suitable
+ * as an argument to the
+ * {@link org.apache.sling.api.resource.ResourceResolver#getResource(String)}
+ * method.
+ *
+ * @since 2.3 (Sling API 2.3.2)
+ */
+ Resource getSuffixResource();
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestProgressTracker.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestProgressTracker.java
new file mode 100644
index 0000000..daa71d3
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestProgressTracker.java
@@ -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.api.request;
+
+import java.io.PrintWriter;
+import java.util.Iterator;
+
+/**
+ * The <code>RequestProgressTracker</code> class provides the functionality to
+ * track the progress of request processing. Instances of this class are
+ * provided through the
+ * {@link org.apache.sling.api.SlingHttpServletRequest#getRequestProgressTracker()}
+ * method.
+ * <p>
+ * The following functionality is provided:
+ * <ol>
+ * <li>Track the progress of request processing through the
+ * {@link #log(String)} and {@link #log(String, Object...)} methods.
+ * <li>Ability to measure and track processing times of parts of request
+ * processing through the {@link #startTimer(String)},
+ * {@link #logTimer(String)} and {@link #logTimer(String, String, Object...)}
+ * methods.
+ * <li>Dumping the recording messages through the {@link #dump(PrintWriter)}
+ * method.
+ * <li>Return the log messages through the {@link #getMessages()} method.
+ * </ol>
+ * <p>
+ * <b>Tracking Request Processing</b>
+ * <p>
+ * As the request being processed, certain steps may be tracked by calling
+ * either of the <code>log</code> methods. A tracking entry consists of a time
+ * stamp managed by this class and a tracking message noting the actual step
+ * being tracked.
+ * <p>
+ * <b>Timing Processing Steps</b>
+ * </p>
+ * Certain steps during request processing may need to be timed in that the time
+ * required for processing should be recorded. Instances of this class maintain
+ * a map of named timers. Each timer is started (initialized or reset) by
+ * calling the {@link #startTimer(String)} method. This method just records the
+ * starting time of the named timer and adds a tracking entry with the timer
+ * name as the message.
+ * <p>
+ * To record the number of milliseconds elapsed since a timer has been started,
+ * the {@link #logTimer(String)} or {@link #logTimer(String, String, Object...)}
+ * method may be called. This method logs a tracking entry with a message
+ * consisting of the name of the timer and the number of milliseconds elapsed
+ * since the timer was last {@link #startTimer(String) started}. The
+ * <code>logTimer</code> methods may be called multiple times to record
+ * several timed steps.
+ * <p>
+ * Calling the {@link #startTimer(String)} method with the name of timer which
+ * already exists, resets the start time of the named timer to the current
+ * system time.
+ * <p>
+ * <b>Retrieving Tracking Entries</b>
+ * <p>
+ * The {@link #dump(PrintWriter)} method may be used to write the tracking
+ * entries to the given <code>PrintWriter</code> to for example log them in a
+ * HTML comment. Alternatively the tracking entries may be retrieved as an
+ * iterator of messages through the {@link #getMessages()} method. The
+ * formatting of the tracking entries is implementation specific.
+ */
+public interface RequestProgressTracker {
+
+ /** Creates an entry with the given message */
+ void log(String message);
+
+ /**
+ * Creates an entry with a message constructed from the given
+ * <code>MessageFormat</code> format evaluated using the given formatting
+ * arguments.
+ */
+ void log(String format, Object... args);
+
+ /**
+ * Starts a named timer. If a timer of the same name already exists, it is
+ * reset to the current time.
+ */
+ void startTimer(String timerName);
+
+ /**
+ * Logs an entry with the message set to the name of the timer and the
+ * number of milliseconds elapsed since the timer start.
+ */
+ void logTimer(String timerName);
+
+ /**
+ * Logs an entry with the message constructed from the given
+ * <code>MessageFormat</code> pattern evaluated using the given arguments
+ * and the number of milliseconds elapsed since the timer start.
+ */
+ void logTimer(String timerName, String format, Object... args);
+
+ /**
+ * Returns an <code>Iterator</code> of tracking entries.
+ * If there are no messages <code>null</code> is returned.
+ */
+ Iterator<String> getMessages();
+
+ /**
+ * Dumps the process timer entries to the given writer, one entry per line.
+ */
+ void dump(PrintWriter writer);
+
+ /**
+ * Call this when done processing the request - all calls except the first
+ * one are ignored
+ */
+ void done();
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestUtil.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestUtil.java
new file mode 100644
index 0000000..cdd3721
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/RequestUtil.java
@@ -0,0 +1,190 @@
+/*
+ * 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.api.request;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.Servlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.sling.api.servlets.HttpConstants;
+import org.apache.sling.api.SlingHttpServletRequest;
+
+/**
+ * @since 2.1
+ */
+public class RequestUtil {
+
+ /**
+ * Parses a header of the form:
+ *
+ * <pre>
+ * Header = Token { "," Token } .
+ * Token = name { ";" Parameter } .
+ * Paramter = name [ "=" value ] .
+ * </pre>
+ *
+ * "," and ";" are not allowed within name and value
+ *
+ * @param value
+ * @return A Map indexed by the Token names where the values are Map
+ * instances indexed by parameter name
+ */
+ public static Map<String, Map<String, String>> parserHeader(String value) {
+ Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>();
+ String[] tokens = value.split(",");
+ for (int i = 0; i < tokens.length; i++) {
+ String[] parameters = tokens[i].split(";");
+ String name = parameters[0].trim();
+ Map<String, String> parMap;
+ if (parameters.length > 0) {
+ parMap = new HashMap<String, String>();
+ for (int j = 1; j < parameters.length; j++) {
+ String[] content = parameters[j].split("=", 2);
+ if (content.length > 1) {
+ parMap.put(content[0].trim(), content[1].trim());
+ } else {
+ parMap.put(content[0].trim(), content[0].trim());
+ }
+ }
+ } else {
+ parMap = Collections.emptyMap();
+ }
+ result.put(name, parMap);
+ }
+ return result;
+ }
+
+ /**
+ * Parses an <code>Accept-*</code> header of the form:
+ *
+ * <pre>
+ * Header = Token { "," Token } .
+ * Token = name { ";" "q" [ "=" value ] } .
+ * Paramter = .
+ * </pre>
+ *
+ * "," and ";" are not allowed within name and value
+ *
+ * @param value
+ * @return A Map indexed by the Token names where the values are
+ * <code>Double</code> instances providing the value of the
+ * <code>q</code> parameter.
+ */
+ public static Map<String, Double> parserAcceptHeader(String value) {
+ Map<String, Double> result = new HashMap<String, Double>();
+ String[] tokens = value.split(",");
+ for (int i = 0; i < tokens.length; i++) {
+ String[] parameters = tokens[i].split(";");
+ String name = parameters[0];
+ Double qVal = new Double(1.0);
+ if (parameters.length > 1) {
+ for (int j = 1; j < parameters.length; j++) {
+ String[] content = parameters[j].split("=", 2);
+ if (content.length > 1 && "q".equals(content[0])) {
+ try {
+ qVal = Double.valueOf(content[1]);
+ } catch (NumberFormatException nfe) {
+ // don't care
+ }
+ }
+ }
+ }
+ if (qVal != null) {
+ result.put(name, qVal);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Utility method to return a name for the given servlet. This method
+ * applies the following algorithm to find a non-<code>null</code>,
+ * non-empty name:
+ * <ol>
+ * <li>If the servlet has a servlet config, the servlet name from the
+ * servlet config is taken.
+ * <li>Otherwise check the servlet info
+ * <li>Otherwise use the fully qualified name of the servlet class
+ * </ol>
+ */
+ public static String getServletName(Servlet servlet) {
+ String name = null;
+
+ if (servlet.getServletConfig() != null) {
+ name = servlet.getServletConfig().getServletName();
+ }
+ if (name == null || name.length() == 0) {
+ name = servlet.getServletInfo();
+ }
+ if (name == null || name.length() == 0) {
+ name = servlet.getClass().getName();
+ }
+
+ return name;
+ }
+
+ /**
+ * Sets the named request attribute to the new value and returns the
+ * previous value.
+ *
+ * @param request The request object whose attribute is to be set.
+ * @param name The name of the attribute to be set.
+ * @param value The new value of the attribute. If this is <code>null</code>
+ * the attribte is actually removed from the request.
+ * @return The previous value of the named request attribute or
+ * <code>null</code> if it was not set.
+ */
+ public static Object setRequestAttribute(HttpServletRequest request,
+ String name, Object value) {
+ Object oldValue = request.getAttribute(name);
+ if (value == null) {
+ request.removeAttribute(name);
+ } else {
+ request.setAttribute(name, value);
+ }
+ return oldValue;
+ }
+
+ /**
+ * Checks if the request contains a if-last-modified-since header and if the the
+ * request's underlying resource has a jcr:lastModified property. if the properties were modified
+ * before the header a 304 is sent otherwise the response last modified header is set.
+ * @param req the request
+ * @param resp the response
+ * @return <code>true</code> if the response was set
+ */
+ public static boolean handleIfModifiedSince(SlingHttpServletRequest req, HttpServletResponse resp){
+ boolean responseSet=false;
+ long lastModified=req.getResource().getResourceMetadata().getModificationTime();
+ if (lastModified!=-1){
+ long modifiedTime = lastModified/1000; //seconds
+ long ims = req.getDateHeader(HttpConstants.HEADER_IF_MODIFIED_SINCE)/1000; //seconds
+ if (modifiedTime <= ims) {
+ resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ responseSet=true;
+ }
+ resp.setDateHeader(HttpConstants.HEADER_LAST_MODIFIED, lastModified);
+ }
+ return responseSet;
+ }
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/ResponseUtil.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/ResponseUtil.java
new file mode 100644
index 0000000..709d04f
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/ResponseUtil.java
@@ -0,0 +1,110 @@
+/*
+ * 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.api.request;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Response-related utilities
+ * @since 2.1
+ */
+public class ResponseUtil {
+
+ private static class XmlEscapingWriter extends Writer {
+ private final Writer target;
+
+ XmlEscapingWriter(Writer target) {
+ this.target = target;
+ }
+
+ @Override
+ public void close() throws IOException {
+ target.close();
+ }
+
+ @Override
+ public void flush() throws IOException {
+ target.flush();
+ }
+
+ @Override
+ public void write(char[] buffer, int offset, int length) throws IOException {
+ for(int i = offset; i < offset + length; i++) {
+ write(buffer[i]);
+ }
+ }
+
+ @Override
+ public void write(char[] cbuf) throws IOException {
+ write(cbuf, 0, cbuf.length);
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ if(c == '&') {
+ target.write("&");
+ } else if(c == '<') {
+ target.write("<");
+ } else if(c == '>') {
+ target.write(">");
+ } else {
+ target.write(c);
+ }
+ }
+
+ @Override
+ public void write(String str, int off, int len) throws IOException {
+ write(str.toCharArray(), off, len);
+ }
+
+ @Override
+ public void write(String str) throws IOException {
+ write(str.toCharArray());
+ }
+ }
+
+ /** Escape xml text */
+ public static String escapeXml(String input) {
+ if(input == null) {
+ return null;
+ }
+
+ final StringBuilder b = new StringBuilder(input.length());
+ for(int i = 0;i < input.length(); i++) {
+ final char c = input.charAt(i);
+ if(c == '&') {
+ b.append("&");
+ } else if(c == '<') {
+ b.append("<");
+ } else if(c == '>') {
+ b.append(">");
+ } else {
+ b.append(c);
+ }
+ }
+ return b.toString();
+ }
+
+ /** Return a Writer that writes escaped XML text to target
+ */
+ public static Writer getXmlEscapingWriter(Writer target) {
+ return new XmlEscapingWriter(target);
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/SlingRequestEvent.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/SlingRequestEvent.java
new file mode 100644
index 0000000..d12c029
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/SlingRequestEvent.java
@@ -0,0 +1,73 @@
+/*
+ * 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.api.request;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+
+/**
+ * represents an event published by the Sling engine while
+ * dispatching a request.
+ *
+ * @see org.apache.sling.api.request.SlingRequestListener
+ * @since 2.1.0
+*/
+public class SlingRequestEvent {
+
+ private final ServletContext sc;
+ private final ServletRequest request;
+ private final EventType type;
+
+ /**
+ * type of the event
+ */
+ public enum EventType { EVENT_INIT, EVENT_DESTROY };
+
+ public SlingRequestEvent (ServletContext sc, ServletRequest request, EventType type ) {
+ this.sc = sc;
+ this.request = request;
+ this.type = type;
+ }
+
+ /**
+ * Gets the actual servlet context object as <code>ServletContext</code>
+ * @return the actual servlet context.
+ */
+ public ServletContext getServletContext() {
+ return sc;
+ }
+
+ /**
+ * Gets the actual request object as <code>ServletRequest</code>
+ * @return the actual request object as <code>ServletRequest</code>
+ */
+ public ServletRequest getServletRequest() {
+ return request;
+ }
+
+ /**
+ * get the type of the event, eg. EVENT_INIT or EVENT_DESTROY
+ * @return the type of the event as <code>EventType</code>,
+ * eg. EVENT_INIT or EVENT_DESTROY
+ */
+ public EventType getType () {
+ return type;
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/SlingRequestListener.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/SlingRequestListener.java
new file mode 100644
index 0000000..9d847d9
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/SlingRequestListener.java
@@ -0,0 +1,43 @@
+/*
+ * 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.api.request;
+
+/**
+ * Implementations of this service interface receive notifications about
+ * changes to Sling request of the Sling application they are part of.
+ * To receive notification events, the implementation class must be
+ * registered as an OSGi service with the service name
+ * org.apache.sling.api.request.SlingRequestListener.
+ */
+public interface SlingRequestListener {
+
+ String SERVICE_NAME = "org.apache.sling.api.request.SlingRequestListener";
+
+ /**
+ * This method is called from the Sling application for every
+ * <code>EventType</code> appearing during the dispatching of
+ * a Sling request
+ *
+ * @param sre the object representing the event
+ *
+ * @see org.apache.sling.api.request.SlingRequestEvent.EventType
+ */
+ void onEvent( SlingRequestEvent sre );
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/TooManyCallsException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/TooManyCallsException.java
new file mode 100644
index 0000000..22534c9
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/TooManyCallsException.java
@@ -0,0 +1,45 @@
+/*
+ * 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.api.request;
+
+import org.apache.sling.api.SlingException;
+
+/**
+ * The <code>TooManyCallsException</code> is thrown by the Sling implementation
+ * if to many inclusions have been called for during a single request. The limit
+ * of inclusions is implementation dependent.
+ */
+public class TooManyCallsException extends SlingException {
+
+ private static final long serialVersionUID = -8725296173002395104L;
+
+ /**
+ * Creates an instance of this exception naming the Servlet (or Script)
+ * whose call caused this exception to be thrown.
+ * <p>
+ * The servlet name is the actual message of the exception.
+ *
+ * @param servletName The name of the Servlet (or Script) causing this
+ * exception.
+ */
+ public TooManyCallsException(String servletName) {
+ super(servletName);
+ }
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/package-info.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/package-info.java
new file mode 100644
index 0000000..9229ac2
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/request/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("2.3")
+package org.apache.sling.api.request;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/AbstractResource.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/AbstractResource.java
new file mode 100644
index 0000000..c37cd68
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/AbstractResource.java
@@ -0,0 +1,117 @@
+/*
+ * 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.api.resource;
+
+import java.util.Iterator;
+
+import org.apache.sling.api.adapter.SlingAdaptable;
+
+/**
+ * The <code>AbstractResource</code> is an abstract implementation of the
+ * {@link Resource} interface.
+ * <p>
+ * Implementations of the {@link Resource} interface are strongly encouraged to
+ * either extend from this class or the {@link ResourceWrapper} class instead of
+ * implementing the {@link Resource} from the ground up. This will ensure to
+ * always be able to support new methods that might be introduced in the
+ * {@link Resource} interface in the future.
+ *
+ * @since 2.1.0
+ */
+public abstract class AbstractResource
+ extends SlingAdaptable
+ implements Resource {
+
+ /**
+ * Returns the name of this resource.
+ * <p>
+ * This method is implemented as a pure string operation by calling the
+ * {@link ResourceUtil#getName(String)} method with the path of this
+ * resource.
+ */
+ public String getName() {
+ return ResourceUtil.getName(getPath());
+ }
+
+ /**
+ * Returns the parent resource of this resource.
+ * <p>
+ * This method is implemented by getting the parent resource path first
+ * calling the {@link ResourceUtil#getParent(String)} method and then to
+ * retrieve that resource from the resource resolver.
+ */
+ public Resource getParent() {
+ final String parentPath = ResourceUtil.getParent(getPath());
+ if (parentPath == null) {
+ return null;
+ }
+ return getResourceResolver().getResource(parentPath);
+ }
+
+ /**
+ * Returns the indicated child of this resource.
+ * <p>
+ * This method is implemented calling the
+ * {@link ResourceResolver#getResource(Resource, String)} method. As such
+ * the <code>relPath</code> argument may even be an absolute path or a path
+ * containing relative path segments <code>.</code> (current resource) and
+ * <code>..</code> (parent resource).
+ * <p>
+ * Implementations should not generally overwrite this method without
+ * calling this base class implementation.
+ */
+ public Resource getChild(String relPath) {
+ return getResourceResolver().getResource(this, relPath);
+ }
+
+ /**
+ * Returns an iterator on the direct child resources.
+ * <p>
+ * This method is implemented calling the
+ * {@link ResourceResolver#listChildren(Resource)} method.
+ * <p>
+ * Implementations should not generally overwrite this method without
+ * calling this base class implementation.
+ */
+ public Iterator<Resource> listChildren() {
+ return getResourceResolver().listChildren(this);
+ }
+
+ /**
+ * @see org.apache.sling.api.resource.Resource#getChildren()
+ */
+ public Iterable<Resource> getChildren() {
+ return new Iterable<Resource>() {
+
+ public Iterator<Resource> iterator() {
+ return listChildren();
+ }
+ };
+ }
+
+ /**
+ * Returns <code>true</code> if this resource is of the given resource type
+ * or if any of the super resource types equals the given resource type.
+ * <p>
+ * This method delegates to {@link ResourceResolver#isResourceType(Resource, String)}
+ */
+ public boolean isResourceType(final String resourceType) {
+ return this.getResourceResolver().isResourceType(this, resourceType);
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/AbstractResourceVisitor.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/AbstractResourceVisitor.java
new file mode 100644
index 0000000..804e878
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/AbstractResourceVisitor.java
@@ -0,0 +1,61 @@
+/*
+ * 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.api.resource;
+
+import java.util.Iterator;
+
+/**
+ * The <code>AbstractResourceVisitor</code> helps in traversing a
+ * resource tree by decoupling the actual traversal code
+ * from application code. Concrete subclasses should implement
+ * the {@link ResourceVisitor#visit(Resource)} method.
+ *
+ * @since 2.2
+ */
+public abstract class AbstractResourceVisitor {
+
+ /**
+ * Visit the given resource and all its descendants.
+ * @param res The resource
+ */
+ public void accept(final Resource res) {
+ if (res != null) {
+ this.visit(res);
+ this.traverseChildren(res.listChildren());
+ }
+ }
+
+ /**
+ * Visit the given resources.
+ * @param children The list of resources
+ */
+ protected void traverseChildren(final Iterator<Resource> children) {
+ while (children.hasNext()) {
+ final Resource child = children.next();
+
+ accept(child);
+ }
+ }
+
+ /**
+ * Implement this method to do actual work on the resources.
+ * @param res The resource
+ */
+ protected abstract void visit(final Resource res);
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/AttributableResourceProvider.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/AttributableResourceProvider.java
new file mode 100644
index 0000000..ec2d4d3
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/AttributableResourceProvider.java
@@ -0,0 +1,64 @@
+/*
+ * 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.api.resource;
+
+import java.util.Collection;
+
+/**
+ * The attributes provider is an extensions of a {@link ResourceProvider}.
+ * It allows to add attributes to the set of available attributes from a
+ * resource resolver.
+ *
+ * This extension is supported for services directly implementing the
+ * {@link ResourceProvider} interface and {@link ResourceProvider}s
+ * returned through a {@link ResourceProviderFactory}.
+ *
+ * @see ResourceResolver#getAttribute(String)
+ * @see ResourceResolver#getAttributeNames()
+ *
+ * @since 2.2
+ */
+public interface AttributableResourceProvider extends ResourceProvider {
+
+ /**
+ * Returns a collection of attribute names whose value can be retrieved
+ * calling the {@link #getAttribute(ResourceResolver, String)} method.
+ *
+ * @return A collection of attribute names or <code>null</code>
+ * @throws IllegalStateException if this resource provider has already been
+ * closed.
+ */
+ Collection<String> getAttributeNames(ResourceResolver resolver);
+
+ /**
+ * Returns the value of the given resource provider attribute or <code>null</code>
+ * if the attribute is not set or not visible (as e.g. security
+ * sensitive attributes).
+ *
+ * @param name
+ * The name of the attribute to access
+ * @return The value of the attribute or <code>null</code> if the attribute
+ * is not set or not accessible.
+ * @throws NullPointerException
+ * if <code>name</code> is <code>null</code>.
+ * @throws IllegalStateException
+ * if this resource provider has already been closed.
+ */
+ Object getAttribute(ResourceResolver resolver, String name);
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/DynamicResourceProvider.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/DynamicResourceProvider.java
new file mode 100644
index 0000000..823ab8e
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/DynamicResourceProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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.api.resource;
+
+/**
+ * A dynamic resource provider is an extension of a resource provider which
+ * is only supported if the resource provider has been created through
+ * a {@link ResourceProviderFactory}.
+ *
+ * A dynamic resource provider supports access to systems where the
+ * connection to the system is dynamic and might go away (due to network
+ * changes, updates etc.).
+ *
+ * The {@link #isLive()} method can be called to check whether the
+ * provider is still active.
+ * The {@link #close()} method should be called to free any resources
+ * held by this resource provider.
+ *
+ * @see ResourceProviderFactory#getResourceProvider(java.util.Map)
+ * @see ResourceProviderFactory#getAdministrativeResourceProvider(java.util.Map)
+ *
+ * @since 2.2
+ */
+public interface DynamicResourceProvider extends ResourceProvider {
+
+ /**
+ * Returns <code>true</code> if this resource provider has not been closed
+ * yet and can still be used.
+ * <p>
+ * This method will never throw an exception
+ * even after the resource provider has been closed
+ *
+ * @return <code>true</code> if the resource provider has not been closed
+ * yet and is still active.. Once the resource provider has been closed
+ * or is not active anymore, this method returns <code>false</code>.
+ */
+ boolean isLive();
+
+ /**
+ * Close the resource provider.
+ * Once the resource provider is not used anymore, it should be closed with
+ * this method.
+ */
+ void close();
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/LoginException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/LoginException.java
new file mode 100644
index 0000000..5b0ad66
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/LoginException.java
@@ -0,0 +1,74 @@
+/*
+ * 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.api.resource;
+
+/**
+ * Exception thrown by
+ * <code>{@link ResourceResolverFactory#getAdministrativeResourceResolver(java.util.Map)}</code>
+ * ,
+ * <code>{@link ResourceResolverFactory#getResourceResolver(java.util.Map)}</code>
+ * , and <code>{@link ResourceResolver#clone(java.util.Map)}</code> if a resource
+ * resolver cannot be created because the credential data is not valid.
+ *
+ * @since 2.1
+ */
+public class LoginException extends Exception {
+
+ private static final long serialVersionUID = -5896615185390272299L;
+
+ /**
+ * Constructs a new instance of this class with <code>null</code> as its
+ * detail message.
+ */
+ public LoginException() {
+ super();
+ }
+
+ /**
+ * Constructs a new instance of this class with the specified detail
+ * message.
+ *
+ * @param message the detail message. The detail message is saved for later
+ * retrieval by the {@link #getMessage()} method.
+ */
+ public LoginException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new instance of this class with the specified detail message
+ * and root cause.
+ *
+ * @param message the detail message. The detail message is saved for later
+ * retrieval by the {@link #getMessage()} method.
+ * @param rootCause root failure cause
+ */
+ public LoginException(String message, Throwable rootCause) {
+ super(message, rootCause);
+ }
+
+ /**
+ * Constructs a new instance of this class with the specified root cause.
+ *
+ * @param rootCause root failure cause
+ */
+ public LoginException(Throwable rootCause) {
+ super(rootCause);
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ModifiableValueMap.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ModifiableValueMap.java
new file mode 100644
index 0000000..b97e9d3
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ModifiableValueMap.java
@@ -0,0 +1,61 @@
+/*
+ * 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.api.resource;
+
+/**
+ * The <code>ModifiableValueMap</code> is an extension
+ * of the {@link ValueMap} which allows to modify and
+ * persist properties. All changes to this map are
+ * stored in the transient layer of the resource resolver
+ * or more precisely in the transient layer of the
+ * resource provider managing this resource.
+ * <p>
+ * Once {@link ResourceResolver#commit()} is called, the
+ * changes are finally persisted.
+ * <p>
+ * The modifiable value map is only changeable through
+ * one of these methods
+ * <ul>
+ * <li>{@link #put(String, Object)}</li>
+ * <li>{@link #putAll(java.util.Map)}</li>
+ * <li>{@link #remove(Object)}</li>
+ * </ul>
+ * <p>
+ * The map is not modifiable through the collections provided
+ * by
+ * <ul>
+ * <li>{@link #entrySet()}</li>
+ * <li>{@link #keySet()}</li>
+ * <li>{@link #values()}</li>
+ * </ul>
+ * And it can't be modified by these methods:
+ * <ul>
+ * <li>{@link #clear()}</li>
+ * </ul>
+ * <p>
+ *
+ * A modifiable value map should value {@link ResourceResolver#PROPERTY_RESOURCE_TYPE}
+ * to set the resource type of a resource.
+ *
+ * @since 2.2
+ */
+public interface ModifiableValueMap extends ValueMap {
+
+ // just a marker
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ModifyingResourceProvider.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ModifyingResourceProvider.java
new file mode 100644
index 0000000..a586529
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ModifyingResourceProvider.java
@@ -0,0 +1,100 @@
+/*
+ * 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.api.resource;
+
+import java.util.Map;
+
+/**
+ * A modifying resource provider is an extension of a resource provider which
+ * is only supported if the resource provider has been created through
+ * a {@link ResourceProviderFactory}.
+ *
+ * A modifying resource provider allows to create, update, and delete
+ * resources. Update is handled through {@link ModifiableValueMap}.
+ *
+ * All changes should be kept in a transient store until {@link #commit(ResourceResolver)}
+ * is called. {@link #revert(ResourceResolver)} discards all transient changes.
+ *
+ * If the modifying resource provider needs to clean up resources when it
+ * is discarded like removing objects from the transient state which are
+ * not committed etc., it should also implement the {@link DynamicResourceProvider}
+ * interface.
+ *
+ * @see ResourceProviderFactory#getResourceProvider(java.util.Map)
+ * @see ResourceProviderFactory#getAdministrativeResourceProvider(java.util.Map)
+ *
+ * @since 2.2.0
+ */
+public interface ModifyingResourceProvider extends ResourceProvider {
+
+ /**
+ * Create a new resource at the given path.
+ * The new resource is put into the transient space of this provider
+ * until {@link #commit(ResourceResolver)} is called.
+ *
+ * A resource provider should value {@link ResourceResolver#PROPERTY_RESOURCE_TYPE}
+ * to set the resource type of a resource.
+ *
+ * @param resolver The current resource resolver.
+ * @param path The resource path.
+ * @param properties Optional properties
+ * @return The new resource.
+ *
+ * @throws PersistenceException If anything fails
+ */
+ Resource create(ResourceResolver resolver, String path, Map<String, Object> properties)
+ throws PersistenceException;
+
+ /**
+ * Delete the resource at the given path.
+ * This change is kept in the transient space of this provider
+ * until {@link #commit(ResourceResolver)} is called.
+ *
+ * @param resolver The current resource resolver.
+ * @param path The resource path.
+ *
+ * @throws PersistenceException If anything fails
+ */
+ void delete(ResourceResolver resolver, String path)
+ throws PersistenceException;
+
+ /**
+ * Revert all transient changes: create, delete and updates.
+ *
+ * @param resolver The current resource resolver.
+ */
+ void revert(ResourceResolver resolver);
+
+ /**
+ * Commit all transient changes: create, delete and updates
+ *
+ * @param resolver The current resource resolver.
+ *
+ * @throws PersistenceException If anything fails
+ */
+ void commit(ResourceResolver resolver)
+ throws PersistenceException;
+
+ /**
+ * Are there any transient changes?
+ *
+ * @param resolver The current resource resolver.
+ */
+ boolean hasChanges(ResourceResolver resolver);
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/NonExistingResource.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/NonExistingResource.java
new file mode 100644
index 0000000..c3f70b6
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/NonExistingResource.java
@@ -0,0 +1,57 @@
+/*
+ * 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.api.resource;
+
+/**
+ * Simple helper class representing nonexisting resources.
+ *
+ * This is an utility class to <em>create</em> non existing resources.
+ * It is not meant to be used to check if a resource is a non existing
+ * resource. Use the {@link ResourceUtil#isNonExistingResource(Resource)}
+ * method instead (or check the resource type yourself). The reason
+ * for this is that this resource might be wrapped by other resource
+ * implementations like resource decorators etc.
+ */
+public final class NonExistingResource extends SyntheticResource {
+
+ /**
+ * Create a new non existing resource.
+ * @param resourceResolver The resource resolver.
+ * @param resourceURI The path of the resource.
+ */
+ public NonExistingResource(final ResourceResolver resourceResolver,
+ final String resourceURI) {
+ super(resourceResolver, resourceURI, RESOURCE_TYPE_NON_EXISTING);
+ }
+
+ /**
+ * @see org.apache.sling.api.resource.SyntheticResource#getResourceType()
+ */
+ public final String getResourceType() {
+ // overwrite to prevent overwriting of this method in extensions of
+ // this class because the specific resource type is the marker of a
+ // NonExistingResource
+ return RESOURCE_TYPE_NON_EXISTING;
+ }
+
+ public String toString() {
+ // overwrite to only list the class name and path, type is irrelevant
+ return getClass().getSimpleName() + ", path=" + getPath();
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/PersistableValueMap.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/PersistableValueMap.java
new file mode 100644
index 0000000..f95fb36
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/PersistableValueMap.java
@@ -0,0 +1,45 @@
+/*
+ * 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.api.resource;
+
+/**
+ * The <code>PersistableValueMap</code> is an extension
+ * of the {@link ValueMap} which allows to modify and
+ * persist the properties.
+ *
+ * Note, that each time you call {@link Resource#adaptTo(Class)}
+ * you get a new map instance which does not share modified
+ * properties with other representations.
+ *
+ * @deprecated Use the {@link ModifiableValueMap} instead.
+ */
+@Deprecated
+public interface PersistableValueMap extends ValueMap {
+
+ /**
+ * Persists the changes.
+ * @throws PersistenceException If the changes can't be persisted.
+ */
+ void save() throws PersistenceException;
+
+ /**
+ * Reset the changes.
+ */
+ void reset();
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/PersistenceException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/PersistenceException.java
new file mode 100644
index 0000000..7fc8354
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/PersistenceException.java
@@ -0,0 +1,95 @@
+/*
+ * 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.api.resource;
+
+import java.io.IOException;
+
+/**
+ * This exception will be thrown during the try to persist
+ * changes to a {@link PersistableValueMap}, a
+ * {@link ModifiableValueMap#update()} or
+ * the {@link ResourceResolver}.
+ */
+public class PersistenceException extends IOException {
+
+ private static final long serialVersionUID = 2454225989618227698L;
+
+ /** Optional resource path. */
+ private final String resourcePath;
+
+ /** Optional property name. */
+ private final String propertyName;
+
+ /**
+ * Create a new persistence exception.
+ */
+ public PersistenceException() {
+ this(null, null, null, null);
+ }
+
+ /**
+ * Create a new persistence exception.
+ * @param msg Exception message.
+ */
+ public PersistenceException(final String msg) {
+ this(msg, null, null, null);
+ }
+
+ /**
+ * Create a new persistence exception.
+ * @param msg Exception message.
+ * @param cause Exception cause.
+ */
+ public PersistenceException(final String msg, final Throwable cause) {
+ this(msg, cause, null, null);
+ }
+
+ /**
+ * Create a new persistence exception.
+ * @param msg Exception message.
+ * @param cause Exception cause.
+ */
+ public PersistenceException(final String msg,
+ final Throwable cause,
+ final String resourcePath,
+ final String propertyName) {
+ super(msg);
+ initCause(cause);
+ this.resourcePath = resourcePath;
+ this.propertyName = propertyName;
+ }
+
+ /**
+ * Get the resource path related to this exception.
+ * @return The resource path or <code>null</code>
+ * @since 2.2
+ */
+ public String getResourcePath() {
+ return this.resourcePath;
+ }
+
+ /**
+ * Get the property name related to this exception.
+ * @return The property name or <code>null</code>
+ * @since 2.2
+ */
+ public String getPropertyName() {
+ return this.propertyName;
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/QueriableResourceProvider.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/QueriableResourceProvider.java
new file mode 100644
index 0000000..ef3fdee
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/QueriableResourceProvider.java
@@ -0,0 +1,97 @@
+/*
+ * 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.api.resource;
+
+import java.util.Iterator;
+
+
+/**
+ * A queriable resource provider is an extension of a resource provider.
+ *
+ * This extension is supported for services directly implementing the
+ * {@link ResourceProvider} interface and {@link ResourceProvider}s
+ * returned through a {@link ResourceProviderFactory}.
+ *
+ * @since 2.2.0
+ */
+public interface QueriableResourceProvider extends ResourceProvider {
+
+ /**
+ * The name of the service registration property containing the supported
+ * languages of the resource provider (value is "provider.query.languages").
+ * If the resource provider is delivered by a {@link ResourceProviderFactory}
+ * this property should be declared on the factory.
+ */
+ String LANGUAGES = "provider.query.languages";
+
+ /**
+ * Searches for resources using the given query formulated in the given
+ * language.
+ * <p>
+ * The semantic meaning of the query and language depend on the actual
+ * implementation and storage used for the resources. For JCR repository
+ * being used as storage, the query and language parameters are used to
+ * create a JCR <code>Query</code> through the <code>QueryManager</code>.
+ * The result returned is then based on the <code>NodeIterator</code>
+ * provided by the query result.
+ *
+ * @param query The query string to use to find the resources.
+ * @param language The language in which the query is formulated.
+ * @return An <code>Iterator</code> of {@link Resource} objects matching the
+ * query. If no resources match, <code>null</code> might be
+ * returned instead of an empty iterator.
+ * @throws QuerySyntaxException If the query is not syntactically correct
+ * according to the query language indicator or if the query
+ * language is not supported as specified in {@link #LANGUAGES}.
+ * @throws org.apache.sling.api.SlingException If an error occurs querying
+ * for the resources.
+ * @throws IllegalStateException if this resource provider has already been
+ * closed.
+ */
+ Iterator<Resource> findResources(ResourceResolver resolver, String query, String language);
+
+ /**
+ * Queries the storage using the given query formulated in the given
+ * language.
+ * <p>
+ * The semantic meaning of the query and language depend on the actual
+ * implementation and storage used for the resources. For JCR repository
+ * being used as storage, the query and language parameters are used to
+ * create a JCR <code>Query</code> through the <code>QueryManager</code>.
+ * The result returned is then based on the <code>RowIterator</code>
+ * provided by the query result. The map returned for each row is indexed by
+ * the column name and the column value is the JCR <code>Value</code> object
+ * converted into the respective Java object, such as <code>Boolean</code>
+ * for a value of property type <em>Boolean</em>.
+ *
+ * @param query The query string to use to find the resources.
+ * @param language The language in which the query is formulated.
+ * @return An <code>Iterator</code> of <code>Map</code> instances providing
+ * access to the query result. If no resources match, <code>null</code>
+ * might be returned instead of an empty iterator.
+ * @throws QuerySyntaxException If the query is not syntactically correct
+ * according to the query language indicator or if the query
+ * language is not supported as specified in {@link #LANGUAGES}.
+ * @throws org.apache.sling.api.SlingException If an error occurs querying
+ * for the resources.
+ * @throws IllegalStateException if this resource provider has already been
+ * closed.
+ */
+ Iterator<ValueMap> queryResources(ResourceResolver resolver, String query, String language);
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/QuerySyntaxException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/QuerySyntaxException.java
new file mode 100644
index 0000000..947c807
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/QuerySyntaxException.java
@@ -0,0 +1,59 @@
+/*
+ * 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.api.resource;
+
+import org.apache.sling.api.SlingException;
+
+/**
+ * The <code>QuerySyntaxException</code> is thrown by the
+ * {@link ResourceResolver#findResources(String, String)} and
+ * {@link ResourceResolver#queryResources(String, String)} methods if the query
+ * syntax is wrong or the requested query language is not available.
+ */
+public class QuerySyntaxException extends SlingException {
+
+ private static final long serialVersionUID = -6529624886228517646L;
+
+ private final String query;
+
+ private final String language;
+
+ public QuerySyntaxException(String message, String query, String language) {
+ super(message);
+
+ this.query = query;
+ this.language = language;
+ }
+
+ public QuerySyntaxException(String message, String query, String language,
+ Throwable cause) {
+ super(message, cause);
+
+ this.query = query;
+ this.language = language;
+ }
+
+ public String getQuery() {
+ return query;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/RefreshableResourceProvider.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/RefreshableResourceProvider.java
new file mode 100644
index 0000000..51347cc
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/RefreshableResourceProvider.java
@@ -0,0 +1,44 @@
+/*
+ * 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.api.resource;
+
+
+
+/**
+ * A resource provider might return the state when it was created and not
+ * update to the latest state.
+ * If the provider supports updating to the latest state, it should
+ * implement this method.
+ *
+ * This interface is only supported if the provider has been create through
+ * a {@link ResourceProviderFactory}.
+ *
+ * @see ResourceProviderFactory#getResourceProvider(java.util.Map)
+ * @see ResourceProviderFactory#getAdministrativeResourceProvider(java.util.Map)
+ *
+ * @since 2.3.0
+ */
+public interface RefreshableResourceProvider extends ResourceProvider {
+
+ /**
+ * The provider is updated to reflect the latest state.
+ * Resources which have changes pending are not discarded.
+ */
+ void refresh();
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/Resource.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/Resource.java
new file mode 100644
index 0000000..9d30734
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/Resource.java
@@ -0,0 +1,158 @@
+/*
+ * 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.api.resource;
+
+import java.util.Iterator;
+
+import org.apache.sling.api.adapter.Adaptable;
+
+/**
+ * Resources are pieces of content on which Sling acts
+ * <p>
+ * The <code>Resource</code> is also an {@link Adaptable} to get adapters to
+ * other types. A JCR based resource might support adapting to the JCR Node on
+ * which the resource is based.
+ * <p>
+ * Implementor's Note: It is recommended to not implement this interface
+ * directly. Rather consider either extending from {@link AbstractResource} or
+ * {@link ResourceWrapper}. This will make sure your implementation will not be
+ * suffering from missing method problems should the Sling Resource API be
+ * extended in the future.
+ */
+public interface Resource extends Adaptable {
+
+ /**
+ * The special resource type for resource instances representing nonexisting
+ * resources (value is "sling:nonexisting"). This resource type is used by
+ * {@link ResourceResolver} instances to mark a resource which could not
+ * actually be resolved.
+ *
+ * @see #getResourceType()
+ * @see ResourceUtil#isNonExistingResource(Resource)
+ * @see ResourceResolver#resolve(javax.servlet.http.HttpServletRequest,
+ * String)
+ */
+ String RESOURCE_TYPE_NON_EXISTING = "sling:nonexisting";
+
+ /**
+ * Returns the absolute path of this resource in the resource tree.
+ */
+ String getPath();
+
+ /**
+ * Returns the name of this resource. The name of a resource is the last
+ * segment of the {@link #getPath() path}.
+ *
+ * @since 2.1.0
+ */
+ String getName();
+
+ /**
+ * Returns the parent resource or <code>null</code> if this resource
+ * represents the root of the resource tree.
+ *
+ * @since 2.1.0
+ */
+ Resource getParent();
+
+ /**
+ * Returns an iterator of the direct children of this resource.
+ * <p>
+ * This method is a convenience and returns exactly the same resources as
+ * calling <code>getResourceResolver().listChildren(resource)</code>.
+ *
+ * @since 2.1.0
+ * @see ResourceResolver#listChildren(Resource)
+ */
+ Iterator<Resource> listChildren();
+
+ /**
+ * Returns an iterable of the direct children of this resource.
+ * <p>
+ * This method is a convenience and returns exactly the same resources as
+ * calling <code>getResourceResolver().getChildren(resource)</code>.
+ *
+ * @since 2.2.0
+ * @see ResourceResolver#getChildren(Resource)
+ */
+ Iterable<Resource> getChildren();
+
+ /**
+ * Returns the child at the given relative path of this resource or
+ * <code>null</code> if no such child exists.
+ * <p>
+ * This method is a convenience and returns exactly the same resources as
+ * calling <code>getResourceResolver().getResource(resource, relPath)</code>.
+ *
+ * @since 2.1.0
+ * @see ResourceResolver#getResource(Resource, String)
+ */
+ Resource getChild(String relPath);
+
+ /**
+ * The resource type is meant to point to rendering/processing scripts,
+ * editing dialogs, etc. It is usually a path in the repository, where
+ * scripts and other tools definitions are found, but the
+ * {@link ResourceResolver} is free to set this to any suitable value such
+ * as the primary node type of the JCR node from which the resource is
+ * created.
+ * <p>
+ * If the resource instance represents a resource which is not actually
+ * existing, this method returns {@link #RESOURCE_TYPE_NON_EXISTING}.
+ */
+ String getResourceType();
+
+ /**
+ * Returns the super type of the resource if the resource defines its
+ * own super type. Otherwise <code>null</code> is returned.
+ * A resource might return a resource super type to overwrite the
+ * resource type hierarchy.
+ * If a client is interested in the effective resource super type
+ * of a resource, it should call {@link ResourceResolver#getParentResourceType(Resource)}.
+ */
+ String getResourceSuperType();
+
+ /**
+ * Returns <code>true</code> if the resource type or any of the resource's
+ * super type(s) equals the given resource type.
+ *
+ * @param resourceType The resource type to check this resource against.
+ * @return <code>true</code> if the resource type or any of the resource's
+ * super type(s) equals the given resource type. <code>false</code>
+ * is also returned if <code>resourceType</code> is
+ * <code>null</code>.
+ * @since 2.1.0
+ */
+ boolean isResourceType(String resourceType);
+
+ /**
+ * Returns the metadata of this resource. The concrete data contained in the
+ * {@link ResourceMetadata} object returned is implementation specific
+ * except for the {@link ResourceMetadata#RESOLUTION_PATH} property which is
+ * required to be set to the part of the request URI used to resolve the
+ * resource.
+ *
+ * @see ResourceMetadata
+ */
+ ResourceMetadata getResourceMetadata();
+
+ /**
+ * Returns the {@link ResourceResolver} from which this resource has been
+ * retrieved.
+ */
+ ResourceResolver getResourceResolver();
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceDecorator.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceDecorator.java
new file mode 100644
index 0000000..752533f
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceDecorator.java
@@ -0,0 +1,60 @@
+/*
+ * 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.api.resource;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Optional service to decorate {@link Resource}s returned by
+ * the {@link ResourceResolver}.
+ * Typical use cases for a decorator are
+ * - overwrite resource type/resource super type (for example
+ * based on the resource path)
+ * - add metadata
+ *
+ * @since 2.1
+ */
+public interface ResourceDecorator {
+
+ /**
+ * Decorate a resource.
+ * If the service decorates the resource it should return
+ * the new resource. If the service does not want to decorate
+ * the resource, it should return the original resource.
+ * Returning <code>null</code> is considered the same as
+ * returning the original resource.
+ * @param resource The resource to decorate
+ * @return The decorated resource, the original resource or null.
+ */
+ Resource decorate(Resource resource);
+
+ /**
+ * Decorate a resource.
+ * If the service decorates the resource it should return
+ * the new resource. If the service does not want to decorate
+ * the resource, it should return the original resource.
+ * Returning <code>null</code> is considered the same as
+ * returning the original resource.
+ * @param resource The resource to decorate
+ * @param request The current request.
+ * @return The decorated resource, the original resource or null.
+ *
+ * @deprecated since 2.3.0 (and JCR Resource 2.1.0), this method will not be invoked.
+ */
+ @Deprecated
+ Resource decorate(Resource resource, HttpServletRequest request);
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceMetadata.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceMetadata.java
new file mode 100644
index 0000000..825d08d
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceMetadata.java
@@ -0,0 +1,372 @@
+/*
+ * 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.api.resource;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The <code>ResourceMetadata</code> interface defines the API for the
+ * metadata of a Sling {@link Resource}. Essentially the resource's metadata is
+ * just a map of objects indexed by string keys.
+ * <p>
+ * The actual contents of the meta data map is implementation specific with the
+ * exception of the {@link #RESOLUTION_PATH sling.resolutionPath} property which
+ * must be provided by all implementations and contain the part of the request
+ * URI used to resolve the resource. The type of this property value is defined
+ * to be <code>String</code>.
+ * <p>
+ * Note, that the prefix <em>sling.</em> to key names is reserved for the
+ * Sling implementation.
+ *
+ * Once a resource is returned by the {@link ResourceResolver}, the resource
+ * metadata is made read-only and therefore can't be changed by client code!
+ */
+public class ResourceMetadata extends HashMap<String, Object> {
+
+ private static final long serialVersionUID = 4692666752269523738L;
+
+ /**
+ * The name of the required property providing the part of the request URI
+ * which was used to the resolve the resource to which the meta data
+ * instance belongs (value is "sling.resolutionPath").
+ */
+ public static final String RESOLUTION_PATH = "sling.resolutionPath";
+
+ /**
+ * The name of the required property providing the part of the request URI
+ * which was not used to the resolve the resource to which the meta data
+ * instance belongs (value is "sling.resolutionPathInfo"). The value of this
+ * property concatenated to the value of the
+ * {@link #RESOLUTION_PATH sling.resolutionPath} property returns the
+ * original request URI leading to the resource.
+ * <p>
+ * This property is optional. If missing, it should be assumed equal to an
+ * empty string.
+ *
+ * @since 2.0.4
+ */
+ public static final String RESOLUTION_PATH_INFO = "sling.resolutionPathInfo";
+
+ /**
+ * The name of the optional property providing the content type of the
+ * resource if the resource is streamable (value is "sling.contentType").
+ * This property may be missing if the resource is not streamable or if the
+ * content type is not known.
+ */
+ public static final String CONTENT_TYPE = "sling.contentType";
+
+ /**
+ * The name of the optional property providing the content length of the
+ * resource if the resource is streamable (value is "sling.contentLength").
+ * This property may be missing if the resource is not streamable or if the
+ * content length is not known.
+ * <p>
+ * Note, that unlike the other properties, this property may be set only
+ * after the resource has successfully been adapted to an
+ * <code>InputStream</code> for performance reasons.
+ */
+ public static final String CONTENT_LENGTH = "sling.contentLength";
+
+ /**
+ * The name of the optional property providing the character encoding of the
+ * resource if the resource is streamable and contains character data (value
+ * is "sling.characterEncoding"). This property may be missing if the
+ * resource is not streamable or if the character encoding is not known.
+ */
+ public static final String CHARACTER_ENCODING = "sling.characterEncoding";
+
+ /**
+ * Returns the creation time of this resource in the repository in
+ * milliseconds (value is "sling.creationTime"). The type of this property
+ * is <code>java.lang.Long</code>. The property may be missing if the
+ * resource is not streamable or if the creation time is not known.
+ */
+ public static final String CREATION_TIME = "sling.creationTime";
+
+ /**
+ * Returns the last modification time of this resource in the repository in
+ * milliseconds (value is "sling.modificationTime"). The type of this
+ * property is <code>java.lang.Long</code>. The property may be missing
+ * if the resource is not streamable or if the last modification time is not
+ * known.
+ */
+ public static final String MODIFICATION_TIME = "sling.modificationTime";
+
+ /**
+ * Returns whether the resource resolver should continue to search for a
+ * resource.
+ * A resource provider can set this flag to indicate that the resource
+ * resolver should search for a provider with a lower priority. If it
+ * finds a resource using such a provider, that resource is returned
+ * instead. If none is found this resource is returned.
+ * This flag should never be manipulated by application code!
+ * The value of this property has no meaning, the resource resolver
+ * just checks whether this flag is set or not.
+ * @since 2.2
+ */
+ public static final String INTERNAL_CONTINUE_RESOLVING = ":org.apache.sling.resource.internal.continue.resolving";
+
+ private boolean isReadOnly = false;
+
+ /**
+ * Sets the {@link #CHARACTER_ENCODING} property to <code>encoding</code>
+ * if not <code>null</code>.
+ */
+ public void setCharacterEncoding(String encoding) {
+ if (encoding != null) {
+ put(CHARACTER_ENCODING, encoding);
+ }
+ }
+
+ /**
+ * Returns the {@link #CHARACTER_ENCODING} property if not <code>null</code>
+ * and a <code>String</code> instance. Otherwise <code>null</code> is
+ * returned.
+ */
+ public String getCharacterEncoding() {
+ Object value = get(CHARACTER_ENCODING);
+ if (value instanceof String) {
+ return (String) value;
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the {@link #CONTENT_TYPE} property to <code>contentType</code> if
+ * not <code>null</code>.
+ */
+ public void setContentType(String contentType) {
+ if (contentType != null) {
+ put(CONTENT_TYPE, contentType);
+ }
+ }
+
+ /**
+ * Returns the {@link #CONTENT_TYPE} property if not <code>null</code> and
+ * a <code>String</code> instance. Otherwise <code>null</code> is
+ * returned.
+ */
+ public String getContentType() {
+ Object value = get(CONTENT_TYPE);
+ if (value instanceof String) {
+ return (String) value;
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the {@link #CONTENT_LENGTH} property to <code>contentType</code>
+ * if not <code>null</code>.
+ */
+ public void setContentLength(long contentLength) {
+ if (contentLength > 0) {
+ put(CONTENT_LENGTH, contentLength);
+ }
+ }
+
+ /**
+ * Returns the {@link #CONTENT_LENGTH} property if not <code>null</code>
+ * and a <code>long</code>. Otherwise <code>-1</code> is returned.
+ */
+ public long getContentLength() {
+ Object value = get(CONTENT_LENGTH);
+ if (value instanceof Long) {
+ return (Long) value;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Sets the {@link #CREATION_TIME} property to <code>creationTime</code>
+ * if not negative.
+ */
+ public void setCreationTime(long creationTime) {
+ if (creationTime >= 0) {
+ put(CREATION_TIME, creationTime);
+ }
+ }
+
+ /**
+ * Returns the {@link #CREATION_TIME} property if not <code>null</code>
+ * and a <code>long</code>. Otherwise <code>-1</code> is returned.
+ */
+ public long getCreationTime() {
+ Object value = get(CREATION_TIME);
+ if (value instanceof Long) {
+ return (Long) value;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Sets the {@link #MODIFICATION_TIME} property to
+ * <code>modificationTime</code> if not negative.
+ */
+ public void setModificationTime(long modificationTime) {
+ if (modificationTime >= 0) {
+ put(MODIFICATION_TIME, modificationTime);
+ }
+ }
+
+ /**
+ * Returns the {@link #MODIFICATION_TIME} property if not <code>null</code>
+ * and a <code>long</code>. Otherwise <code>-1</code> is returned.
+ */
+ public long getModificationTime() {
+ Object value = get(MODIFICATION_TIME);
+ if (value instanceof Long) {
+ return (Long) value;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Sets the {@link #RESOLUTION_PATH} property to <code>resolutionPath</code>
+ * if not <code>null</code>.
+ */
+ public void setResolutionPath(String resolutionPath) {
+ if (resolutionPath != null) {
+ put(RESOLUTION_PATH, resolutionPath);
+ }
+ }
+
+ /**
+ * Returns the {@link #RESOLUTION_PATH} property if not <code>null</code>
+ * and a <code>String</code> instance. Otherwise <code>null</code> is
+ * returned.
+ */
+ public String getResolutionPath() {
+ Object value = get(RESOLUTION_PATH);
+ if (value instanceof String) {
+ return (String) value;
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the {@link #RESOLUTION_PATH_INFO} property to
+ * <code>resolutionPathInfo</code> if not <code>null</code>.
+ */
+ public void setResolutionPathInfo(String resolutionPathInfo) {
+ if (resolutionPathInfo != null) {
+ put(RESOLUTION_PATH_INFO, resolutionPathInfo);
+ }
+ }
+
+ /**
+ * Returns the {@link #RESOLUTION_PATH_INFO} property if not
+ * <code>null</code> and a <code>String</code> instance. Otherwise
+ * <code>null</code> is returned.
+ */
+ public String getResolutionPathInfo() {
+ Object value = get(RESOLUTION_PATH_INFO);
+ if (value instanceof String) {
+ return (String) value;
+ }
+
+ return null;
+ }
+
+ /**
+ * Make this object read-only. All method calls trying to modify this object
+ * result in an exception!
+ * @since 2.3
+ */
+ public void lock() {
+ this.isReadOnly = true;
+ }
+
+ /**
+ * Check if this object is read only and if so throw an unsupported operation exception.
+ */
+ private void checkReadOnly() {
+ if ( this.isReadOnly ) {
+ throw new UnsupportedOperationException(getClass().getSimpleName() + " is locked");
+ }
+ }
+
+ @Override
+ public void clear() {
+ this.checkReadOnly();
+ super.clear();
+ }
+
+ @Override
+ public Object put(final String key, final Object value) {
+ this.checkReadOnly();
+ return super.put(key, value);
+ }
+
+ @Override
+ public void putAll(final Map<? extends String, ? extends Object> m) {
+ this.checkReadOnly();
+ super.putAll(m);
+ }
+
+ @Override
+ public Object remove(final Object key) {
+ this.checkReadOnly();
+ return super.remove(key);
+ }
+
+ // volatile for correct double-checked locking in getLockedData()
+ private volatile Set<Map.Entry<String, Object>> lockedEntrySet;
+ private Set<String> lockedKeySet;
+ private Collection<Object> lockedValues;
+
+ private void getLockedData() {
+ if(isReadOnly && lockedEntrySet == null) {
+ synchronized (this) {
+ if(isReadOnly && lockedEntrySet == null) {
+ lockedEntrySet = Collections.unmodifiableSet(super.entrySet());
+ lockedKeySet = Collections.unmodifiableSet(super.keySet());
+ lockedValues = Collections.unmodifiableCollection(super.values());
+ }
+ }
+ }
+ }
+
+ @Override
+ public Set<Map.Entry<String, Object>> entrySet() {
+ getLockedData();
+ return lockedEntrySet != null ? lockedEntrySet : super.entrySet();
+ }
+
+ @Override
+ public Set<String> keySet() {
+ getLockedData();
+ return lockedKeySet != null ? lockedKeySet : super.keySet();
+ }
+
+ @Override
+ public Collection<Object> values() {
+ getLockedData();
+ return lockedValues != null ? lockedValues : super.values();
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceNotFoundException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceNotFoundException.java
new file mode 100644
index 0000000..907be46
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceNotFoundException.java
@@ -0,0 +1,60 @@
+/*
+ * 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.api.resource;
+
+import org.apache.sling.api.SlingException;
+
+/**
+ * An Exception that causes Sling to return a 404 (NOT FOUND) status code. This
+ * exception should not be caught but rather let be handed up the call stack up
+ * to the Sling error and exception handling.
+ * <p>
+ * The advantage of using this exception over the
+ * <code>HttpServletResponse.sendError</code> methods is that the request can
+ * be aborted immediately all the way up in the call stack and that in addition
+ * to the status code and an optional message a <code>Throwable</code> may be
+ * supplied providing more information.
+ */
+public class ResourceNotFoundException extends SlingException {
+
+ private static final long serialVersionUID = -6684709279554347984L;
+
+ private final String resource;
+
+ public ResourceNotFoundException(String message) {
+ this(null, message);
+ }
+
+ public ResourceNotFoundException(String resource, String message) {
+ super(message);
+ this.resource = resource;
+ }
+
+ public ResourceNotFoundException(String message, Throwable cause) {
+ this(null, message, cause);
+ }
+
+ public ResourceNotFoundException(String resource, String message,
+ Throwable cause) {
+ super(message, cause);
+ this.resource = resource;
+ }
+
+ public String getResource() {
+ return resource;
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceProvider.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceProvider.java
new file mode 100644
index 0000000..191a3d9
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceProvider.java
@@ -0,0 +1,153 @@
+/*
+ * 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.api.resource;
+
+import java.util.Iterator;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.SlingException;
+
+/**
+ * API for providers of resources. Used by the {@link ResourceResolver} to
+ * transparently access resources from different locations such as a JCR
+ * repository (the default) or OSGi bundles.
+ * <p>
+ * This interface is intended to be implemented by providers of resource
+ * instances on behalf of the {@link ResourceResolver}. It
+ * is not intended to be used by client applications directly. A resource
+ * provider can either directly implement this service interface, e.g.
+ * when no user authentication is provided (like for bundle resources)
+ * or a {@link ResourceProviderFactory} service can be implemented which
+ * upon successful authentication returns a resource provider with the
+ * given user credentials.
+ */
+public interface ResourceProvider {
+
+ /**
+ * The service name to use when registering implementations of this
+ * interface as services (value is
+ * "org.apache.sling.api.resource.ResourceProvider").
+ */
+ String SERVICE_NAME = ResourceProvider.class.getName();
+
+ /**
+ * The name of the service registration property containing the root paths
+ * of the resources provided by this provider (value is "provider.roots").
+ */
+ String ROOTS = "provider.roots";
+
+ /**
+ * The name of the service registration property containing the a boolean
+ * flag whether this provider owns the tree registered by the roots. The
+ * default for this value is <code>false</code>. If a provider owns a root
+ * no other providers are asked for resources under this root if this
+ * provider does not have a resource. (value is "provider.ownsRoots").
+ *
+ * @since 2.2
+ */
+ String OWNS_ROOTS = "provider.ownsRoots";
+
+ /**
+ * The name of the service registration property containing the a boolean
+ * flag indicating if the ResourceAccessSecurity service should be used for
+ * this provider or not. ResourceProvider implementations are encouraged
+ * to use the ResourceAccessSecurity service for access control unless
+ * the underlying storage already provides it.
+ * The default for this value is <code>false</code>.
+ * (value is "provider.useResourceAccessSecurity")
+ */
+ String USE_RESOURCE_ACCESS_SECURITY = "provider.useResourceAccessSecurity";
+
+ /**
+ * The resource type be set on resources returned by the
+ * {@link #listChildren(Resource)} method to enable traversing the
+ * resource
+ * tree down to a deeply nested provided resource which has no concrete
+ * parent hierarchy (value is"sling:syntheticResourceProviderResource").
+ *
+ * @see #listChildren(Resource)
+ */
+ String RESOURCE_TYPE_SYNTHETIC = "sling:syntheticResourceProviderResource";
+
+ /**
+ * Returns a resource from this resource provider or <code>null</code> if
+ * the resource provider cannot find it. The path should have one of the
+ * {@link #ROOTS} strings as its prefix.
+ * <p>
+ * This method is called to resolve a resource for the given request.
+ * The properties of the request, such as request
+ * parameters, may be use to parameterize the resource resolution. An
+ * example of such parameterization is support for a JSR-311
+ * style resource provider to support the parameterized URL patterns.
+ *
+ * @param resourceResolver
+ * The {@link ResourceResolver} to which the returned
+ * {@link Resource} is attached.
+ * @return <code>null</code> If this provider does not have a resource for
+ * the path.
+ * @throws org.apache.sling.api.SlingException
+ * may be thrown in case of any problem creating the <code>Resource</code> instance.
+ * @deprecated since 2.2.0 (and JCR Resource 2.1.0), this method will not be invoked.
+ */
+ @Deprecated
+ Resource getResource(ResourceResolver resourceResolver, HttpServletRequest request, String path);
+
+ /**
+ * Returns a resource from this resource provider or <code>null</code> if
+ * the resource provider cannot find it. The path should have one of the {@link #ROOTS}
+ * strings as its prefix.
+ *
+ * @param resourceResolver
+ * The {@link ResourceResolver} to which the returned {@link Resource} is attached.
+ * @return <code>null</code> If this provider does not have a resource for
+ * the path.
+ * @throws org.apache.sling.api.SlingException
+ * may be thrown in case of any problem creating the <code>Resource</code> instance.
+ */
+ Resource getResource(ResourceResolver resourceResolver, String path);
+
+ /**
+ * Returns an <code>Iterator</code> of {@link Resource} objects loaded from
+ * the children of the given <code>Resource</code>. The returned {@link Resource} instances
+ * are attached to the same
+ * {@link ResourceResolver} as the given <code>parent</code> resource.
+ * <p>
+ * This method may be called for resource providers whose root path list contains a path such
+ * that the resource path is a
+ * prefix of the list entry. This allows for the enumeration of deeply nested provided resources
+ * for which no actual parent
+ * hierarchy exists.
+ * <p>
+ * The returned iterator may in turn contain resources which do not actually exist but are required
+ * to traverse the resource
+ * tree. Such resources SHOULD be {@link SyntheticResource} objects whose resource type MUST be set to
+ * {@link #RESOURCE_TYPE_SYNTHETIC}.
+ *
+ * @param parent
+ * The {@link Resource Resource} whose children are requested.
+ * @return An <code>Iterator</code> of {@link Resource} objects or <code>null</code> if the resource
+ * provider has no children for the given resource.
+ * @throws NullPointerException
+ * If <code>parent</code> is <code>null</code>.
+ * @throws SlingException
+ * If any error occurs acquiring the child resource iterator.
+ */
+ Iterator<Resource> listChildren(Resource parent);
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceProviderFactory.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceProviderFactory.java
new file mode 100644
index 0000000..e96e0f8
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceProviderFactory.java
@@ -0,0 +1,93 @@
+/*
+ * 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.api.resource;
+
+import java.util.Map;
+
+/**
+ * The <code>ResourceProviderFactory</code> defines the service API to get and
+ * create <code>ResourceProviders</code>s dynamically on a per usage base.
+ *
+ * Instead of sharing a resource provider between resource resolvers, the
+ * factory allows to create an own instance of a resource provider per resource
+ * resolver.
+ * The factory also supports authentication.
+ * <p>
+ * If the resource provider is not used anymore and implements the {@link DynamicResourceProvider}
+ * interface, the close method should be called.
+ *
+ * @since 2.2.0
+ */
+public interface ResourceProviderFactory {
+
+ /**
+ * A required resource provider factory is accessed directly when a new resource resolver
+ * is created. Only if authentication against all required resource provider factories
+ * is successful, a resource resolver is created by the resource resolver factory.
+ * Boolean service property, default value is <code>false</true>
+ */
+ String PROPERTY_REQUIRED = "required";
+
+ /**
+ * Returns a new {@link ResourceProvider} instance with further
+ * configuration taken from the given <code>authenticationInfo</code> map.
+ * Generally this map will contain a user name and password to authenticate.
+ * <p>
+ * If the <code>authenticationInfo</code> map is <code>null</code> the
+ * <code>ResourceProvider</code> returned will generally not be authenticated
+ * and only provide minimal privileges, if any at all.
+ *
+ * @param authenticationInfo
+ * A map of further credential information which may be used by
+ * the implementation to parameterize how the resource provider is
+ * created. This may be <code>null</code>.
+ * @return A {@link ResourceProvider} according to the <code>authenticationInfo</code>.
+ * @throws LoginException
+ * If an error occurs creating the new <code>ResourceProvider</code> with the
+ * provided credential data.
+ */
+ ResourceProvider getResourceProvider(Map<String, Object> authenticationInfo) throws LoginException;
+
+ /**
+ * Returns a new {@link ResourceProvider} instance with administrative
+ * privileges with further configuration taken from the given <code>authenticationInfo</code>
+ * map.
+ * <p>
+ * Note, that if the <code>authenticationInfo</code> map contains the
+ * {@link ResourceResolverFactory#USER_IMPERSONATION} attribute the <code>ResourceProvider</code> returned will only
+ * have administrative privileges if the user identified by the property has administrative
+ * privileges.
+ * <p>
+ * <b><i>NOTE: This method is intended for use by infrastructure bundles to access the
+ * resource tree and provide general services. This method MUST not be used to handle client
+ * requests of whatever kinds. To handle client requests a regular authenticated resource
+ * provider retrieved through {@link #getResourceProvider(Map)} must be used.</i></b>
+ *
+ * @param authenticationInfo
+ * A map of further credential information which may be used by
+ * the implementation to parameterize how the resource provider is
+ * created. This may be <code>null</code>.
+ * @return A {@link ResourceProvider} with administrative privileges unless
+ * the {@link ResourceResolverFactory#USER_IMPERSONATION} was set in the <code>authenticationInfo</code>.
+ * @throws LoginException
+ * If an error occurs creating the new <code>ResourceResolverFactory</code> with the
+ * provided credential data.
+ */
+ ResourceProvider getAdministrativeResourceProvider(Map<String, Object> authenticationInfo) throws LoginException;
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceResolver.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceResolver.java
new file mode 100644
index 0000000..89544a3
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceResolver.java
@@ -0,0 +1,646 @@
+/*
+ * 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.api.resource;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.adapter.Adaptable;
+
+/**
+ * The <code>ResourceResolver</code> defines the service API which may be used
+ * to resolve {@link Resource} objects. The resource resolver is available to
+ * the request processing servlet through the
+ * {@link org.apache.sling.api.SlingHttpServletRequest#getResourceResolver()}
+ * method.
+ * A resource resolver can also be created through the {@link ResourceResolverFactory}.
+ * <p>
+ * The <code>ResourceResolver</code> is also an {@link Adaptable} to get
+ * adapters to other types. A JCR based resource resolver might support adapting
+ * to the JCR Session used by the resolver to access the JCR Repository.
+ * <p>
+ * A <code>ResourceResolver</code> is generally not thread safe! As a
+ * consequence, an application which uses the resolver, its returned resources
+ * and/or objects resulting from adapting either the resolver or a resource,
+ * must provide proper synchronization to ensure no more than one thread
+ * concurrently operates against a single resolver, resource or resulting
+ * objects.
+ * <p>
+ * <b>Accessing Resources</b>
+ * <p>
+ * This interface defines two kinds of methods to access resources: The
+ * <code>resolve</code> methods and the <code>getResource</code> methods. The
+ * difference lies in the algorithm applied to find the requested resource and
+ * in the behavior in case a resource cannot be found:
+ * <table>
+ * <tr>
+ * <th>Method Kind</th>
+ * <th>Access Algorithm</th>
+ * <th>Missing Resource</th>
+ * </tr>
+ * <tr>
+ * <td>resolve</td>
+ * <td>Path is always assumed to be absolute. Uses elaborate resource resolution
+ * algorithm. This kind of method is intended to resolve request URLs to
+ * resources.</td>
+ * <td>Returns {@link NonExistingResource}</td>
+ * </tr>
+ * <tr>
+ * <td>getResource</td>
+ * <td>Directly access resources with absolute path. For relative paths, the
+ * {@link #getSearchPath() search path} is applied. This method is intended to
+ * be used by request processing scripts to access further resources as
+ * required.</td>
+ * <td>Returns <code>null</code></td>
+ * </tr>
+ * </table>
+ * <p>
+ * <b>Lifecycle</b>
+ * <p>
+ * A Resource Resolver has a life cycle which begins with the creation of the
+ * Resource Resolver using any of the factory methods and ends with calling the
+ * {@link #close()} method. It is very important to call the {@link #close()}
+ * method once the resource resolver is not used any more to ensure any system
+ * resources are properly cleaned up.
+ * <p>
+ * To check whether a Resource Resolver can still be used, the {@link #isLive()}
+ * method can be called.
+ * <p>
+ * <b>Resource Resolver Attributes</b>
+ * <p>
+ * The authentication info properties provided to the
+ * {@link ResourceResolverFactory#getResourceResolver(Map)},
+ * {@link ResourceResolverFactory#getAdministrativeResourceResolver(Map)}, or
+ * {@link #clone(Map)} are available through the {@link #getAttributeNames()}
+ * and {@link #getAttribute(String)} methods with the exception of security
+ * sensitive properties like {@link ResourceResolverFactory#PASSWORD} which is
+ * not exposed.
+ */
+public interface ResourceResolver extends Adaptable {
+
+ /**
+ * A request attribute containing the workspace to use for
+ * {@link #resolve(HttpServletRequest)} and
+ * {@link #resolve(HttpServletRequest, String)} if not the default workspace
+ * should be used to resolve the resource.
+ *
+ * @since 2.1
+ * @deprecated
+ */
+ @Deprecated
+ String REQUEST_ATTR_WORKSPACE_INFO = ResourceResolver.class.getName()
+ + "/use.workspace";
+
+ /**
+ * The name of the resource resolver attribute which is set if the resource
+ * resolver has been impersonated as per the
+ * {@link ResourceResolverFactory#USER_IMPERSONATION} property. The value of
+ * this attribute is the name of the primary user provided to the resource
+ * resolver factory method.
+ *
+ * @since 2.1
+ */
+ String USER_IMPERSONATOR = "user.impersonator";
+
+ /**
+ * This is the suggested property to be used for setting the resource type
+ * of a resource during either creation ({@link #create(Resource, String, Map)})
+ * or modifying ({@link ModifiableValueMap}).
+ * However the exact way to set the resource type of a resource is defined
+ * by the underlying resource provider. It should value this property but
+ * is not required to do so.
+ * @since 2.3
+ */
+ String PROPERTY_RESOURCE_TYPE = "sling:resourceType";
+
+ /**
+ * Resolves the resource from the given <code>absPath</code> optionally
+ * taking <code>HttpServletRequest</code> into account, such as the value of
+ * the <code>Host</code> request header. Returns a
+ * {@link NonExistingResource} if the path cannot be resolved to an existing
+ * and accessible resource.
+ * <p>
+ * The difference between this method and the {@link #resolve(String)}
+ * method is, that this method may take request properties like the scheme,
+ * the host header or request parameters into account to resolve the
+ * resource.
+ *
+ * @param request The http servlet request object providing more hints at
+ * how to resolve the <code>absPath</code>. This parameter may be
+ * <code>null</code> in which case the implementation should use
+ * reasonable defaults.
+ * @param absPath The absolute path to be resolved to a resource. If this
+ * parameter is <code>null</code>, it is assumed to address the
+ * root of the resource tree. If the path is relative it is
+ * assumed relative to the root, that is a slash is prepended to
+ * the path before resolving it.
+ * @return The {@link Resource} addressed by the <code>absPath</code> or a
+ * {@link NonExistingResource} if no such resource can be resolved.
+ * @throws org.apache.sling.api.SlingException Or a subclass thereof may be
+ * thrown if an error occurs trying to resolve the resource.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ * @since 2.0.4
+ */
+ Resource resolve(HttpServletRequest request, String absPath);
+
+ /**
+ * Resolves the resource from the given absolute path. Returns a
+ * {@link NonExistingResource} if the path cannot be resolved to an existing
+ * and accessible resource.
+ * <p>
+ * This method is intended to apply the same algorithm to the absolute path
+ * as is used by the {@link #resolve(HttpServletRequest)} method except for
+ * cases where the latter uses request property such as request headers or
+ * request parameters to resolve a resource.
+ * <p>
+ * It is ok for the implementation of this method to just call the
+ * {@link #resolve(HttpServletRequest, String)} method with
+ * <code>null</code> as the request argument.
+ *
+ * @param absPath The absolute path to be resolved to a resource. If this
+ * parameter is <code>null</code>, it is assumed to address the
+ * root of the resource tree. If the path is relative it is
+ * assumed relative to the root, that is a slash is prepended to
+ * the path before resolving it.
+ * @return The {@link Resource} addressed by the <code>absPath</code> or a
+ * {@link NonExistingResource} if no such resource can be resolved.
+ * @throws org.apache.sling.api.SlingException Or a subclass thereof may be
+ * thrown if an error occurs trying to resolve the resource.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ */
+ Resource resolve(String absPath);
+
+ /**
+ * Resolves the resource from the given <code>HttpServletRequest</code>.
+ * Returns a {@link NonExistingResource} if the path cannot be resolved to
+ * an existing and accessible resource.
+ * <p>
+ * This method is deprecated as of API version 2.0.4 and should not be used
+ * anymore. Implementations are expected to implement this method calling
+ * the {@link #resolve(HttpServletRequest, String)} where the
+ * <code>absPath</code> argument is the result of calling the
+ * <code>getPathInfo()</code> on the <code>request</code> object.
+ *
+ * @param request The http servlet request object used to resolve the
+ * resource for. This must not be <code>null</code>.
+ * @return The {@link Resource} addressed by
+ * <code>HttpServletRequest.getPathInfo()</code> or a
+ * {@link NonExistingResource} if no such resource can be resolved.
+ * @throws NullPointerException If <code>request</code> is <code>null</code>
+ * .
+ * @throws org.apache.sling.api.SlingException Or a subclass thereof may be
+ * thrown if an error occurs trying to resolve the resource.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ * @deprecated as of 2.0.4, use {@link #resolve(HttpServletRequest, String)}
+ * instead.
+ */
+ @Deprecated
+ Resource resolve(HttpServletRequest request);
+
+ /**
+ * Returns a path mapped from the (resource) path applying the reverse
+ * mapping used by the {@link #resolve(String)} such that when the path is
+ * given to the {@link #resolve(String)} method the same resource is
+ * returned.
+ * <p>
+ * Note, that technically the <code>resourcePath</code> need not refer to an
+ * existing resource. This method just applies the mappings and returns the
+ * resulting string. If the <code>resourcePath</code> does not address an
+ * existing resource roundtripping may of course not work and calling
+ * {@link #resolve(String)} with the path returned may return
+ * <code>null</code>.
+ * <p>
+ * This method is intended as the reverse operation of the
+ * {@link #resolve(String)} method.
+ *
+ * @param resourcePath The path for which to return a mapped path.
+ * @return The mapped path.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ */
+ String map(String resourcePath);
+
+ /**
+ * Returns an URL mapped from the (resource) path applying the reverse
+ * mapping used by the {@link #resolve(HttpServletRequest, String)} such
+ * that when the path is given to the
+ * {@link #resolve(HttpServletRequest, String)} method the same resource is
+ * returned.
+ * <p>
+ * Note, that technically the <code>resourcePath</code> need not refer to an
+ * existing resource. This method just applies the mappings and returns the
+ * resulting string. If the <code>resourcePath</code> does not address an
+ * existing resource roundtripping may of course not work and calling
+ * {@link #resolve(HttpServletRequest, String)} with the path returned may
+ * return <code>null</code>.
+ * <p>
+ * This method is intended as the reverse operation of the
+ * {@link #resolve(HttpServletRequest, String)} method. As such the URL
+ * returned is expected to be an absolute URL including scheme, host, any
+ * servlet context path and the actual path used to resolve the resource.
+ *
+ * @param request The http servlet request object which may be used to apply
+ * more mapping functionality.
+ * @param resourcePath The path for which to return a mapped path.
+ * @return The mapped URL.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ * @since 2.0.4
+ */
+ String map(HttpServletRequest request, String resourcePath);
+
+ /**
+ * Returns a {@link Resource} object for data located at the given path.
+ * <p>
+ * This specification does not define the location for resources or the
+ * semantics for resource paths. For an implementation reading content from
+ * a Java Content Repository, the path could be a
+ * <code>javax.jcr.Item</code> path from which the resource object is
+ * loaded. In contrast to the {@link #resolve(String)} method, this method
+ * does not apply any logic to the path, so the path is used as-is to fetch
+ * the content.
+ *
+ * @param path The absolute path to the resource object to be loaded. The
+ * path may contain relative path specifiers like <code>.</code>
+ * (current location) and <code>..</code> (parent location),
+ * which are resolved by this method. If the path is relative,
+ * that is the first character is not a slash, implementations
+ * are expected to apply a search path algorithm to resolve the
+ * relative path to a resource.
+ * @return The <code>Resource</code> object loaded from the path or
+ * <code>null</code> if the path does not resolve to a resource.
+ * @throws org.apache.sling.api.SlingException If an error occurs trying to
+ * load the resource object from the path.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ */
+ Resource getResource(String path);
+
+ /**
+ * Returns a {@link Resource} object for data located at the given path.
+ * <p>
+ * This specification does not define the location for resources or the
+ * semantics for resource paths. For an implementation reading content from
+ * a Java Content Repository, the path could be a
+ * <code>javax.jcr.Item</code> path from which the resource object is
+ * loaded.
+ *
+ * @param base The base {@link Resource} against which a relative path
+ * argument given by <code>path</code> is resolved. This
+ * parameter may be <code>null</code> if the <code>path</code> is
+ * known to be absolute.
+ * @param path The path to the resource object to be loaded. If the path is
+ * relative, i.e. does not start with a slash (<code>/</code>),
+ * the resource relative to the given <code>base</code> resource
+ * is returned. The path may contain relative path specifiers
+ * like <code>.</code> (current location) and <code>..</code>
+ * (parent location), which are resolved by this method.
+ * @return The <code>Resource</code> object loaded from the path or
+ * <code>null</code> if the path does not resolve to a resource.
+ * @throws org.apache.sling.api.SlingException If an error occurs trying to
+ * load the resource object from the path or if
+ * <code>base</code> is <code>null</code> and <code>path</code>
+ * is relative.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ */
+ Resource getResource(Resource base, String path);
+
+ /**
+ * Returns the search path used by the {@link #getResource(String)} method
+ * to search for resources by relative path. If no search path is set an
+ * empty array is returned.
+ * <p>
+ * The returns array of Strings is a copy of the internal value, so
+ * modifications to this array have no influence on the operation of the
+ * ResourceResolver.
+ * <p>
+ * Each entry in the array is an absolute path terminated with a slash
+ * character. Thus to create an absolute path from a search path entry and a
+ * relative path, the search path entry and relative path may just be
+ * concatenated.
+ *
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ */
+ String[] getSearchPath();
+
+ /**
+ * Returns an <code>Iterator</code> of {@link Resource} objects loaded from
+ * the children of the given <code>Resource</code>.
+ * <p>
+ * This specification does not define what the term "child" means. This is
+ * left to the implementation to define. For example an implementation
+ * reading content from a Java Content Repository, the children could be the
+ * {@link Resource} objects loaded from child items of the <code>Item</code>
+ * of the given <code>Resource</code>.
+ *
+ * @param parent The {@link Resource Resource} whose children are requested.
+ * @return An <code>Iterator</code> of {@link Resource} objects.
+ * @throws NullPointerException If <code>parent</code> is <code>null</code>.
+ * @throws org.apache.sling.api.SlingException If any error occurs acquiring
+ * the child resource iterator.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ */
+ Iterator<Resource> listChildren(Resource parent);
+
+ /**
+ * Returns an <code>Iterable</code> of {@link Resource} objects loaded from
+ * the children of the given <code>Resource</code>.
+ * <p>
+ * This specification does not define what the term "child" means. This is
+ * left to the implementation to define. For example an implementation
+ * reading content from a Java Content Repository, the children could be the
+ * {@link Resource} objects loaded from child items of the <code>Item</code>
+ * of the given <code>Resource</code>.
+ *
+ * @param parent The {@link Resource Resource} whose children are requested.
+ * @return An <code>Iterable</code> of {@link Resource} objects.
+ * @throws NullPointerException If <code>parent</code> is <code>null</code>.
+ * @throws org.apache.sling.api.SlingException If any error occurs acquiring
+ * the child resource iterator.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ * @since 2.2
+ */
+ Iterable<Resource> getChildren(Resource parent);
+
+ /**
+ * Searches for resources using the given query formulated in the given
+ * language.
+ * <p>
+ * The semantic meaning of the query and language depend on the actual
+ * implementation and storage used for the resources. For JCR repository
+ * being used as storage, the query and language parameters are used to
+ * create a JCR <code>Query</code> through the <code>QueryManager</code>.
+ * The result returned is then based on the <code>NodeIterator</code>
+ * provided by the query result.
+ *
+ * @param query The query string to use to find the resources.
+ * @param language The language in which the query is formulated. The
+ * language should always be specified. However for
+ * compatibility with older version, if no language
+ * is specified, "xpath" is used.
+ * @return An <code>Iterator</code> of {@link Resource} objects matching the
+ * query.
+ * @throws QuerySyntaxException If the query is not syntactically correct
+ * according to the query language indicator.
+ * @throws org.apache.sling.api.SlingException If an error occurs querying
+ * for the resources.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ */
+ Iterator<Resource> findResources(String query, String language);
+
+ /**
+ * Queries the storage using the given query formulated in the given
+ * language.
+ * <p>
+ * The semantic meaning of the query and language depend on the actual
+ * implementation and storage used for the resources. For JCR repository
+ * being used as storage, the query and language parameters are used to
+ * create a JCR <code>Query</code> through the <code>QueryManager</code>.
+ * The result returned is then based on the <code>RowIterator</code>
+ * provided by the query result. The map returned for each row is indexed by
+ * the column name and the column value is the JCR <code>Value</code> object
+ * converted into the respective Java object, such as <code>Boolean</code>
+ * for a value of property type <em>Boolean</em>.
+ *
+ * @param query The query string to use to find the resources.
+ * @param language The language in which the query is formulated. The
+ * language should always be specified. However for
+ * compatibility with older version, if no language
+ * is specified, "xpath" is used.
+ * @return An <code>Iterator</code> of <code>Map</code> instances providing
+ * access to the query result.
+ * @throws QuerySyntaxException If the query is not syntactically correct
+ * according to the query language indicator.
+ * @throws org.apache.sling.api.SlingException If an error occurs querying
+ * for the resources.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ */
+ Iterator<Map<String, Object>> queryResources(String query, String language);
+
+ /**
+ * Returns a new <code>ResourceResolver</code> instance based on the given
+ * <code>authenticationInfo</code> map and the original authentication info
+ * used to create this instance.
+ * <p>
+ * The new resource resolver is created according to the following
+ * algorithm:
+ *
+ * <pre>
+ * Map<String, Object> newAuthenticationInfo = new HashMap(
+ * authenticationInfoOfThisInstance);
+ * newAuthenticationInfo.addAll(authenticationInfo);
+ * return resourceResolverFactory.getResourceResolver(newAuthenticationInfo);
+ * </pre>
+ *
+ * @param authenticationInfo The map or credential data to overlay the
+ * original credential data with for the creation of a new
+ * resource resolver. This may be <code>null</code> in which case
+ * the same credential data is used as was used to create this
+ * instance.
+ * @return A new <code>ResourceResolver</code>
+ * @throws LoginException If an error occurs creating the new
+ * <code>ResourceResolver</code> with the provided credential
+ * data.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ * @since 2.1
+ */
+ ResourceResolver clone(Map<String, Object> authenticationInfo)
+ throws LoginException;
+
+ /**
+ * Returns <code>true</code> if this resource resolver has not been closed
+ * yet.
+ * <p>
+ * Unlike the other methods defined in this interface, this method will
+ * never throw an exception even after the resource resolver has been
+ * {@link #close() closed}.
+ *
+ * @return <code>true</code> if the resource resolver has not been closed
+ * yet. Once the resource resolver has been closed, this method
+ * returns <code>false</code>.
+ * @since 2.1
+ */
+ boolean isLive();
+
+ /**
+ * Close this resource resolver. This method should be called by clients
+ * when the resource resolver is not used anymore. Once this method has been
+ * called, the resource resolver is considered unusable and will throw
+ * exceptions if still used - with the exception of this method, which
+ * can be called several times with no ill effects.
+ *
+ * @since 2.1
+ */
+ void close();
+
+ /**
+ * Get the user ID, if any, associated with this resource resolver. The
+ * meaning of this identifier is an implementation detail defined by the
+ * underlying repository. This method may return null.
+ *
+ * @return the user ID
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ * @since 2.1
+ */
+ String getUserID();
+
+ /**
+ * Returns an iterator of attribute names whose value can be retrieved
+ * calling the {@link #getAttribute(String)} method. This iterator will not
+ * include any attributes which are not accessible.
+ *
+ * @return An iterator of attribute names
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ */
+ Iterator<String> getAttributeNames();
+
+ /**
+ * Returns the value of the given resource resolver attribute or
+ * <code>null</code> if the attribute is not set (or not visible as is the
+ * case of the {@link ResourceResolverFactory#PASSWORD} or other security
+ * sensitive attributes).
+ *
+ * @param name The name of the attribute to access
+ * @return The value of the attribute or <code>null</code> if the attribute
+ * is not set or not accessible.
+ * @throws NullPointerException if <code>name</code> is <code>null</code>.
+ * @throws IllegalStateException if this resource resolver has already been
+ * {@link #close() closed}.
+ */
+ Object getAttribute(String name);
+
+ /**
+ * Delete the resource
+ *
+ * Deleting a non existing resource leads to no operation nor exception.
+ *
+ * @param resource The resource to delete
+ *
+ * @throws NullPointerException if the resource parameter is null
+ * @throws UnsupportedOperationException If the resource provider does not allow to
+ * delete this resource.
+ * @throws PersistenceException If the operation fails.
+ * @since 2.2
+ */
+ void delete(Resource resource)
+ throws PersistenceException;
+
+ /**
+ * Add a child resource to the given parent resource
+ * @param parent The parent resource
+ * @param name The name of the child resource - this is a plain name, not a path!
+ * @param properties Optional properties for the resource
+ * @return The new resource
+ *
+ * @throws NullPointerException if the resource parameter or name parameter is null
+ * @throws IllegalArgumentException if the name contains a slash
+ * @throws UnsupportedOperationException If the resource provider does not allow to
+ * create a resource at that location.
+ * @throws PersistenceException If the operation fails.
+ * @since 2.2
+ */
+ Resource create(Resource parent, String name, Map<String, Object> properties)
+ throws PersistenceException;
+
+ /**
+ * Revert all pending changes.
+ * @since 2.2
+ */
+ void revert();
+
+ /**
+ * Persist all pending changes.
+ *
+ * @throws PersistenceException
+ * @since 2.2
+ */
+ void commit() throws PersistenceException;
+
+ /**
+ * Are there any pending changes?
+ * @since 2.2
+ */
+ boolean hasChanges();
+
+ /**
+ * Returns the super type of the given resource. This method checks first if
+ * the resource itself knows its super type by calling
+ * {@link Resource#getResourceSuperType()}. If that returns
+ * <code>null</code> {@link #getParentResourceType(String)}
+ * is invoked with the resource type of the resource.
+ *
+ * @param resource The resource to return the resource super type for.
+ * @return The resource super type or <code>null</code>. This
+ * method also returns <code>null</code> if the
+ * provided resource is <code>null</code>
+ * @since 2.3
+ */
+ String getParentResourceType(final Resource resource);
+
+ /**
+ * Returns the super type of the given resource type. This method converts
+ * the resource type to a resource path and checks the corresponding resource.
+ * If the resource exists, the {@link Resource#getResourceSuperType()} method
+ * is called.
+ *
+ * @param resourceType The resource type whose super type is to be returned.
+ * @return the super type of the <code>resourceType</code> or
+ * <code>null</code> if the resource type does not exist or returns
+ * <code>null</code> for its super type. It also returns
+ * <code>null</code> if <code>resourceType> is null.
+ * @since 2.3
+ */
+ public String getParentResourceType(final String resourceType);
+
+ /**
+ * Returns <code>true</code> if the resource type or any of the resource's
+ * super type(s) equals the given resource type.
+ *
+ * @param resource The resource to check
+ * @param resourceType The resource type to check this resource against.
+ * @return <code>true</code> if the resource type or any of the resource's
+ * super type(s) equals the given resource type. <code>false</code>
+ * is also returned if <code>resource</code> or<code>resourceType</code>
+ * are <code>null</code>.
+ * @since 2.3
+ */
+ boolean isResourceType(final Resource resource, final String resourceType);
+
+ /**
+ * The resolver is updated to reflect the latest state.
+ * Resources which have changes pending are not discarded.
+ * @since 2.3
+ */
+ void refresh();
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceResolverFactory.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceResolverFactory.java
new file mode 100644
index 0000000..faaacbd
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceResolverFactory.java
@@ -0,0 +1,122 @@
+/*
+ * 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.api.resource;
+
+import java.util.Map;
+
+/**
+ * The <code>ResourceResolverFactory</code> defines the service API to get and
+ * create <code>ResourceResolver</code>s.
+ * <p>
+ * As soon as the resource resolver is not used anymore,
+ * {@link ResourceResolver#close()} should be called.
+ *
+ * @since 2.1
+ */
+public interface ResourceResolverFactory {
+
+ /**
+ * Name of the authentication information property providing the name of the
+ * user for which the {@link #getResourceResolver(Map)} and
+ * {@link #getAdministrativeResourceResolver(Map)} create resource
+ * resolvers. on whose behalf the request is being handled. This property
+ * may be missing in which case an anonymous (unauthenticated) resource
+ * resolver is returned if possible.
+ * <p>
+ * The type of this property, if present, is <code>String</code>.
+ */
+ String USER = "user.name";
+
+ /**
+ * Name of the authentication information property providing the password of
+ * the user for which to create a resource resolver. If this property is
+ * missing an empty password is assumed.
+ * <p>
+ * The type of this property, if present, is <code>char[]</code>.
+ */
+ String PASSWORD = "user.password";
+
+ /**
+ * Name of the authentication information property causing the
+ * {@link #getResourceResolver(Map)} and
+ * {@link #getAdministrativeResourceResolver(Map)} methods to try to
+ * impersonate the created resource resolver to the requested user and
+ * return the impersonated resource resolver.
+ * <p>
+ * If this impersonation fails the actual creation of the resource resolver
+ * fails.
+ * <p>
+ * If this property is not set in the authentication info or is set to the
+ * same name as the {@link #USER user.name} property this property is
+ * ignored.
+ * <p>
+ * The type of this property, if present, is <code>String</code>.
+ */
+ String USER_IMPERSONATION = "user.impersonation";
+
+ /**
+ * Returns a new {@link ResourceResolver} instance with further
+ * configuration taken from the given <code>authenticationInfo</code> map.
+ * Generally this map will contain a user name and password to authenticate.
+ * <p>
+ * If the <code>authenticationInfo</code> map is <code>null</code> the
+ * <code>ResourceResolver</code> returned will generally not be
+ * authenticated and only provide minimal privileges, if any at all.
+ *
+ * @param authenticationInfo A map of further credential information which
+ * may be used by the implementation to parameterize how the
+ * resource resolver is created. This may be <code>null</code>.
+ * @return A {@link ResourceResolver} according to the
+ * <code>authenticationInfo</code>.
+ * @throws LoginException If an error occurs creating the new
+ * <code>ResourceResolver</code> with the provided credential
+ * data.
+ */
+ ResourceResolver getResourceResolver(Map<String, Object> authenticationInfo)
+ throws LoginException;
+
+ /**
+ * Returns a new {@link ResourceResolver} instance with administrative
+ * privileges with further configuration taken from the given
+ * <code>authenticationInfo</code> map.
+ * <p>
+ * Note, that if the <code>authenticationInfo</code> map contains the
+ * {@link #USER_IMPERSONATION} attribute the <code>ResourceResolver</code>
+ * returned will only have administrative privileges if the user identified
+ * by the property has administrative privileges.
+ * <p>
+ * <b><i>NOTE: This method is intended for use by infrastructure bundles to
+ * access the repository and provide general services. This method MUST not
+ * be used to handle client requests of whatever kinds. To handle client
+ * requests a regular authenticated resource resolver retrieved
+ * through {@link #getResourceResolver(Map)} must be used.</i></b>
+ *
+ * @param authenticationInfo A map of further credential information which
+ * may be used by the implementation to parameterize how the
+ * resource resolver is created. This may be <code>null</code>.
+ * @return A {@link ResourceResolver} with administrative privileges unless
+ * the {@link #USER_IMPERSONATION} was set in the
+ * <code>authenticationInfo</code>.
+ * @throws LoginException If an error occurs creating the new
+ * <code>ResourceResolver</code> with the provided credential
+ * data.
+ */
+ ResourceResolver getAdministrativeResourceResolver(
+ Map<String, Object> authenticationInfo) throws LoginException;
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceUtil.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceUtil.java
new file mode 100644
index 0000000..43bfddc
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceUtil.java
@@ -0,0 +1,608 @@
+/*
+ * 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.api.resource;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+
+/**
+ * The <code>ResourceUtil</code> class provides helper methods dealing with
+ * resources.
+ */
+public class ResourceUtil {
+
+ /**
+ * Resolves relative path segments '.' and '..' in the absolute path.
+ * Returns null if not possible (.. points above root) or if path is not
+ * absolute.
+ */
+ public static String normalize(String path) {
+
+ // don't care for empty paths
+ if (path.length() == 0) {
+ return path;
+ }
+
+ // prepare the path buffer with trailing slash (simplifies impl)
+ int absOffset = (path.charAt(0) == '/') ? 0 : 1;
+ char[] buf = new char[path.length() + 1 + absOffset];
+ if (absOffset == 1) {
+ buf[0] = '/';
+ }
+ path.getChars(0, path.length(), buf, absOffset);
+ buf[buf.length - 1] = '/';
+
+ int lastSlash = 0; // last slash in path
+ int numDots = 0; // number of consecutive dots after last slash
+
+ int bufPos = 0;
+ for (int bufIdx = lastSlash; bufIdx < buf.length; bufIdx++) {
+ char c = buf[bufIdx];
+ if (c == '/') {
+ if (numDots == 2) {
+ if (bufPos == 0) {
+ return null;
+ }
+
+ do {
+ bufPos--;
+ } while (bufPos > 0 && buf[bufPos] != '/');
+ }
+
+ lastSlash = bufIdx;
+ numDots = 0;
+ } else if (c == '.' && numDots < 2) {
+ numDots++;
+ } else {
+ // find the next slash
+ int nextSlash = bufIdx + 1;
+ while (nextSlash < buf.length && buf[nextSlash] != '/') {
+ nextSlash++;
+ }
+
+ // append up to the next slash (or end of path)
+ if (bufPos < lastSlash) {
+ int segLen = nextSlash - bufIdx + 1;
+ System.arraycopy(buf, lastSlash, buf, bufPos, segLen);
+ bufPos += segLen;
+ } else {
+ bufPos = nextSlash;
+ }
+
+ numDots = 0;
+ lastSlash = nextSlash;
+ bufIdx = nextSlash;
+ }
+ }
+
+ String resolved;
+ if (bufPos == 0 && numDots == 0) {
+ resolved = (absOffset == 0) ? "/" : "";
+ } else if ((bufPos - absOffset) == path.length()) {
+ resolved = path;
+ } else {
+ resolved = new String(buf, absOffset, bufPos - absOffset);
+ }
+
+ return resolved;
+ }
+
+ /**
+ * Utility method returns the parent path of the given <code>path</code>,
+ * which is normalized by {@link #normalize(String)} before resolving the
+ * parent.
+ *
+ * @param path The path whose parent is to be returned.
+ * @return <code>null</code> if <code>path</code> is the root path (
+ * <code>/</code>) or if <code>path</code> is a single name
+ * containing no slash (<code>/</code>) characters.
+ * @throws IllegalArgumentException If the path cannot be normalized by the
+ * {@link #normalize(String)} method.
+ * @throws NullPointerException If <code>path</code> is <code>null</code>.
+ */
+ public static String getParent(String path) {
+ if ("/".equals(path)) {
+ return null;
+ }
+
+ // normalize path (remove . and ..)
+ path = normalize(path);
+
+ // if normalized to root, there is no parent
+ if (path == null || "/".equals(path)) {
+ return null;
+ }
+
+ String workspaceName = null;
+
+ final int wsSepPos = path.indexOf(":/");
+ if (wsSepPos != -1) {
+ workspaceName = path.substring(0, wsSepPos);
+ path = path.substring(wsSepPos + 1);
+ }
+
+ // find the last slash, after which to cut off
+ int lastSlash = path.lastIndexOf('/');
+ if (lastSlash < 0) {
+ // no slash in the path
+ return null;
+ } else if (lastSlash == 0) {
+ // parent is root
+ if (workspaceName != null) {
+ return workspaceName + ":/";
+ }
+ return "/";
+ }
+
+ String parentPath = path.substring(0, lastSlash);
+ if (workspaceName != null) {
+ return workspaceName + ":" + parentPath;
+ }
+ return parentPath;
+ }
+
+ /**
+ * Utility method returns the ancestor's path at the given <code>level</code>
+ * relative to <code>path</code>, which is normalized by {@link #normalize(String)}
+ * before resolving the ancestor.
+ *
+ * <ul>
+ * <li><code>level</code> = 0 returns the <code>path</code>.</li>
+ * <li><code>level</code> = 1 returns the parent of <code>path</code>, if it exists, <code>null</code> otherwise.</li>
+ * <li><code>level</code> = 2 returns the grandparent of <code>path</code>, if it exists, <code>null</code> otherwise.</li>
+ * </ul>
+ *
+ * @param path The path whose ancestor is to be returned.
+ * @param level The relative level of the ancestor, relative to <code>path</code>.
+ * @return <code>null</code> if <code>path</code> doesn't have an ancestor at the
+ * specified <code>level</code>.
+ * @throws IllegalArgumentException If the path cannot be normalized by the
+ * {@link #normalize(String)} method or if <code>level</code> < 0.
+ * @throws NullPointerException If <code>path</code> is <code>null</code>.
+ * @since 2.2
+ */
+ public static String getParent(final String path, final int level) {
+ if ( level < 0 ) {
+ throw new IllegalArgumentException("level must be non-negative");
+ }
+ String result = path;
+ for(int i=0; i<level; i++) {
+ result = getParent(result);
+ if ( result == null ) {
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Utility method returns the parent resource of the resource.
+ *
+ * @throws NullPointerException If <code>rsrc</code> is <code>null</code>.
+ * @return The parent resource or null if the rsrc is the root.
+ * @deprecated since 2.1.0, use {@link Resource#getParent()} instead
+ */
+ @Deprecated
+ public static Resource getParent(Resource rsrc) {
+ return rsrc.getParent();
+ }
+
+ /**
+ * Utility method returns the name of the resource.
+ *
+ * @throws NullPointerException If <code>rsrc</code> is <code>null</code>.
+ * @deprecated since 2.1.0, use {@link Resource#getName()} instead
+ */
+ @Deprecated
+ public static String getName(Resource rsrc) {
+ /*
+ * Same as AbstractResource.getName() implementation to prevent problems
+ * if there are implementations of the pre-2.1.0 Resource interface in
+ * the framework.
+ */
+ return getName(rsrc.getPath());
+ }
+
+ /**
+ * Utility method returns the name of the given <code>path</code>, which is
+ * normalized by {@link #normalize(String)} before resolving the name.
+ *
+ * @param path The path whose name (the last path element) is to be
+ * returned.
+ * @return The empty string if <code>path</code> is the root path (
+ * <code>/</code>) or if <code>path</code> is a single name
+ * containing no slash (<code>/</code>) characters.
+ * @throws IllegalArgumentException If the path cannot be normalized by the
+ * {@link #normalize(String)} method.
+ * @throws NullPointerException If <code>path</code> is <code>null</code>.
+ */
+ public static String getName(String path) {
+ if ("/".equals(path)) {
+ return "";
+ }
+
+ // normalize path (remove . and ..)
+ path = normalize(path);
+ if ("/".equals(path)) {
+ return "";
+ }
+
+ // find the last slash
+ return path.substring(path.lastIndexOf('/') + 1);
+ }
+
+ /**
+ * Returns <code>true</code> if the resource <code>res</code> is a synthetic
+ * resource.
+ * <p>
+ * This method checks whether the resource is an instance of the
+ * <code>org.apache.sling.resource.SyntheticResource</code> class.
+ *
+ * @param res The <code>Resource</code> to check whether it is a synthetic
+ * resource.
+ * @return <code>true</code> if <code>res</code> is a synthetic resource.
+ * <code>false</code> is returned if <code>res</code> is
+ * <code>null</code> or not an instance of the
+ * <code>org.apache.sling.resource.SyntheticResource</code> class.
+ */
+ public static boolean isSyntheticResource(Resource res) {
+ if (res instanceof SyntheticResource) {
+ return true;
+ }
+
+ if (!(res instanceof ResourceWrapper)) {
+ return false;
+ }
+
+ do {
+ res = ((ResourceWrapper) res).getResource();
+ } while (res instanceof ResourceWrapper);
+
+ return res instanceof SyntheticResource;
+ }
+
+ /**
+ * Returns <code>true</code> if the resource <code>res</code> is a "star
+ * resource". A <i>star resource</i> is a resource returned from the
+ * <code>ResourceResolver.resolve(HttpServletRequest)</code> whose path
+ * terminates in a <code>/*</code>. Generally such resource result from
+ * requests to something like <code>/some/path/*</code> or
+ * <code>/some/path/*.html</code> which may be used web applications to
+ * uniformly handle resources to be created.
+ * <p>
+ * This method checks whether the resource path ends with a <code>/*</code>
+ * indicating such a star resource.
+ *
+ * @param res The <code>Resource</code> to check whether it is a star
+ * resource.
+ * @return <code>true</code> if <code>res</code> is to be considered a star
+ * resource.
+ * @throws NullPointerException if <code>res</code> is <code>null</code>.
+ */
+ public static boolean isStarResource(Resource res) {
+ return res.getPath().endsWith("/*");
+ }
+
+ /**
+ * Returns <code>true</code> if the resource <code>res</code> is a
+ * non-existing resource.
+ * <p>
+ * This method checks the resource type of the resource to match the
+ * well-known resource type <code>sling:nonexisting</code> of the
+ * <code>NonExistingResource</code> class defined in the Sling API.
+ *
+ * @param res The <code>Resource</code> to check whether it is a
+ * non-existing resource.
+ * @return <code>true</code> if <code>res</code> is to be considered a
+ * non-existing resource.
+ * @throws NullPointerException if <code>res</code> is <code>null</code>.
+ */
+ public static boolean isNonExistingResource(Resource res) {
+ return Resource.RESOURCE_TYPE_NON_EXISTING.equals(res.getResourceType());
+ }
+
+ /**
+ * Returns an <code>Iterator</code> of {@link Resource} objects loaded from
+ * the children of the given <code>Resource</code>.
+ * <p>
+ * This is a convenience method for
+ * {@link ResourceResolver#listChildren(Resource)}.
+ *
+ * @param parent The {@link Resource Resource} whose children are requested.
+ * @return An <code>Iterator</code> of {@link Resource} objects.
+ * @throws NullPointerException If <code>parent</code> is <code>null</code>.
+ * @throws org.apache.sling.api.SlingException If any error occurs acquiring
+ * the child resource iterator.
+ * @see ResourceResolver#listChildren(Resource)
+ * @deprecated since 2.1.0, use {@link Resource#listChildren()} instead
+ */
+ @Deprecated
+ public static Iterator<Resource> listChildren(Resource parent) {
+ /*
+ * Same as AbstractResource.listChildren() implementation to prevent
+ * problems if there are implementations of the pre-2.1.0 Resource
+ * interface in the framework.
+ */
+ return parent.getResourceResolver().listChildren(parent);
+ }
+
+ /**
+ * Returns an <code>ValueMap</code> object for the given
+ * <code>Resource</code>. This method calls {@link Resource#adaptTo(Class)}
+ * with the {@link ValueMap} class as an argument. If the
+ * <code>adaptTo</code> method returns a map, this map is returned. If the
+ * resource is not adaptable to a value map, next an adaption to {@link Map}
+ * is tried and if this is successful the map is wrapped as a value map. If
+ * the adaptions are not successful an empty value map is returned. If
+ * <code>null</code> is provided as the resource an empty map is returned as
+ * well.
+ *
+ * @param res The <code>Resource</code> to adapt to the value map.
+ * @return A value map.
+ */
+ @SuppressWarnings("unchecked")
+ public static ValueMap getValueMap(final Resource res) {
+ // adapt to ValueMap if resource is not null
+ ValueMap valueMap = (res != null) ? res.adaptTo(ValueMap.class) : null;
+
+ // if no resource or no ValueMap adapter, check Map
+ if (valueMap == null) {
+
+ Map map = (res != null) ? res.adaptTo(Map.class) : null;
+
+ // if not even adapting to map, assume an empty map
+ if (map == null) {
+ map = new HashMap<String, Object>();
+ }
+
+ // .. and decorate the plain map
+ valueMap = new ValueMapDecorator(map);
+ }
+
+ return valueMap;
+ }
+
+ /**
+ * Helper method, which returns the given resource type as returned from the
+ * {@link org.apache.sling.api.resource.Resource#getResourceType()} as a
+ * relative path.
+ *
+ * @param type The resource type to be converted into a path
+ * @return The resource type as a path.
+ * @since 2.0.6
+ */
+ public static String resourceTypeToPath(final String type) {
+ return type.replaceAll("\\:", "/");
+ }
+
+ /**
+ * Returns the super type of the given resource type. This method converts
+ * the resource type to a resource path by calling
+ * {@link #resourceTypeToPath(String)} and uses the
+ * <code>resourceResolver</code> to get the corresponding resource. If the
+ * resource exists, the {@link Resource#getResourceSuperType()} method is
+ * called.
+ *
+ * @param resourceResolver The <code>ResourceResolver</code> used to access
+ * the resource whose path (relative or absolute) is given by the
+ * <code>resourceType</code> parameter.
+ * @param resourceType The resource type whose super type is to be returned.
+ * This type is turned into a path by calling the
+ * {@link #resourceTypeToPath(String)} method before trying to
+ * get the resource through the <code>resourceResolver</code>.
+ * @return the super type of the <code>resourceType</code> or
+ * <code>null</code> if the resource type does not exists or returns
+ * <code>null</code> for its super type.
+ * @since 2.0.6
+ * @deprecated Use {@link ResourceResolver#getParentResourceType(String)}
+ */
+ @Deprecated
+ public static String getResourceSuperType(
+ final ResourceResolver resourceResolver, final String resourceType) {
+ return resourceResolver.getParentResourceType(resourceType);
+ }
+
+ /**
+ * Returns the super type of the given resource. This method checks first if
+ * the resource itself knows its super type by calling
+ * {@link Resource#getResourceSuperType()}. If that returns
+ * <code>null</code> {@link #getResourceSuperType(ResourceResolver, String)}
+ * is invoked with the resource type of the resource.
+ *
+ * @param resource The resource to return the resource super type for.
+ * @return the super type of the <code>resource</code> or <code>null</code>
+ * if no super type could be computed.
+ * @since 2.0.6
+ * @deprecated Use {@link ResourceResolver#getParentResourceType(Resource)}
+ */
+ @Deprecated
+ public static String findResourceSuperType(final Resource resource) {
+ if ( resource == null ) {
+ return null;
+ }
+ return resource.getResourceResolver().getParentResourceType(resource);
+ }
+
+ /**
+ * Check if the resource is of the given type. This method first checks the
+ * resource type of the resource, then its super resource type and continues
+ * to go up the resource super type hierarchy.
+ *
+ * @param resource the resource to check
+ * @param resourceType the resource type to check the resource against
+ * @return <code>false</code> if <code>resource</code> is <code>null</code>.
+ * Otherwise returns the result of calling
+ * {@link Resource#isResourceType(String)} with the given
+ * <code>resourceType</code>.
+ * @since 2.0.6
+ * @deprecated Use {@link ResourceResolver#isResourceType(Resource, String)}
+ */
+ @Deprecated
+ public static boolean isA(final Resource resource, final String resourceType) {
+ if ( resource == null ) {
+ return false;
+ }
+ return resource.getResourceResolver().isResourceType(resource, resourceType);
+ }
+
+ /**
+ * Return an iterator for objects of the specified type. A new iterator is
+ * returned which tries to adapt the provided resources to the given type
+ * (using {@link Resource#adaptTo(Class)}. If a resource in the original
+ * iterator is not adaptable to the given class, this object is skipped.
+ * This implies that the number of objects returned by the new iterator
+ * might be less than the number of resource objects.
+ *
+ * @param iterator A resource iterator.
+ * @param <T> The adapted type
+ * @since 2.0.6
+ */
+ public static <T> Iterator<T> adaptTo(final Iterator<Resource> iterator,
+ final Class<T> type) {
+ return new Iterator<T>() {
+
+ private T nextObject = seek();
+
+ public boolean hasNext() {
+ return nextObject != null;
+ }
+
+ public T next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ final T object = nextObject;
+ nextObject = seek();
+ return object;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ private T seek() {
+ T result = null;
+ while (result == null && iterator.hasNext()) {
+ final Resource r = iterator.next();
+ result = r.adaptTo(type);
+ }
+ return result;
+ }
+ };
+ }
+
+ /**
+ * Creates or gets the resource at the given path.
+ *
+ * @param resolver The resource resolver to use for creation
+ * @param path The full path to be created
+ * @param resourceType The optional resource type of the final resource to create
+ * @param intermediateResourceType THe optional resource type of all intermediate resources
+ * @param autoCommit If set to true, a commit is performed after each resource creation.
+ * @since 2.3.0
+ */
+ public static Resource getOrCreateResource(
+ final ResourceResolver resolver,
+ final String path,
+ final String resourceType,
+ final String intermediateResourceType,
+ final boolean autoCommit)
+ throws PersistenceException {
+ final Map<String, Object> props;
+ if ( resourceType == null ) {
+ props = null;
+ } else {
+ props = Collections.singletonMap(ResourceResolver.PROPERTY_RESOURCE_TYPE, (Object)resourceType);
+ }
+ return getOrCreateResource(resolver, path, props, intermediateResourceType, autoCommit);
+ }
+
+ /**
+ * Creates or gets the resource at the given path.
+ *
+ * @param resolver The resource resolver to use for creation
+ * @param path The full path to be created
+ * @param resourceProperties The optional resource properties of the final resource to create
+ * @param intermediateResourceType THe optional resource type of all intermediate resources
+ * @param autoCommit If set to true, a commit is performed after each resource creation.
+ * @since 2.3.0
+ */
+ public static Resource getOrCreateResource(
+ final ResourceResolver resolver,
+ final String path,
+ final Map<String, Object> resourceProperties,
+ final String intermediateResourceType,
+ final boolean autoCommit)
+ throws PersistenceException {
+ Resource rsrc = resolver.getResource(path);
+ if ( rsrc == null ) {
+ final int lastPos = path.lastIndexOf('/');
+ final String name = path.substring(lastPos + 1);
+
+ final Resource parentResource;
+ if ( lastPos == 0 ) {
+ parentResource = resolver.getResource("/");
+ } else {
+ final String parentPath = path.substring(0, lastPos);
+ parentResource = getOrCreateResource(resolver,
+ parentPath,
+ intermediateResourceType,
+ intermediateResourceType,
+ autoCommit);
+ }
+ if ( autoCommit ) {
+ resolver.refresh();
+ }
+ try {
+ rsrc = resolver.create(parentResource, name, resourceProperties);
+ } catch ( final PersistenceException pe ) {
+ // this could be thrown because someone else tried to create this
+ // node concurrently
+ resolver.refresh();
+ rsrc = resolver.getResource(parentResource, name);
+ if ( rsrc == null ) {
+ throw pe;
+ }
+ }
+ if ( autoCommit ) {
+ try {
+ resolver.commit();
+ resolver.refresh();
+ rsrc = resolver.getResource(parentResource, name);
+ } catch ( final PersistenceException pe ) {
+ // try again - maybe someone else did create the resource in the meantime
+ // or we ran into Jackrabbit's stale item exception in a clustered environment
+ resolver.revert();
+ resolver.refresh();
+ rsrc = resolver.getResource(parentResource, name);
+ if ( rsrc == null ) {
+ rsrc = resolver.create(parentResource, name, resourceProperties);
+ resolver.commit();
+ }
+ }
+ }
+ }
+ return rsrc;
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceWrapper.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceWrapper.java
new file mode 100644
index 0000000..dfbab66
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ResourceWrapper.java
@@ -0,0 +1,173 @@
+/*
+ * 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.api.resource;
+
+import java.util.Iterator;
+
+/**
+ * The <code>ResourceWrapper</code> is a wrapper for any <code>Resource</code>
+ * delegating all method calls to the wrapped resource by default. Extensions of
+ * this class may overwrite any method to return different values as
+ * appropriate.
+ */
+public class ResourceWrapper implements Resource {
+
+ /** the wrapped resource */
+ private final Resource resource;
+
+ /**
+ * Creates a new wrapper instance delegating all method calls to the given
+ * <code>resource</code>.
+ */
+ public ResourceWrapper(final Resource resource) {
+ this.resource = resource;
+ }
+
+ /**
+ * Returns the <code>Resource</code> wrapped by this instance. This method
+ * can be overwritten by subclasses if required. All methods implemented by
+ * this class use this method to get the resource object.
+ */
+ public Resource getResource() {
+ return resource;
+ }
+
+ /**
+ * Returns the value of calling <code>getPath</code> on the
+ * {@link #getResource() wrapped resource}.
+ */
+ public String getPath() {
+ return getResource().getPath();
+ }
+
+ /**
+ * Returns the value of calling <code>getName</code> on the
+ * {@link #getResource() wrapped resource}.
+ *
+ * @since 2.1.0
+ */
+ public String getName() {
+ return getResource().getName();
+ }
+
+ /**
+ * Returns the value of calling <code>getParent</code> on the
+ * {@link #getResource() wrapped resource}.
+ *
+ * @since 2.1.0
+ */
+ public Resource getParent() {
+ return getResource().getParent();
+ }
+
+ /**
+ * Returns the value of calling <code>getChild</code> on the
+ * {@link #getResource() wrapped resource}.
+ *
+ * @since 2.1.0
+ */
+ public Resource getChild(String relPath) {
+ return getResource().getChild(relPath);
+ }
+
+ /**
+ * Returns the value of calling <code>listChildren</code> on the
+ * {@link #getResource() wrapped resource}.
+ *
+ * @since 2.1.0
+ */
+ public Iterator<Resource> listChildren() {
+ return getResource().listChildren();
+ }
+
+ /**
+ * @see org.apache.sling.api.resource.Resource#getChildren()
+ */
+ public Iterable<Resource> getChildren() {
+ return getResource().getChildren();
+ }
+
+ /**
+ * Returns the value of calling <code>getResourceMetadata</code> on the
+ * {@link #getResource() wrapped resource}.
+ */
+ public ResourceMetadata getResourceMetadata() {
+ return getResource().getResourceMetadata();
+ }
+
+ /**
+ * Returns the value of calling <code>getResourceResolver</code> on the
+ * {@link #getResource() wrapped resource}.
+ */
+ public ResourceResolver getResourceResolver() {
+ return getResource().getResourceResolver();
+ }
+
+ /**
+ * Returns the value of calling <code>getResourceType</code> on the
+ * {@link #getResource() wrapped resource}.
+ */
+ public String getResourceType() {
+ return getResource().getResourceType();
+ }
+
+ /**
+ * Returns the value of calling <code>getResourceSuperType</code> on the
+ * {@link #getResource() wrapped resource}.
+ */
+ public String getResourceSuperType() {
+ return getResource().getResourceSuperType();
+ }
+
+ /**
+ * Returns the value of calling <code>isResourceType</code> on the
+ * {@link #getResource() wrapped resource}.
+ *
+ * @since 2.1.0
+ */
+ public boolean isResourceType(final String resourceType) {
+ return getResource().isResourceType(resourceType);
+ }
+
+ /**
+ * Returns the value of calling <code>adaptTo</code> on the
+ * {@link #getResource() wrapped resource}.
+ */
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ return getResource().adaptTo(type);
+ }
+
+ /**
+ * Returns a string representation of this wrapper consisting of the class'
+ * simple name, the {@link #getResourceType() resource type} and
+ * {@link #getPath() path} as well as the string representation of the
+ * {@link #getResource() wrapped resource}.
+ */
+ @Override
+ public String toString() {
+ final String className;
+ if (getClass().getSimpleName().length() == 0) {
+ className = getClass().getName();
+ } else {
+ className = getClass().getSimpleName();
+ }
+ return className + ", type=" + getResourceType()
+ + ", path=" + getPath() + ", resource=[" + getResource() + "]";
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/SyntheticResource.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/SyntheticResource.java
new file mode 100644
index 0000000..2aa967e
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/SyntheticResource.java
@@ -0,0 +1,109 @@
+/*
+ * 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.api.resource;
+
+
+/**
+ * The <code>SyntheticResource</code> class is a simple implementation of the
+ * <code>Resource</code> interface which may be used to provide a resource
+ * object which has no actual resource data.
+ */
+public class SyntheticResource extends AbstractResource {
+
+ /** The resource resolver to which this resource is related */
+ private final ResourceResolver resourceResolver;
+
+ /** The path of the synthetic resource */
+ private final String path;
+
+ /** The type this synthetic resource assumes */
+ private final String resourceType;
+
+ /** The metadata of this resource just containing the resource path */
+ private final ResourceMetadata resourceMetadata;
+
+ /**
+ * Creates a synthetic resource with the given <code>path</code> and
+ * <code>resourceType</code>.
+ */
+ public SyntheticResource(ResourceResolver resourceResolver, String path,
+ String resourceType) {
+ this.resourceResolver = resourceResolver;
+ this.path = path;
+ this.resourceType = resourceType;
+ this.resourceMetadata = new ResourceMetadata();
+ this.resourceMetadata.setResolutionPath(path);
+ }
+
+ /**
+ * Creates a synthetic resource with the given <code>ResourceMetadata</code>
+ * and <code>resourceType</code>.
+ */
+ public SyntheticResource(ResourceResolver resourceResolver, ResourceMetadata rm,
+ String resourceType) {
+ this.resourceResolver = resourceResolver;
+ this.path = rm.getResolutionPath();
+ this.resourceType = resourceType;
+ this.resourceMetadata = rm;
+ }
+
+ /**
+ * @see org.apache.sling.api.resource.Resource#getPath()
+ */
+ public String getPath() {
+ return path;
+ }
+
+ /**
+ * @see org.apache.sling.api.resource.Resource#getResourceType()
+ */
+ public String getResourceType() {
+ return resourceType;
+ }
+
+ /**
+ * Synthetic resources by default do not have a resource super type.
+ */
+ public String getResourceSuperType() {
+ return null;
+ }
+
+ /**
+ * Returns a resource metadata object containing just the path of this
+ * resource as the {@link ResourceMetadata#RESOLUTION_PATH} property.
+ */
+ public ResourceMetadata getResourceMetadata() {
+ return resourceMetadata;
+ }
+
+ /**
+ * Returns the {@link ResourceResolver} with which this synthetic resource
+ * is related or <code>null</code> if none.
+ */
+ public ResourceResolver getResourceResolver() {
+ return resourceResolver;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + ", type=" + getResourceType()
+ + ", path=" + getPath();
+ }
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ValueMap.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ValueMap.java
new file mode 100644
index 0000000..be30c16
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/ValueMap.java
@@ -0,0 +1,69 @@
+/*
+ * 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.api.resource;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+
+/**
+ * The <code>ValueMap</code> is an easy way to access properties of a resource.
+ * With most resources you can use {@link Resource#adaptTo(Class)} to adapt the
+ * resource to a value map. The various getter methods can be used to get the
+ * properties of the resource.
+ */
+public interface ValueMap extends Map<String, Object> {
+
+ /**
+ * Empty immutable value map.
+ */
+ final ValueMap EMPTY = new ValueMapDecorator(
+ Collections.<String, Object> emptyMap());
+
+ /**
+ * Get a named property and convert it into the given type.
+ * This method does not support conversion into a primitive type or an
+ * array of a primitive type. It should return <code>null</code> in this
+ * case.
+ *
+ * @param name The name of the property
+ * @param type The class of the type
+ * @return Return named value converted to type T or <code>null</code> if
+ * non existing or can't be converted.
+ */
+ <T> T get(String name, Class<T> type);
+
+ /**
+ * Get a named property and convert it into the given type.
+ * This method does not support conversion into a primitive type or an
+ * array of a primitive type. It should return the default value in this
+ * case.
+ *
+ * @param name The name of the property
+ * @param defaultValue The default value to use if the named property does
+ * not exist or cannot be converted to the requested type. The
+ * default value is also used to define the type to convert the
+ * value to. If this is <code>null</code> any existing property is
+ * not converted.
+ * @return Return named value converted to type T or the default value if
+ * non existing or can't be converted.
+ */
+ <T> T get(String name, T defaultValue);
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/package-info.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/package-info.java
new file mode 100644
index 0000000..b114094
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/resource/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("2.3.2")
+package org.apache.sling.api.resource;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/InvalidServiceFilterSyntaxException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/InvalidServiceFilterSyntaxException.java
new file mode 100644
index 0000000..cb45f29
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/InvalidServiceFilterSyntaxException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.api.scripting;
+
+import org.apache.sling.api.SlingException;
+
+/** Thrown when an invalid service filter is used */
+public class InvalidServiceFilterSyntaxException extends SlingException {
+
+ private static final long serialVersionUID = 6557699360505403255L;
+
+ private final String filter;
+
+ public InvalidServiceFilterSyntaxException(String filter, String reason) {
+ super(reason);
+
+ this.filter = filter;
+ }
+
+ public InvalidServiceFilterSyntaxException(String filter, String reason,
+ Throwable cause) {
+ super(reason, cause);
+
+ this.filter = filter;
+ }
+
+ public String getFilter() {
+ return filter;
+ }
+}
\ No newline at end of file
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/ScriptEvaluationException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/ScriptEvaluationException.java
new file mode 100644
index 0000000..753882b
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/ScriptEvaluationException.java
@@ -0,0 +1,50 @@
+/*
+ * 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.api.scripting;
+
+import org.apache.sling.api.SlingException;
+
+/**
+ * The <code>ScriptEvaluationException</code> is thrown by the
+ * {@link SlingScript#eval(SlingBindings)} method if an error occurrs evaluating
+ * the script.
+ */
+public class ScriptEvaluationException extends SlingException {
+
+ private static final long serialVersionUID = -274759591325189020L;
+
+ private final String scriptName;
+
+ public ScriptEvaluationException(String scriptName, String message) {
+ super(message);
+
+ this.scriptName = scriptName;
+ }
+
+ public ScriptEvaluationException(String scriptName, String message,
+ Throwable cause) {
+ super(message, cause);
+
+ this.scriptName = scriptName;
+ }
+
+ public String getScriptName() {
+ return scriptName;
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingBindings.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingBindings.java
new file mode 100644
index 0000000..451d551
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingBindings.java
@@ -0,0 +1,303 @@
+/*
+ * 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.api.scripting;
+
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.util.HashMap;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.Resource;
+import org.slf4j.Logger;
+
+/**
+ * The <code>SlingBindings</code> class is used to prepare global variables
+ * for script execution. The constants in this class define names of variables
+ * which <em>MUST</em> or <em>MAY</em> be provided for the script execution.
+ * Other variables may be define as callers see fit.
+ */
+public class SlingBindings extends HashMap<String, Object> {
+
+ private static final long serialVersionUID = 209505693646323450L;
+
+ /**
+ * The name of the global scripting variable providing the
+ * {@link org.apache.sling.api.SlingHttpServletRequest} object (value is
+ * "request"). The value of the scripting variable is the same as that
+ * returned by the
+ * {@link org.apache.sling.api.scripting.SlingScriptHelper#getRequest()}
+ * method.
+ * <p>
+ * This bound variable is required in the bindings given the script.
+ */
+ public static final String REQUEST = "request";
+
+ /**
+ * The name of the global scripting variable providing the
+ * {@link org.apache.sling.api.SlingHttpServletResponse} object (value is
+ * "response"). The value of the scripting variable is the same as that
+ * returned by the
+ * {@link org.apache.sling.api.scripting.SlingScriptHelper#getResponse()}
+ * method.
+ * <p>
+ * This bound variable is required in the bindings given the script.
+ */
+ public static final String RESPONSE = "response";
+
+ /**
+ * The name of the global scripting variable providing the
+ * {@link java.io.Reader} object (value is "reader").
+ * <p>
+ * This bound variable is required in the bindings given the script.
+ */
+ public static final String READER = "reader";
+
+ /**
+ * The name of the global scripting variable providing the
+ * {@link org.apache.sling.api.scripting.SlingScriptHelper} for the request
+ * (value is "sling").
+ * <p>
+ * This bound variable is optional. If existing, the script helper instance
+ * must be bound to the same request and response objects as bound with the
+ * {@link #REQUEST} and {@link #RESPONSE} variables. If this variable is not
+ * bound, the script implementation will create it before actually
+ * evaluating the script.
+ */
+ public static final String SLING = "sling";
+
+ /**
+ * The name of the global scripting variable providing the
+ * {@link org.apache.sling.api.resource.Resource} object (value is
+ * "resource"). The value of the scripting variable is the same as that
+ * returned by the <code>SlingScriptHelper.getRequest().getResource()</code>
+ * method.
+ * <p>
+ * This bound variable is optional. If existing, the resource must be bound
+ * to the same resource as returned by the
+ * <code>SlingHttpServletRequest.getResource()</code> method. If this
+ * variable is not bound, the script implementation will bind it before
+ * actually evaluating the script.
+ */
+ public static final String RESOURCE = "resource";
+
+ /**
+ * The name of the global scripting variable providing the
+ * <code>java.io.PrintWriter</code> object to return the response content
+ * (value is "out"). The value of the scripting variable is the same as that
+ * returned by the <code>SlingScriptHelper.getResponse().getWriter()</code>
+ * method.
+ * <p>
+ * Note, that it may be advisable to implement a lazy acquiring writer for
+ * the <em>out</em> variable to enable the script to write binary data to
+ * the response output stream instead of the writer.
+ * <p>
+ * This bound variable is optional. If existing, the resource must be bound
+ * to the same writer as returned by the
+ * <code>SlingHttpServletResponse.getWriter()</code> method of the
+ * response object bound to the {@link #RESPONSE} variable. If this variable
+ * is not bound, the script implementation will bind it before actually
+ * evaluating the script.
+ */
+ public static final String OUT = "out";
+
+ /**
+ * The name of the global scripting variable indicating whether the output
+ * used by the script should be flushed after the script evaluation ended
+ * normally (value is "flush").
+ * <p>
+ * The type of this variable is <code>java.lang.Boolean</code> indicating
+ * whether to flush the output (value is <code>TRUE</code>) or not (value
+ * is <code>FALSE</code>). If the variable has a non-<code>null</code>
+ * value of another type, the output is not flush as if the value would be
+ * <code>FALSE</code>.
+ */
+ public static final String FLUSH = "flush";
+
+ /**
+ * The name of the global scripting variable providing a logger which may be
+ * used for logging purposes (value is "log"). The logger provides the API
+ * defined by the SLF4J <code>org.slf4j.Logger</code> interface.
+ * <p>
+ * This bound variable is optional. If this variable is not bound, the
+ * script implementation will bind it before actually evaluating the script.
+ */
+ public static final String LOG = "log";
+
+ /**
+ * Helper method to get an object with a given type from this map.
+ * @return The searched object if it has the specified type, otherwise <code>null</code> is returned.
+ */
+ @SuppressWarnings("unchecked")
+ protected <ObjectType> ObjectType get(final String key, final Class<ObjectType> type) {
+ final Object o = this.get(key);
+ if ( type.isInstance(o) ) {
+ return (ObjectType)o;
+ }
+ return null;
+ }
+
+ /**
+ * Helper method which invokes {@link #put(Object, Object)} only if the value is not null.
+ */
+ protected void safePut(final String key, final Object value) {
+ if ( value != null ) {
+ this.put(key, value);
+ }
+ }
+
+ /**
+ * Sets the {@link #FLUSH} property to <code>flush</code>.
+ */
+ public void setFlush(boolean flush) {
+ put(FLUSH, flush);
+ }
+
+ /**
+ * Returns the {@link #FLUSH} property if not <code>null</code> and a
+ * <code>boolean</code>. Otherwise <code>false</code> is returned.
+ */
+ public boolean getFlush() {
+ Boolean value = this.get(FLUSH, Boolean.class);
+ if (value != null ) {
+ return value;
+ }
+
+ return false;
+ }
+
+ /**
+ * Sets the {@link #LOG} property to <code>log</code> if not
+ * <code>null</code>.
+ */
+ public void setLog(Logger log) {
+ this.safePut(LOG, log);
+ }
+
+ /**
+ * Returns the {@link #LOG} property if not <code>null</code> and a
+ * <code>org.slf4j.Logger</code> instance. Otherwise <code>null</code>
+ * is returned.
+ */
+ public Logger getLog() {
+ return this.get(LOG, Logger.class);
+ }
+
+ /**
+ * Sets the {@link #OUT} property to <code>out</code> if not
+ * <code>null</code>.
+ */
+ public void setOut(PrintWriter out) {
+ this.safePut(OUT, out);
+ }
+
+ /**
+ * Returns the {@link #OUT} property if not <code>null</code> and a
+ * <code>PrintWriter</code> instance. Otherwise <code>null</code> is
+ * returned.
+ */
+ public PrintWriter getOut() {
+ return this.get(OUT, PrintWriter.class);
+ }
+
+ /**
+ * Sets the {@link #REQUEST} property to <code>request</code> if not
+ * <code>null</code>.
+ */
+ public void setRequest(SlingHttpServletRequest request) {
+ this.safePut(REQUEST, request);
+ }
+
+ /**
+ * Returns the {@link #REQUEST} property if not <code>null</code> and a
+ * <code>SlingHttpServletRequest</code> instance. Otherwise
+ * <code>null</code> is returned.
+ */
+ public SlingHttpServletRequest getRequest() {
+ return this.get(REQUEST, SlingHttpServletRequest.class);
+ }
+
+ /**
+ * Sets the {@link #READER} property to <code>reader</code> if not
+ * <code>null</code>.
+ */
+ public void setReader(Reader reader) {
+ this.safePut(READER, reader);
+ }
+
+ /**
+ * Returns the {@link #READER} property if not <code>null</code> and a
+ * <code>Reader</code> instance. Otherwise <code>null</code> is
+ * returned.
+ */
+ public Reader getReader() {
+ return this.get(READER, Reader.class);
+ }
+
+ /**
+ * Sets the {@link #RESOURCE} property to <code>resource</code> if not
+ * <code>null</code>.
+ */
+ public void setResource(Resource resource) {
+ this.safePut(RESOURCE, resource);
+ }
+
+ /**
+ * Returns the {@link #RESOURCE} property if not <code>null</code> and a
+ * <code>Resource</code> instance. Otherwise <code>null</code> is
+ * returned.
+ */
+ public Resource getResource() {
+ return this.get(RESOURCE, Resource.class);
+ }
+
+ /**
+ * Sets the {@link #RESPONSE} property to <code>response</code> if not
+ * <code>null</code>.
+ */
+ public void setResponse(SlingHttpServletResponse response) {
+ this.safePut(RESPONSE, response);
+ }
+
+ /**
+ * Returns the {@link #RESPONSE} property if not <code>null</code> and a
+ * <code>SlingHttpServletResponse</code> instance. Otherwise
+ * <code>null</code> is returned.
+ */
+ public SlingHttpServletResponse getResponse() {
+ return this.get(RESPONSE, SlingHttpServletResponse.class);
+ }
+
+ /**
+ * Sets the {@link #SLING} property to <code>sling</code> if not
+ * <code>null</code>.
+ */
+ public void setSling(SlingScriptHelper sling) {
+ this.safePut(SLING, sling);
+ }
+
+ /**
+ * Returns the {@link #SLING} property if not <code>null</code> and a
+ * <code>SlingScriptHelper</code> instance. Otherwise <code>null</code>
+ * is returned.
+ */
+ public SlingScriptHelper getSling() {
+ return this.get(SLING, SlingScriptHelper.class);
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScript.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScript.java
new file mode 100644
index 0000000..a210eec
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScript.java
@@ -0,0 +1,72 @@
+/*
+ * 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.api.scripting;
+
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * The <code>SlingScript</code> defines the API for objects which encapsulate
+ * a script. To evaluate a script prepare a {@link SlingBindings} instance of
+ * variables used as global variables to the script and call the
+ * {@link #eval(SlingBindings)} method.
+ * <p>
+ * You can obtain scripts by resolving a script resource through
+ * {@link org.apache.sling.api.resource.ResourceResolver#resolve(String)}
+ * and then trying to adapt the resource to a script by
+ * calling {@link Resource#adaptTo(Class)}.
+ */
+public interface SlingScript {
+
+ /**
+ * Returns the Resource providing the script source code.
+ */
+ Resource getScriptResource();
+
+ /**
+ * Evaluates this script using the bound variables as global variables to
+ * the script.
+ *
+ * @param props The {@link SlingBindings} providing the bound variables for
+ * evaluating the script. Any bound variables must conform to the
+ * requirements of the {@link SlingBindings} predefined variables
+ * set.
+ * @return The value returned by the script.
+ * @throws ScriptEvaluationException If an error occurrs executing the
+ * script or preparing the script execution. The cause of the
+ * evaluation execption is available as the exception cause.
+ */
+ Object eval(SlingBindings props);
+
+ /**
+ * Evaluates this script using the bound variables as global variables to
+ * the script and then calls the given method with the arguments.
+ *
+ * @param props The {@link SlingBindings} providing the bound variables for
+ * evaluating the script. Any bound variables must conform to the
+ * requirements of the {@link SlingBindings} predefined variables
+ * set.
+ * @param method The name of the method to call.
+ * @param args The arguments for the method call.
+ * @return The value returned by the method from the script.
+ * @throws ScriptEvaluationException If an error occurrs executing the
+ * script or preparing the script execution. The cause of the
+ * evaluation execption is available as the exception cause.
+ */
+ Object call(SlingBindings props, String method, Object... args);
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScriptConstants.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScriptConstants.java
new file mode 100644
index 0000000..9e24ec2
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScriptConstants.java
@@ -0,0 +1,105 @@
+/*
+ * 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.api.scripting;
+
+/**
+ * Some constants for the scripting.
+ * @since 2.0.6
+ */
+public abstract class SlingScriptConstants {
+
+ /**
+ * The name of the script context attribute holding the {@link org.apache.sling.api.resource.ResourceResolver} which
+ * has been used to resolve the script. This resource resolver can be used by the
+ * script engines to further locate scripts (for includes etc.).
+ * The value is set in the {@link SlingScriptConstants#SLING_SCOPE} of the script context.
+ * @since 2.0.6
+ */
+ public static final String ATTR_SCRIPT_RESOURCE_RESOLVER = "org.apache.sling.api.scripting.ScriptResourceResolver";
+
+ /**
+ * The name of the script scope holding the {@link #ATTR_SCRIPT_RESOURCE_RESOLVER}.
+ * @since 2.0.6
+ */
+ public static final int SLING_SCOPE = -314;
+
+ /**
+ * The topic for the OSGi event which is sent when a script engine factory has been added.
+ * The event contains at least the {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_NAME},
+ * {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_VERSION},
+ * {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_EXTENSIONS},
+ * {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_NAME},
+ * {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_VERSION},
+ * and {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_MIME_TYPES} poperties.
+ * @since 2.0.6
+ */
+ public static final String TOPIC_SCRIPT_ENGINE_FACTORY_ADDED = "javax/script/ScriptEngineFactory/ADDED";
+
+ /**
+ * The topic for the OSGi event which is sent when a script engine factory has been removed.
+ * The event contains at least the {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_NAME},
+ * {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_VERSION},
+ * {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_EXTENSIONS},
+ * {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_NAME},
+ * {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_VERSION},
+ * and {@link #PROPERTY_SCRIPT_ENGINE_FACTORY_MIME_TYPES} poperties.
+ * @since 2.0.6
+ */
+ public static final String TOPIC_SCRIPT_ENGINE_FACTORY_REMOVED = "javax/script/ScriptEngineFactory/REMOVED";
+
+ /**
+ * The event property listing the script engine factory name. The value is a string.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_SCRIPT_ENGINE_FACTORY_NAME = "engineName";
+
+ /**
+ * The event property listing the script engine factory name. The value is a string.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_SCRIPT_ENGINE_FACTORY_VERSION = "engineVersion";
+
+ /**
+ * The event property listing the script engine factory extensions. The value is
+ * a string array.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_SCRIPT_ENGINE_FACTORY_EXTENSIONS = "extensions";
+
+ /**
+ * The event property listing the script engine factory language. The value is
+ * a string.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_NAME = "languageName";
+
+ /**
+ * The event property listing the script engine factory language version. The value is
+ * a string.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_VERSION = "languageVersion";
+
+ /**
+ * The event property listing the script engine factory mime types. The value is
+ * a string array.
+ * @since 2.0.6
+ */
+ public static final String PROPERTY_SCRIPT_ENGINE_FACTORY_MIME_TYPES = "mimeTypes";
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScriptHelper.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScriptHelper.java
new file mode 100644
index 0000000..f0e3485
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScriptHelper.java
@@ -0,0 +1,346 @@
+/*
+ * 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.api.scripting;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.resource.Resource;
+
+/**
+ * The <code>SlingScriptHelper</code> interface defines the API of a helper
+ * class which is provided to the scripts called from sling through the global
+ * <code>{@link SlingBindings#SLING sling}</code> variable.
+ */
+public interface SlingScriptHelper {
+
+ /**
+ * Returns the {@link SlingHttpServletRequest} representing the input of the
+ * request.
+ */
+ SlingHttpServletRequest getRequest();
+
+ /**
+ * Returns the {@link SlingHttpServletResponse} representing the output of
+ * the request.
+ */
+ SlingHttpServletResponse getResponse();
+
+ /**
+ * Returns the {@link SlingScript} being called to handle the request.
+ */
+ SlingScript getScript();
+
+ /**
+ * Same as {@link #include(String,RequestDispatcherOptions)}, but using
+ * empty options.
+ *
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the include.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the include.
+ */
+ void include(String path);
+
+ /**
+ * Helper method to include the result of processing the request for the
+ * given <code>path</code> and <code>requestDispatcherOptions</code>.
+ * This method is intended to be implemented as follows:
+ *
+ * <pre>
+ * RequestDispatcher dispatcher = getRequest().getRequestDispatcher(path,
+ * "option:xyz");
+ * if (dispatcher != null) {
+ * dispatcher.include(getRequest(), getResponse());
+ * }
+ * </pre>
+ *
+ * <p>
+ * This method creates a <code>RequestDispatcherOptions</code> object by
+ * calling the
+ * {@link RequestDispatcherOptions#RequestDispatcherOptions(String)}
+ * constructor.
+ *
+ * @param path The path to the resource to include.
+ * @param requestDispatcherOptions influence the rendering of the included
+ * Resource
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the include.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the include.
+ * @see RequestDispatcherOptions#RequestDispatcherOptions(String)
+ * @see #include(String, RequestDispatcherOptions)
+ */
+ void include(String path, String requestDispatcherOptions);
+
+ /**
+ * Helper method to include the result of processing the request for the
+ * given <code>path</code> and <code>options</code>. This method is
+ * intended to be implemented as follows:
+ *
+ * <pre>
+ * RequestDispatcherOptions opts = new RequestDispatcherOptions();
+ * opts.put("option", "xyz");
+ * RequestDispatcher dispatcher = getRequest().getRequestDispatcher(path, opts);
+ * if (dispatcher != null) {
+ * dispatcher.include(getRequest(), getResponse());
+ * }
+ * </pre>
+ *
+ * @param path The path to the resource to include.
+ * @param options influence the rendering of the included Resource
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the include.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the include.
+ * @see RequestDispatcherOptions
+ * @see #include(String, String)
+ */
+ void include(String path, RequestDispatcherOptions options);
+
+ /**
+ * Same as {@link #include(Resource,RequestDispatcherOptions)}, but using
+ * empty options.
+ *
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the include.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the include.
+ */
+ void include(Resource resource);
+
+ /**
+ * Helper method to include the result of processing the request for the
+ * given <code>resource</code> and <code>requestDispatcherOptions</code>.
+ * This method is intended to be implemented as follows:
+ *
+ * <pre>
+ * RequestDispatcher dispatcher = getRequest().getRequestDispatcher(resource,
+ * "option:xyz");
+ * if (dispatcher != null) {
+ * dispatcher.include(getRequest(), getResponse());
+ * }
+ * </pre>
+ *
+ * <p>
+ * This method creates a <code>RequestDispatcherOptions</code> object by
+ * calling the
+ * {@link RequestDispatcherOptions#RequestDispatcherOptions(String)}
+ * constructor.
+ *
+ * @param resource The resource to include.
+ * @param requestDispatcherOptions influence the rendering of the included
+ * Resource
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the include.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the include.
+ * @see RequestDispatcherOptions#RequestDispatcherOptions(String)
+ * @see #include(String, RequestDispatcherOptions)
+ */
+ void include(Resource resource, String requestDispatcherOptions);
+
+ /**
+ * Helper method to include the result of processing the request for the
+ * given <code>resource</code> and <code>options</code>. This method is
+ * intended to be implemented as follows:
+ *
+ * <pre>
+ * RequestDispatcherOptions opts = new RequestDispatcherOptions();
+ * opts.put("option", "xyz");
+ * RequestDispatcher dispatcher = getRequest().getRequestDispatcher(resource, opts);
+ * if (dispatcher != null) {
+ * dispatcher.include(getRequest(), getResponse());
+ * }
+ * </pre>
+ *
+ * @param resource The resource to include.
+ * @param options influence the rendering of the included Resource
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the include.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the include.
+ * @see RequestDispatcherOptions
+ * @see #include(String, String)
+ */
+ void include(Resource resource, RequestDispatcherOptions options);
+
+ /**
+ * Same as {@link #forward(String,RequestDispatcherOptions)}, but using
+ * empty options.
+ *
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the forward.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the forward.
+ */
+ void forward(String path);
+
+ /**
+ * Helper method to forward the request to a Servlet or script for the given
+ * <code>path</code> and <code>requestDispatcherOptions</code>. This method
+ * is intended to be implemented as follows:
+ *
+ * <pre>
+ * RequestDispatcher dispatcher = getRequest().getRequestDispatcher(path,
+ * "option:xyz");
+ * if (dispatcher != null) {
+ * dispatcher.forward(getRequest(), getResponse());
+ * }
+ * </pre>
+ *
+ * <p>
+ * This method creates a <code>RequestDispatcherOptions</code> object by
+ * calling the
+ * {@link RequestDispatcherOptions#RequestDispatcherOptions(String)}
+ * constructor.
+ *
+ * @param path The path to the resource to forward to.
+ * @param requestDispatcherOptions influence the rendering of the forwarded
+ * Resource
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the forward.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the forward.
+ * @see RequestDispatcherOptions#RequestDispatcherOptions(String)
+ * @see #forward(String, RequestDispatcherOptions)
+ */
+ void forward(String path, String requestDispatcherOptions);
+
+ /**
+ * Helper method to forward the request to a Servlet or script for the given
+ * <code>path</code> and <code>options</code>. This method is intended
+ * to be implemented as follows:
+ *
+ * <pre>
+ * RequestDispatcherOptions opts = new RequestDispatcherOptions();
+ * opts.put("option", "xyz");
+ * RequestDispatcher dispatcher = getRequest().getRequestDispatcher(path, opts);
+ * if (dispatcher != null) {
+ * dispatcher.forward(getRequest(), getResponse());
+ * }
+ * </pre>
+ *
+ * @param path The path to the resource to forward the request to.
+ * @param options influence the rendering of the forwarded Resource
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the forward.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the forward.
+ * @throws IllegalStateException If the respoonse has already been committed
+ * @see RequestDispatcherOptions
+ */
+ void forward(String path, RequestDispatcherOptions options);
+
+ /**
+ * Same as {@link #forward(Resource,RequestDispatcherOptions)}, but using
+ * empty options.
+ *
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the forward.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the forward.
+ */
+ void forward(Resource resource);
+
+ /**
+ * Helper method to forward the request to a Servlet or script for the given
+ * <code>resource</code> and <code>requestDispatcherOptions</code>. This method
+ * is intended to be implemented as follows:
+ *
+ * <pre>
+ * RequestDispatcher dispatcher = getRequest().getRequestDispatcher(resource,
+ * "option:xyz");
+ * if (dispatcher != null) {
+ * dispatcher.forward(getRequest(), getResponse());
+ * }
+ * </pre>
+ *
+ * <p>
+ * This method creates a <code>RequestDispatcherOptions</code> object by
+ * calling the
+ * {@link RequestDispatcherOptions#RequestDispatcherOptions(String)}
+ * constructor.
+ *
+ * @param resource The resource to forward to.
+ * @param requestDispatcherOptions influence the rendering of the forwarded
+ * Resource
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the forward.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the forward.
+ * @see RequestDispatcherOptions#RequestDispatcherOptions(String)
+ * @see #forward(String, RequestDispatcherOptions)
+ */
+ void forward(Resource resource, String requestDispatcherOptions);
+
+ /**
+ * Helper method to forward the request to a Servlet or script for the given
+ * <code>resource</code> and <code>options</code>. This method is intended
+ * to be implemented as follows:
+ *
+ * <pre>
+ * RequestDispatcherOptions opts = new RequestDispatcherOptions();
+ * opts.put("option", "xyz");
+ * RequestDispatcher dispatcher = getRequest().getRequestDispatcher(resource, opts);
+ * if (dispatcher != null) {
+ * dispatcher.forward(getRequest(), getResponse());
+ * }
+ * </pre>
+ *
+ * @param resource The resource to forward the request to.
+ * @param options influence the rendering of the forwarded Resource
+ * @throws org.apache.sling.api.SlingIOException Wrapping a <code>IOException</code> thrown
+ * while handling the forward.
+ * @throws org.apache.sling.api.SlingServletException Wrapping a <code>ServletException</code>
+ * thrown while handling the forward.
+ * @throws IllegalStateException If the respoonse has already been committed
+ * @see RequestDispatcherOptions
+ */
+ void forward(Resource resource, RequestDispatcherOptions options);
+
+ /**
+ * Lookup a single service
+ *
+ * @param serviceType The type (interface) of the service.
+ * @return The service instance, or null if the service is not available.
+ */
+ <ServiceType> ServiceType getService(Class<ServiceType> serviceType);
+
+ /**
+ * Lookup one or several services
+ *
+ * @param serviceType The type (interface) of the service.
+ * @param filter An optional filter (LDAP-like, see OSGi spec)
+ * @return The services object or null.
+ * @throws InvalidServiceFilterSyntaxException If the <code>filter</code>
+ * string is not a valid OSGi service filter string.
+ */
+ <ServiceType> ServiceType[] getServices(Class<ServiceType> serviceType,
+ String filter);
+
+ /**
+ * Dispose the helper. This method can be used to clean up the script helper
+ * after the script is run.
+ * @deprecated This method is deprecated since version 2.1 and will be removed.
+ * It should never be called by clients.
+ */
+ @Deprecated
+ void dispose();
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScriptResolver.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScriptResolver.java
new file mode 100644
index 0000000..83b6c03
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/SlingScriptResolver.java
@@ -0,0 +1,49 @@
+/*
+ * 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.api.scripting;
+
+import org.apache.sling.api.resource.ResourceResolver;
+
+/**
+ * The <code>ScriptResolver</code> interface defines the API for a service
+ * capable of locating scripts. Where the script is actually located is an
+ * implementation detail of the service implementation.
+ *
+ * @deprecated The SlingScriptResolver interface is intended to be implemented
+ * and also used by project specific code. To keep the API as clean as possible
+ * this interface was deprecated
+ */
+public interface SlingScriptResolver {
+
+ /**
+ * Finds the {@link SlingScript} for the given name.
+ * <p>
+ * The semantic meaning of the name is implementation specific: It may be an
+ * absolute path to a <code>Resource</code> providing the script source or
+ * it may be a relative path resolved according to some path settings.
+ * Finally, the name may also just be used as an identifier to find the
+ * script in some registry.
+ *
+ * @param resourceResolver The <code>ResourceResolver</code> used to
+ * access the script.
+ * @param name The script name. Must not be <code>null</code>.
+ * @return The {@link SlingScript} to which the name resolved or
+ * <code>null</code> otherwise.
+ * @throws org.apache.sling.api.SlingException If an error occurrs trying to resolve the name.
+ */
+ SlingScript findScript(ResourceResolver resourceResolver, String name);
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/package-info.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/package-info.java
new file mode 100644
index 0000000..fda7cf0
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/scripting/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("2.1")
+package org.apache.sling.api.scripting;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/security/AccessSecurityException.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/security/AccessSecurityException.java
new file mode 100644
index 0000000..46dda29
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/security/AccessSecurityException.java
@@ -0,0 +1,73 @@
+/*
+ * 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.api.security;
+
+/**
+ * Exception thrown by methods of {@link ResourceAccessSecurity} This exception
+ * is used to catch unpredictable situations in methods of
+ * {@link ResourceAccessSecurity}
+ */
+public class AccessSecurityException extends Exception {
+
+ private static final long serialVersionUID = -8388988380137140280L;
+
+ /**
+ * Constructs a new instance of this class with <code>null</code> as its
+ * detail message.
+ */
+ public AccessSecurityException() {
+ super();
+ }
+
+ /**
+ * Constructs a new instance of this class with the specified detail
+ * message.
+ *
+ * @param message
+ * the detail message. The detail message is saved for later
+ * retrieval by the {@link #getMessage()} method.
+ */
+ public AccessSecurityException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new instance of this class with the specified detail message
+ * and root cause.
+ *
+ * @param message
+ * the detail message. The detail message is saved for later
+ * retrieval by the {@link #getMessage()} method.
+ * @param rootCause
+ * root failure cause
+ */
+ public AccessSecurityException(String message, Throwable rootCause) {
+ super(message, rootCause);
+ }
+
+ /**
+ * Constructs a new instance of this class with the specified root cause.
+ *
+ * @param rootCause
+ * root failure cause
+ */
+ public AccessSecurityException(Throwable rootCause) {
+ super(rootCause);
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/security/ResourceAccessSecurity.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/security/ResourceAccessSecurity.java
new file mode 100644
index 0000000..9761fc3
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/security/ResourceAccessSecurity.java
@@ -0,0 +1,88 @@
+/*
+ * 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.api.security;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+/**
+ * The <code>ResourceAccessSecurity</code> defines a service API which might be
+ * used in implementations of resource providers where the underlying
+ * persistence layer does not implement access control. The goal is to make it
+ * easy to implement a lightweight access control in such providers.
+ *
+ * Expected to only be implemented once in the framework/application (much
+ * like the OSGi LogService or ConfigurationAdmin Service) - ResourceProvider
+ * implementations are encouraged to use this service for access control unless
+ * the underlying storage already provides it.
+ *
+ * JCR resource providers should *not* use this - in a JCR context, security is
+ * fully delegated to the underlying repository, and mixing security models would
+ * be a bad idea.
+ */
+public interface ResourceAccessSecurity {
+
+ /** If supplied Resource can be read, return it (or a wrapped
+ * variant of it). The returned Resource should then be used
+ * instead of the one that was passed into the method.
+ * @return null if {@link Resource} cannot be read
+ */
+ Resource getReadableResource(Resource resource);
+
+ /** @return true if a {@link Resource} can be created at the supplied
+ * absolute path. */
+ boolean canCreate(String absPathName, ResourceResolver resourceResolver);
+
+ /** @return true if supplied {@link Resource} can be updated */
+ boolean canUpdate(Resource resource);
+
+ /** @return true if supplied {@link Resource} can be deleted */
+ boolean canDelete(Resource resource);
+
+ /** @return true if supplied {@link Resource} can be executed as a script */
+ boolean canExecute(Resource resource);
+
+ /** @return true if the "valueName" value of supplied {@link Resource} can be read */
+ boolean canReadValue(Resource resource, String valueName);
+
+ /** @return true if the "valueName" value of supplied {@link Resource} can be set */
+ boolean canSetValue(Resource resource, String valueName);
+
+ /** @return true if the "valueName" value of supplied {@link Resource} can be deleted */
+ boolean canDeleteValue(Resource resource, String valueName);
+
+ /**
+ * Optionally transform a query based on the current
+ * user's credentials. Can be used to narrow down queries to omit results
+ * that the current user is not allowed to see anyway, to speed up
+ * downstream access control.
+ *
+ * Query transformations are not critical with respect to access control as results
+ * are filtered downstream using the canRead.. methods.
+ *
+ * @param query the query
+ * @param language the language in which the query is expressed
+ * @param resourceResolver the resource resolver which resolves the query
+ * @return the transformed query
+ * @throws AccessSecurityException
+ */
+ String transformQuery(String query, String language, ResourceResolver resourceResolver)
+ throws AccessSecurityException;
+
+}
\ No newline at end of file
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/security/package-info.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/security/package-info.java
new file mode 100644
index 0000000..2f93562
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/security/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("1.0")
+package org.apache.sling.api.security;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/HtmlResponse.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/HtmlResponse.java
new file mode 100644
index 0000000..798628b
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/HtmlResponse.java
@@ -0,0 +1,496 @@
+/*
+ * 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.api.servlets;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.request.ResponseUtil;
+
+/**
+ * Generator for a HTML status response that displays the changes made in a post
+ * request. see <a href="HtmlResponse.html">HtmlResponse.html</a> for the
+ * format.
+ *
+ * @deprecated use org.apache.sling.servlets.post.HtmlResponse instead.
+ */
+@Deprecated
+public class HtmlResponse {
+
+ /**
+ * some human readable title like: 200 Created /foo/bar
+ */
+ public static final String PN_TITLE = "title";
+
+ /**
+ * status code. more or less http response status codes
+ */
+ public static final String PN_STATUS_CODE = "status.code";
+
+ /**
+ * some human readable status message
+ */
+ public static final String PN_STATUS_MESSAGE = "status.message";
+
+ /**
+ * externally mapped location url of the modified path
+ */
+ public static final String PN_LOCATION = "location";
+
+ /**
+ * externally mapped location url of the parent of the modified path
+ */
+ public static final String PN_PARENT_LOCATION = "parentLocation";
+
+ /**
+ * the path of the modified item. this is usually the addressed resource or
+ * in case of a creation request (eg: /foo/*) the path of the newly created
+ * node.
+ */
+ public static final String PN_PATH = "path";
+
+ /**
+ * the referrer of the request
+ */
+ public static final String PN_REFERER = "referer";
+
+ /**
+ * Indicating whether request processing created new data. This property
+ * is initialized to <code>false</code> and may be changed by calling
+ * the {@link #setCreateRequest(boolean)} method.
+ */
+ public static final String PN_IS_CREATED = "isCreate";
+
+ /**
+ * human readable changelog
+ */
+ public static final String PN_CHANGE_LOG = "changeLog";
+
+ /**
+ * The Throwable caught while processing the request. This property is not
+ * set unless the {@link #setError(Throwable)} method is called.
+ */
+ public static final String PN_ERROR = "error";
+
+ /**
+ * name of the html template
+ */
+ private static final String TEMPLATE_NAME = "HtmlResponse.html";
+
+ /**
+ * list of changes
+ */
+ private final StringBuilder changes = new StringBuilder();
+
+ /**
+ * Properties of the response
+ */
+ private final Map<String, Object> properties = new HashMap<String, Object>();
+
+ /**
+ * Creates a new html response with default settings, which is
+ * <code>null</code> for almost all properties except the
+ * {@link #isCreateRequest()} which defaults to <code>false</code>.
+ */
+ public HtmlResponse() {
+ setCreateRequest(false);
+ }
+
+ // ---------- Settings for the response ------------------------------------
+
+ /**
+ * Returns the referer as from the 'referer' request header.
+ */
+ public String getReferer() {
+ return getProperty(PN_REFERER, String.class);
+ }
+
+ /**
+ * Sets the referer property
+ */
+ public void setReferer(String referer) {
+ setProperty(PN_REFERER, referer);
+ }
+
+ /**
+ * Returns the absolute path of the item upon which the request operated.
+ * <p>
+ * If the {@link #setPath(String)} method has not been called yet, this
+ * method returns <code>null</code>.
+ */
+ public String getPath() {
+ return getProperty(PN_PATH, String.class);
+ }
+
+ /**
+ * Sets the absolute path of the item upon which the request operated.
+ */
+ public void setPath(String path) {
+ setProperty(PN_PATH, path);
+ }
+
+ /**
+ * Returns <code>true</code> if this was a create request.
+ * <p>
+ * Before calling the {@link #setCreateRequest(boolean)} method, this method
+ * always returns <code>false</code>.
+ */
+ public boolean isCreateRequest() {
+ return getProperty(PN_IS_CREATED, Boolean.class);
+ }
+
+ /**
+ * Sets whether the request was a create request or not.
+ */
+ public void setCreateRequest(boolean isCreateRequest) {
+ setProperty(PN_IS_CREATED, isCreateRequest);
+ }
+
+ /**
+ * Returns the location of the modification. this is the externalized form
+ * of the current path.
+ *
+ * @return the location of the modification.
+ */
+ public String getLocation() {
+ return getProperty(PN_LOCATION, String.class);
+ }
+
+ public void setLocation(String location) {
+ setProperty(PN_LOCATION, location);
+ }
+
+ /**
+ * Returns the parent location of the modification. this is the externalized
+ * form of the parent node of the current path.
+ *
+ * @return the location of the modification.
+ */
+ public String getParentLocation() {
+ return getProperty(PN_PARENT_LOCATION, String.class);
+ }
+
+ public void setParentLocation(String parentLocation) {
+ setProperty(PN_PARENT_LOCATION, parentLocation);
+ }
+
+ /**
+ * Sets the title of the response message
+ *
+ * @param title the title
+ */
+ public void setTitle(String title) {
+ setProperty(PN_TITLE, title);
+ }
+
+ /**
+ * sets the response status code properties
+ *
+ * @param code the code
+ * @param message the message
+ */
+ public void setStatus(int code, String message) {
+ setProperty(PN_STATUS_CODE, code);
+ setProperty(PN_STATUS_MESSAGE, message);
+ }
+
+ /**
+ * Returns the status code of this instance. If the status code has never
+ * been set by calling the {@link #setStatus(int, String)} method, the
+ * status code is determined by checking if there was an error. If there
+ * was an error, the response is assumed to be unsuccessful and 500 is returned.
+ * If there is no error, the response is assumed to be successful and 200 is returned.
+ */
+ public int getStatusCode() {
+ Integer status = getProperty(PN_STATUS_CODE, Integer.class);
+ if (status == null) {
+ if (getError() != null) {
+ //if there was an error
+ status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+ } else {
+ status = HttpServletResponse.SC_OK;
+ }
+ }
+ return status;
+ }
+
+ public String getStatusMessage() {
+ return getProperty(PN_STATUS_MESSAGE, String.class);
+ }
+
+ /**
+ * Returns any recorded error or <code>null</code>
+ *
+ * @return an error or <code>null</code>
+ */
+ public Throwable getError() {
+ return getProperty(PN_ERROR, Throwable.class);
+ }
+
+ public void setError(Throwable error) {
+ setProperty(PN_ERROR, error);
+ }
+
+ /**
+ * Returns <code>true</code> if no {@link #getError() error} is set and if
+ * the {@link #getStatusCode() status code} is one of the 2xx codes.
+ */
+ public boolean isSuccessful() {
+ return getError() == null && (getStatusCode() / 100) == 2;
+ }
+
+ // ---------- ChangeLog ----------------------------------------------------
+
+ /**
+ * Records a 'modified' change
+ *
+ * @param path path of the item that was modified
+ */
+ public void onModified(String path) {
+ onChange("modified", path);
+ }
+
+ /**
+ * Records a 'created' change
+ *
+ * @param path path of the item that was created
+ */
+ public void onCreated(String path) {
+ onChange("created", path);
+ }
+
+ /**
+ * Records a 'deleted' change
+ *
+ * @param path path of the item that was deleted
+ */
+ public void onDeleted(String path) {
+ if (path != null) {
+ onChange("deleted", path);
+ }
+ }
+
+ /**
+ * Records a 'moved' change. <p/> Note: the moved change only records the
+ * basic move command. the implied changes on the moved properties and sub
+ * nodes are not recorded.
+ *
+ * @param srcPath source path of the node that was moved
+ * @param dstPath destination path of the node that was moved.
+ */
+ public void onMoved(String srcPath, String dstPath) {
+ onChange("moved", srcPath, dstPath);
+ }
+
+ /**
+ * Records a 'copied' change. <p/> Note: the copy change only records the
+ * basic copy command. the implied changes on the copied properties and sub
+ * nodes are not recorded.
+ *
+ * @param srcPath source path of the node that was copied
+ * @param dstPath destination path of the node that was copied.
+ */
+ public void onCopied(String srcPath, String dstPath) {
+ onChange("copied", srcPath, dstPath);
+ }
+
+ /**
+ * Records a generic change of the given <code>type</code>.
+ * <p>
+ * The change is added to the internal list of changes with the syntax of a
+ * method call, where the <code>type</code> is the method name and the
+ * <code>arguments</code> are the string arguments to the method enclosed in
+ * double quotes. For example, the the call
+ *
+ * <pre>
+ * onChange("sameple", "arg1", "arg2");
+ * </pre>
+ *
+ * is aded as
+ *
+ * <pre>
+ * sample("arg1", "arg2")
+ * </pre>
+ *
+ * to the internal list of changes.
+ *
+ * @param type The type of the modification
+ * @param arguments The arguments to the modifications
+ */
+ public void onChange(String type, String... arguments) {
+ changes.append(type);
+ String delim = "(";
+ for (String a : arguments) {
+ changes.append(delim);
+ changes.append('\"');
+ changes.append(a);
+ changes.append('\"');
+ delim = ", ";
+ }
+ changes.append(");<br/>");
+ }
+
+ // ---------- Response Generation ------------------------------------------
+
+ /**
+ * prepares the response properties
+ */
+ private void prepare() {
+ String path = getPath();
+ if (getProperty(PN_STATUS_CODE) == null) {
+ if (getError() != null) {
+ setStatus(500, getError().toString());
+ setTitle("Error while processing " + path);
+ } else {
+ if (isCreateRequest()) {
+ setStatus(201, "Created");
+ setTitle("Content created " + path);
+ } else {
+ setStatus(200, "OK");
+ setTitle("Content modified " + path);
+ }
+ }
+ }
+
+ String referer = getReferer();
+ if (referer == null) {
+ referer = "";
+ }
+ setReferer(referer);
+
+ // get changelog
+ changes.insert(0, "<pre>");
+ changes.append("</pre>");
+ setProperty(PN_CHANGE_LOG, changes.toString());
+ }
+
+ /**
+ * Sets a generic response property with the given
+ *
+ * @param name name of the property
+ * @param value value of the property
+ */
+ public void setProperty(String name, Object value) {
+ properties.put(name, value);
+ }
+
+ /**
+ * Returns the generic response property with the given name and type or
+ * <code>null</code> if no such property exists or the property is not of
+ * the requested type.
+ */
+ @SuppressWarnings("unchecked")
+ public <Type> Type getProperty(String name, Class<Type> type) {
+ Object value = getProperty(name);
+ if (type.isInstance(value)) {
+ return (Type) value;
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the generic response property with the given name and type or
+ * <code>null</code> if no such property exists.
+ */
+ public Object getProperty(String name) {
+ return properties.get(name);
+ }
+
+ /**
+ * Writes the response to the given writer and replaces all ${var} patterns
+ * by the value of the respective property. if the property is not defined
+ * the pattern is not modified.
+ *
+ * @param response to send to
+ * @param setStatus whether to set the status code on the response
+ * @throws IOException if an i/o exception occurs
+ */
+ public void send(HttpServletResponse response, boolean setStatus)
+ throws IOException {
+ prepare();
+
+ if (setStatus) {
+ Object status = getProperty(PN_STATUS_CODE);
+ if (status instanceof Number) {
+ int statusCode = ((Number) status).intValue();
+ response.setStatus(statusCode);
+
+ // special treatment of 201/CREATED: Requires Location
+ if (statusCode == HttpServletResponse.SC_CREATED) {
+ response.setHeader("Location", getLocation());
+ }
+ }
+ }
+
+ response.setContentType("text/html");
+ response.setCharacterEncoding("UTF-8");
+
+ Writer out = response.getWriter();
+ InputStream template = getClass().getResourceAsStream(TEMPLATE_NAME);
+ Reader in = new BufferedReader(new InputStreamReader(template));
+ StringBuffer varBuffer = new StringBuffer();
+ int state = 0;
+ int read;
+ while ((read = in.read()) >= 0) {
+ char c = (char) read;
+ switch (state) {
+ // initial
+ case 0:
+ if (c == '$') {
+ state = 1;
+ } else {
+ out.write(c);
+ }
+ break;
+ // $ read
+ case 1:
+ if (c == '{') {
+ state = 2;
+ } else {
+ state = 0;
+ out.write('$');
+ out.write(c);
+ }
+ break;
+ // { read
+ case 2:
+ if (c == '}') {
+ state = 0;
+ Object prop = properties.get(varBuffer.toString());
+ if (prop != null) {
+ out.write(ResponseUtil.escapeXml(prop.toString()));
+ }
+ varBuffer.setLength(0);
+ } else {
+ varBuffer.append(c);
+ }
+ }
+ }
+ in.close();
+ out.flush();
+ }
+
+}
\ No newline at end of file
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/HttpConstants.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/HttpConstants.java
new file mode 100644
index 0000000..c26a0ad
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/HttpConstants.java
@@ -0,0 +1,37 @@
+/*
+ * 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.api.servlets;
+
+/** HTTP-related constants */
+public class HttpConstants {
+
+ public static final String METHOD_OPTIONS = "OPTIONS";
+ public static final String METHOD_GET = "GET";
+ public static final String METHOD_HEAD = "HEAD";
+ public static final String METHOD_POST = "POST";
+ public static final String METHOD_PUT = "PUT";
+ public static final String METHOD_DELETE = "DELETE";
+ public static final String METHOD_TRACE = "TRACE";
+ public static final String METHOD_CONNECT = "CONNECT";
+
+ public static final String HEADER_ACCEPT = "Accept";
+ public static final String HEADER_ETAG = "ETag";
+ public static final String HEADER_IF_MATCH = "If-Match";
+ public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
+ public static final String HEADER_LAST_MODIFIED = "Last-Modified";
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/OptingServlet.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/OptingServlet.java
new file mode 100644
index 0000000..2597edf
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/OptingServlet.java
@@ -0,0 +1,45 @@
+/*
+ * 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.api.servlets;
+
+import javax.servlet.Servlet;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+
+/**
+ * The <code>OptingServlet</code> interface may be implemented by
+ * <code>Servlets</code> used by Sling which may choose to not handle all
+ * requests for which they would be selected based on their registration
+ * properties.
+ */
+public interface OptingServlet extends Servlet {
+
+ /**
+ * Examines the request, and return <code>true</code> if this servlet is
+ * willing to handle the request. If <code>false</code> is returned, the
+ * request will be ignored by this servlet, and may be handled by other
+ * servlets.
+ *
+ * @param request The request to examine
+ * @return <code>true</code> if this servlet will handle the request,
+ * <code>false</code> otherwise
+ */
+ boolean accepts(SlingHttpServletRequest request);
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/ServletResolver.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/ServletResolver.java
new file mode 100644
index 0000000..3dfdba9
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/ServletResolver.java
@@ -0,0 +1,119 @@
+/*
+ * 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.api.servlets;
+
+import javax.servlet.Servlet;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+/**
+ * The <code>ServletResolver</code> defines the API for a service capable of
+ * resolving <code>javax.servlet.Servlet</code> instances to handle the
+ * processing of a request or resource.
+ * <p>
+ * Applications of the Sling Framework generally do not need the servlet
+ * resolver as resolution of the servlets to process requests and sub-requests
+ * through a <code>RequestDispatcher</code> is handled by the Sling Framework.
+ * <p>
+ */
+public interface ServletResolver {
+
+ /**
+ * Resolves a <code>javax.servlet.Servlet</code> whose
+ * <code>service</code> method may be used to handle the given
+ * <code>request</code>.
+ * <p>
+ * The returned servlet must be assumed to be initialized and ready to run.
+ * That is, the <code>init</code> nor the <code>destroy</code> methods
+ * must <em>NOT</em> be called on the returned servlet.
+ * <p>
+ * This method must not return a <code>Servlet</code> instance
+ * implementing the {@link OptingServlet} interface and returning
+ * <code>false</code> when the
+ * {@link OptingServlet#accepts(SlingHttpServletRequest)} method is called.
+ *
+ * @param request The {@link SlingHttpServletRequest} object used to drive
+ * selection of the servlet.
+ * @return The servlet whose <code>service</code> method may be called to
+ * handle the request.
+ * @throws org.apache.sling.api.SlingException Is thrown if an error occurrs
+ * while trying to find an appropriate servlet to handle the
+ * request or if no servlet could be resolved to handle the
+ * request.
+ */
+ Servlet resolveServlet(SlingHttpServletRequest request);
+
+ /**
+ * Resolves a <code>javax.servlet.Servlet</code> whose
+ * <code>service</code> method may be used to handle a request.
+ * <p>
+ * The returned servlet must be assumed to be initialized and ready to run.
+ * That is, the <code>init</code> nor the <code>destroy</code> methods
+ * must <em>NOT</em> be called on the returned servlet.
+ * <p>
+ * This method skips all {@link OptingServlet}s as there is no
+ * request object available.
+ *
+ * Basically this method searches a script with the <code>scriptName</code>
+ * for the resource type defined by the <code>resource</code>
+ * @param resource The {@link Resource} object used to drive
+ * selection of the servlet.
+ * @param scriptName The name of the script - the script might have an
+ * extension. In this case only a script with the
+ * matching extension is used.
+ * @return The servlet whose <code>service</code> method may be called to
+ * handle the request.
+ * @throws org.apache.sling.api.SlingException Is thrown if an error occurrs
+ * while trying to find an appropriate servlet to handle the
+ * request or if no servlet could be resolved to handle the
+ * request.
+ * @since 2.1
+ */
+ Servlet resolveServlet(Resource resource, String scriptName);
+
+ /**
+ * Resolves a <code>javax.servlet.Servlet</code> whose
+ * <code>service</code> method may be used to handle a request.
+ * <p>
+ * The returned servlet must be assumed to be initialized and ready to run.
+ * That is, the <code>init</code> nor the <code>destroy</code> methods
+ * must <em>NOT</em> be called on the returned servlet.
+ * <p>
+ * This method skips all {@link OptingServlet}s as there is no
+ * request object available.
+ *
+ * Basically this method searches a script with the <code>scriptName</code>
+ * @param resolver The {@link ResourceResolver} object used to drive
+ * selection of the servlet.
+ * @param scriptName The name of the script - the script might have an
+ * extension. In this case only a script with the
+ * matching extension is used.
+ * @return The servlet whose <code>service</code> method may be called to
+ * handle the request.
+ * @throws org.apache.sling.api.SlingException Is thrown if an error occurrs
+ * while trying to find an appropriate servlet to handle the
+ * request or if no servlet could be resolved to handle the
+ * request.
+ * @since 2.1
+ */
+ Servlet resolveServlet(ResourceResolver resolver, String scriptName);
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/SlingAllMethodsServlet.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/SlingAllMethodsServlet.java
new file mode 100644
index 0000000..6fadbe0
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/SlingAllMethodsServlet.java
@@ -0,0 +1,215 @@
+/*
+ * 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.api.servlets;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+
+/**
+ * Helper base class for data modifying Servlets used in Sling. This class
+ * extends the {@link SlingSafeMethodsServlet} by support for the <em>POST</em>,
+ * <em>PUT</em> and <em>DELETE</em> methods.
+ * <p>
+ * Implementors note: The methods in this class are all declared to throw the
+ * exceptions according to the intentions of the Servlet API rather than
+ * throwing their Sling RuntimeException counter parts. This is done to easy the
+ * integration with traditional servlets.
+ *
+ * @see SlingSafeMethodsServlet for more information on supporting more HTTP
+ * methods
+ */
+public class SlingAllMethodsServlet extends SlingSafeMethodsServlet {
+
+ private static final long serialVersionUID = -7960975481323952419L;
+
+ /**
+ * Called by the
+ * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method to
+ * handle an HTTP <em>POST</em> request.
+ * <p>
+ * This default implementation reports back to the client that the method is
+ * not supported.
+ * <p>
+ * Implementations of this class should overwrite this method with their
+ * implementation for the HTTP <em>POST</em> method support.
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response
+ * @throws ServletException Not thrown by this implementation.
+ * @throws IOException If the error status cannot be reported back to the
+ * client.
+ */
+ @SuppressWarnings("unused")
+ protected void doPost(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+ handleMethodNotImplemented(request, response);
+ }
+
+ /**
+ * Called by the
+ * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method to
+ * handle an HTTP <em>PUT</em> request.
+ * <p>
+ * This default implementation reports back to the client that the method is
+ * not supported.
+ * <p>
+ * Implementations of this class should overwrite this method with their
+ * implementation for the HTTP <em>PUT</em> method support.
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response
+ * @throws ServletException Not thrown by this implementation.
+ * @throws IOException If the error status cannot be reported back to the
+ * client.
+ */
+ @SuppressWarnings("unused")
+ protected void doPut(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+ handleMethodNotImplemented(request, response);
+ }
+
+ /**
+ * Called by the
+ * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method to
+ * handle an HTTP <em>DELETE</em> request.
+ * <p>
+ * This default implementation reports back to the client that the method is
+ * not supported.
+ * <p>
+ * Implementations of this class should overwrite this method with their
+ * implementation for the HTTP <em>DELETE</em> method support.
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response
+ * @throws ServletException Not thrown by this implementation.
+ * @throws IOException If the error status cannot be reported back to the
+ * client.
+ */
+ @SuppressWarnings("unused")
+ protected void doDelete(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+ handleMethodNotImplemented(request, response);
+ }
+
+ /**
+ * Tries to handle the request by calling a Java method implemented for the
+ * respective HTTP request method.
+ * <p>
+ * This implementation first calls the base class implementation and only if
+ * the base class cannot dispatch will try to dispatch the supported methods
+ * <em>POST</em>, <em>PUT</em> and <em>DELETE</em> and returns
+ * <code>true</code> if any of these methods is requested. Otherwise
+ * <code>false</code> is just returned.
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response
+ * @return <code>true</code> if the requested method (<code>request.getMethod()</code>)
+ * is known. Otherwise <code>false</code> is returned.
+ * @throws ServletException Forwarded from any of the dispatched methods
+ * @throws IOException Forwarded from any of the dispatched methods
+ */
+ protected boolean mayService(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+
+ // assume the method is known for now
+ if (super.mayService(request, response)) {
+ return true;
+ }
+
+ // assume the method is known for now
+ boolean methodKnown = true;
+
+ String method = request.getMethod();
+ if (HttpConstants.METHOD_POST.equals(method)) {
+ doPost(request, response);
+ } else if (HttpConstants.METHOD_PUT.equals(method)) {
+ doPut(request, response);
+ } else if (HttpConstants.METHOD_DELETE.equals(method)) {
+ doDelete(request, response);
+ } else {
+ // actually we do not know the method
+ methodKnown = false;
+ }
+
+ // return whether we actually knew the request method or not
+ return methodKnown;
+ }
+
+ /**
+ * Helper method called by
+ * {@link #doOptions(SlingHttpServletRequest, SlingHttpServletResponse)} to calculate
+ * the value of the <em>Allow</em> header sent as the response to the HTTP
+ * <em>OPTIONS</em> request.
+ * <p>
+ * This implementation overwrites the base class implementation adding
+ * support for the <em>POST</em>, <em>PUT</em> and <em>DELETE</em>
+ * methods in addition to the methods returned by the base class
+ * implementation.
+ *
+ * @param declaredMethods The public and protected methods declared in the
+ * extension of this class.
+ * @return A <code>StringBuffer</code> containing the list of HTTP methods
+ * supported.
+ */
+ protected StringBuffer getAllowedRequestMethods(
+ Map<String, Method> declaredMethods) {
+ StringBuffer allowBuf = super.getAllowedRequestMethods(declaredMethods);
+
+ // add more method names depending on the methods found
+ String className = SlingAllMethodsServlet.class.getName();
+ if (isMethodValid(declaredMethods.get("doPost"), className)) {
+ allowBuf.append(", ").append(HttpConstants.METHOD_POST);
+
+ } else if (isMethodValid(declaredMethods.get("doPut"), className)) {
+ allowBuf.append(", ").append(HttpConstants.METHOD_PUT);
+
+ } else if (isMethodValid(declaredMethods.get("doDelete"), className)) {
+ allowBuf.append(", ").append(HttpConstants.METHOD_DELETE);
+ }
+
+ return allowBuf;
+ }
+
+ /**
+ * Returns <code>true</code> if <code>method</code> is not
+ * <code>null</code> and the method is not defined in the class named by
+ * <code>className</code>.
+ * <p>
+ * This method may be used to make sure a method is actually overwritten and
+ * not just the default implementation.
+ *
+ * @param method The Method to check
+ * @param className The name of class assumed to contained the initial
+ * declaration of the method.
+ * @return <code>true</code> if <code>method</code> is not
+ * <code>null</code> and the methods declaring class is not the
+ * given class.
+ */
+ protected boolean isMethodValid(Method method, String className) {
+ return method != null && !method.getClass().getName().equals(className);
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/SlingSafeMethodsServlet.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/SlingSafeMethodsServlet.java
new file mode 100644
index 0000000..c9167b5
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/SlingSafeMethodsServlet.java
@@ -0,0 +1,565 @@
+/*
+ * 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.api.servlets;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper;
+
+/**
+ * Helper base class for read-only Servlets used in Sling. This base class is
+ * actually just a better implementation of the Servlet API <em>HttpServlet</em>
+ * class which accounts for extensibility. So extensions of this class have
+ * great control over what methods to overwrite.
+ * <p>
+ * If any of the default HTTP methods is to be implemented just overwrite the
+ * respective doXXX method. If additional methods should be supported implement
+ * appropriate doXXX methods and overwrite the
+ * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method
+ * to dispatch to the doXXX methods as appropriate and overwrite the
+ * {@link #getAllowedRequestMethods(Map)} to add the new method names.
+ * <p>
+ * Please note, that this base class is intended for applications where data is
+ * only read. As such, this servlet by itself does not support the <em>POST</em>,
+ * <em>PUT</em> and <em>DELETE</em> methods. Extensions of this class should
+ * either overwrite any of the doXXX methods of this class or add support for
+ * other read-only methods only. Applications wishing to support data
+ * modification should rather use or extend the {@link SlingAllMethodsServlet}
+ * which also contains support for the <em>POST</em>, <em>PUT</em> and
+ * <em>DELETE</em> methods. This latter class should also be overwritten to
+ * add support for HTTP methods modifying data.
+ * <p>
+ * Implementors note: The methods in this class are all declared to throw the
+ * exceptions according to the intentions of the Servlet API rather than
+ * throwing their Sling RuntimeException counter parts. This is done to ease the
+ * integration with traditional servlets.
+ *
+ * @see SlingAllMethodsServlet
+ */
+public class SlingSafeMethodsServlet extends GenericServlet {
+
+ private static final long serialVersionUID = 3620512288346703072L;
+
+ /**
+ * Handles the <em>HEAD</em> method.
+ * <p>
+ * This base implementation just calls the
+ * {@link #doGet(SlingHttpServletRequest, SlingHttpServletResponse)} method dropping
+ * the output. Implementations of this class may overwrite this method if
+ * they have a more performing implementation. Otherwise, they may just keep
+ * this base implementation.
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response which only gets the headers set
+ * @throws ServletException Forwarded from the
+ * {@link #doGet(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * method called by this implementation.
+ * @throws IOException Forwarded from the
+ * {@link #doGet(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * method called by this implementation.
+ */
+ protected void doHead(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+
+ // the null-output wrapper
+ NoBodyResponse wrappedResponse = new NoBodyResponse(response);
+
+ // do a normal get request, dropping the output
+ doGet(request, wrappedResponse);
+
+ // ensure the content length is set as gathered by the null-output
+ wrappedResponse.setContentLength();
+ }
+
+ /**
+ * Called by the
+ * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method to
+ * handle an HTTP <em>GET</em> request.
+ * <p>
+ * This default implementation reports back to the client that the method is
+ * not supported.
+ * <p>
+ * Implementations of this class should overwrite this method with their
+ * implementation for the HTTP <em>GET</em> method support.
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response
+ * @throws ServletException Not thrown by this implementation.
+ * @throws IOException If the error status cannot be reported back to the
+ * client.
+ */
+ @SuppressWarnings("unused")
+ protected void doGet(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+ handleMethodNotImplemented(request, response);
+ }
+
+ /**
+ * Handles the <em>OPTIONS</em> method by setting the HTTP
+ * <code>Allow</code> header on the response depending on the methods
+ * declared in this class.
+ * <p>
+ * Extensions of this class should generally not overwrite this method but
+ * rather the {@link #getAllowedRequestMethods(Map)} method. This method
+ * gathers all declared public and protected methods for the concrete class
+ * (upto but not including this class) and calls the
+ * {@link #getAllowedRequestMethods(Map)} method with the methods gathered.
+ * The returned value is then used as the value of the <code>Allow</code>
+ * header set.
+ *
+ * @param request The HTTP request object. Not used.
+ * @param response The HTTP response object on which the header is set.
+ * @throws ServletException Not thrown by this implementation.
+ * @throws IOException Not thrown by this implementation.
+ */
+ @SuppressWarnings("unused")
+ protected void doOptions(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+ Map<String, Method> methods = getAllDeclaredMethods(getClass());
+ StringBuffer allowBuf = getAllowedRequestMethods(methods);
+ response.setHeader("Allow", allowBuf.toString());
+ }
+
+ /**
+ * Handles the <em>TRACE</em> method by just returning the list of all
+ * header values in the response body.
+ * <p>
+ * Extensions of this class do not generally need to overwrite this method
+ * as it contains all there is to be done to the <em>TRACE</em> method.
+ *
+ * @param request The HTTP request whose headers are returned.
+ * @param response The HTTP response into which the request headers are
+ * written.
+ * @throws ServletException Not thrown by this implementation.
+ * @throws IOException May be thrown if there is an problem sending back the
+ * request headers in the response stream.
+ */
+ @SuppressWarnings("unused")
+ protected void doTrace(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+
+ String CRLF = "\r\n";
+
+ StringBuffer responseString = new StringBuffer();
+ responseString.append("TRACE ").append(request.getRequestURI());
+ responseString.append(' ').append(request.getProtocol());
+
+ Enumeration<?> reqHeaderEnum = request.getHeaderNames();
+ while (reqHeaderEnum.hasMoreElements()) {
+ String headerName = (String) reqHeaderEnum.nextElement();
+
+ Enumeration<?> reqHeaderValEnum = request.getHeaders(headerName);
+ while (reqHeaderValEnum.hasMoreElements()) {
+ responseString.append(CRLF);
+ responseString.append(headerName).append(": ");
+ responseString.append(reqHeaderValEnum.nextElement());
+ }
+ }
+
+ responseString.append(CRLF);
+
+ String charset = "UTF-8";
+ byte[] rawResponse = responseString.toString().getBytes(charset);
+ int responseLength = rawResponse.length;
+
+ response.setContentType("message/http");
+ response.setCharacterEncoding(charset);
+ response.setContentLength(responseLength);
+
+ ServletOutputStream out = response.getOutputStream();
+ out.write(rawResponse);
+ }
+
+ /**
+ * Called by the {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * method to handle a request for an HTTP method, which is not known and
+ * handled by this class or its extension.
+ * <p>
+ * This default implementation reports back to the client that the method is
+ * not supported.
+ * <p>
+ * This method should be overwritten with great care. It is better to
+ * overwrite the
+ * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method and
+ * add support for any extension HTTP methods through an additional doXXX
+ * method.
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response
+ * @throws ServletException Not thrown by this implementation.
+ * @throws IOException If the error status cannot be reported back to the
+ * client.
+ */
+ @SuppressWarnings("unused")
+ protected void doGeneric(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+ handleMethodNotImplemented(request, response);
+ }
+
+ /**
+ * Tries to handle the request by calling a Java method implemented for the
+ * respective HTTP request method.
+ * <p>
+ * This base class implentation dispatches the <em>HEAD</em>,
+ * <em>GET</em>, <em>OPTIONS</em> and <em>TRACE</em> to the
+ * respective <em>doXXX</em> methods and returns <code>true</code> if
+ * any of these methods is requested. Otherwise <code>false</code> is just
+ * returned.
+ * <p>
+ * Implementations of this class may overwrite this method but should first
+ * call this base implementation and in case <code>false</code> is
+ * returned add handling for any other method and of course return whether
+ * the requested method was known or not.
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response
+ * @return <code>true</code> if the requested method (<code>request.getMethod()</code>)
+ * is known. Otherwise <code>false</code> is returned.
+ * @throws ServletException Forwarded from any of the dispatched methods
+ * @throws IOException Forwarded from any of the dispatched methods
+ */
+ protected boolean mayService(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+
+ // assume the method is known for now
+ boolean methodKnown = true;
+
+ String method = request.getMethod();
+ if (HttpConstants.METHOD_HEAD.equals(method)) {
+ doHead(request, response);
+ } else if (HttpConstants.METHOD_GET.equals(method)) {
+ doGet(request, response);
+ } else if (HttpConstants.METHOD_OPTIONS.equals(method)) {
+ doOptions(request, response);
+ } else if (HttpConstants.METHOD_TRACE.equals(method)) {
+ doTrace(request, response);
+ } else {
+ // actually we do not know the method
+ methodKnown = false;
+ }
+
+ // return whether we actually knew the request method or not
+ return methodKnown;
+ }
+
+ /**
+ * Helper method which causes an appropriate HTTP response to be sent for an
+ * unhandled HTTP request method. In case of HTTP/1.1 a 405 status code
+ * (Method Not Allowed) is returned, otherwise a 400 status (Bad Request) is
+ * returned.
+ *
+ * @param request The HTTP request from which the method and protocol values
+ * are extracted to build the appropriate message.
+ * @param response The HTTP response to which the error status is sent.
+ * @throws IOException Thrown if the status cannot be sent to the client.
+ */
+ protected void handleMethodNotImplemented(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws IOException {
+ String protocol = request.getProtocol();
+ String msg = "Method " + request.getMethod() + " not supported";
+
+ if (protocol.endsWith("1.1")) {
+
+ // for HTTP/1.1 use 405 Method Not Allowed
+ response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
+
+ } else {
+
+ // otherwise use 400 Bad Request
+ response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
+
+ }
+ }
+
+ /**
+ * Called by the {@link #service(ServletRequest, ServletResponse)} method to
+ * handle the HTTP request. This implementation calls the
+ * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method and
+ * depedending on its return value call the
+ * {@link #doGeneric(SlingHttpServletRequest, SlingHttpServletResponse)} method. If
+ * the {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method
+ * can handle the request, the
+ * {@link #doGeneric(SlingHttpServletRequest, SlingHttpServletResponse)} method is not
+ * called otherwise it is called.
+ * <p>
+ * Implementations of this class should not generally overwrite this method.
+ * Rather the {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * method should be overwritten to add support for more HTTP methods.
+ *
+ * @param request The HTTP request
+ * @param response The HTTP response
+ * @throws ServletException Forwarded from the
+ * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * or
+ * {@link #doGeneric(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * methods.
+ * @throws IOException Forwarded from the
+ * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * or
+ * {@link #doGeneric(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * methods.
+ */
+ protected void service(SlingHttpServletRequest request,
+ SlingHttpServletResponse response) throws ServletException,
+ IOException {
+
+ // first try to handle the request by the known methods
+ boolean methodKnown = mayService(request, response);
+
+ // otherwise try to handle it through generic means
+ if (!methodKnown) {
+ doGeneric(request, response);
+ }
+ }
+
+ /**
+ * Forwards the request to the
+ * {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * method if the request is a HTTP request.
+ * <p>
+ * Implementations of this class will not generally overwrite this method.
+ *
+ * @param req The Servlet request
+ * @param res The Servlet response
+ * @throws ServletException If the request is not a HTTP request or
+ * forwarded from the
+ * {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * called.
+ * @throws IOException Forwarded from the
+ * {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)}
+ * called.
+ */
+ public void service(ServletRequest req, ServletResponse res)
+ throws ServletException, IOException {
+
+ if ((req instanceof SlingHttpServletRequest)
+ && (res instanceof SlingHttpServletResponse)) {
+
+ service((SlingHttpServletRequest) req,
+ (SlingHttpServletResponse) res);
+
+ } else {
+
+ throw new ServletException("Not a Sling HTTP request/response");
+
+ }
+ }
+
+ /**
+ * Returns the simple class name of this servlet class. Extensions of this
+ * class may overwrite to return more specific information.
+ */
+ public String getServletInfo() {
+ return getClass().getSimpleName();
+ }
+
+ /**
+ * Helper method called by
+ * {@link #doOptions(SlingHttpServletRequest, SlingHttpServletResponse)} to calculate
+ * the value of the <em>Allow</em> header sent as the response to the HTTP
+ * <em>OPTIONS</em> request.
+ * <p>
+ * This base class implementation checks whether any doXXX methods exist for
+ * <em>GET</em> and <em>HEAD</em> and returns the list of methods
+ * supported found. The list returned always includes the HTTP
+ * <em>OPTIONS</em> and <em>TRACE</em> methods.
+ * <p>
+ * Implementations of this class may overwrite this method check for more
+ * methods supported by the extension (generally the same list as used in
+ * the {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method).
+ * This base class implementation should always be called to make sure the
+ * default HTTP methods are included in the list.
+ *
+ * @param declaredMethods The public and protected methods declared in the
+ * extension of this class.
+ * @return A <code>StringBuffer</code> containing the list of HTTP methods
+ * supported.
+ */
+ protected StringBuffer getAllowedRequestMethods(
+ Map<String, Method> declaredMethods) {
+ StringBuffer allowBuf = new StringBuffer();
+
+ // OPTIONS and TRACE are always supported by this servlet
+ allowBuf.append(HttpConstants.METHOD_OPTIONS);
+ allowBuf.append(", ").append(HttpConstants.METHOD_TRACE);
+
+ // add more method names depending on the methods found
+ if (declaredMethods.containsKey("doHead")
+ && !declaredMethods.containsKey("doGet")) {
+ allowBuf.append(", ").append(HttpConstants.METHOD_HEAD);
+
+ } else if (declaredMethods.containsKey("doGet")) {
+ allowBuf.append(", ").append(HttpConstants.METHOD_GET);
+ allowBuf.append(", ").append(HttpConstants.METHOD_HEAD);
+
+ }
+
+ return allowBuf;
+ }
+
+ /**
+ * Returns a map of methods declared by the class indexed by method name.
+ * This method is called by the
+ * {@link #doOptions(SlingHttpServletRequest, SlingHttpServletResponse)} method to
+ * find the methods to be checked by the
+ * {@link #getAllowedRequestMethods(Map)} method. Note, that only extension
+ * classes of this class are considered to be sure to not account for the
+ * default implementations of the doXXX methods in this class.
+ *
+ * @param c The <code>Class</code> to get the declared methods from
+ * @return The Map of methods considered for support checking.
+ */
+ private Map<String, Method> getAllDeclaredMethods(Class<?> c) {
+ // stop (and do not include) the AbstractSlingServletClass
+ if (c == null
+ || c.getName().equals(SlingSafeMethodsServlet.class.getName())) {
+ return new HashMap<String, Method>();
+ }
+
+ // get the declared methods from the base class
+ Map<String, Method> methodSet = getAllDeclaredMethods(c.getSuperclass());
+
+ // add declared methods of c (maybe overwrite base class methods)
+ Method[] declaredMethods = c.getDeclaredMethods();
+ for (Method method : declaredMethods) {
+ // only consider public and protected methods
+ if (Modifier.isProtected(method.getModifiers())
+ || Modifier.isPublic(method.getModifiers())) {
+ methodSet.put(method.getName(), method);
+ }
+ }
+
+ return methodSet;
+ }
+
+ /**
+ * A response that includes no body, for use in (dumb) "HEAD" support. This
+ * just swallows that body, counting the bytes in order to set the content
+ * length appropriately.
+ */
+ private class NoBodyResponse extends SlingHttpServletResponseWrapper {
+
+ /** The byte sink and counter */
+ private NoBodyOutputStream noBody;
+
+ /** Optional writer around the byte sink */
+ private PrintWriter writer;
+
+ /** Whether the request processor set the content length itself or not. */
+ private boolean didSetContentLength;
+
+ NoBodyResponse(SlingHttpServletResponse wrappedResponse) {
+ super(wrappedResponse);
+ noBody = new NoBodyOutputStream();
+ }
+
+ /**
+ * Called at the end of request processing to ensure the content length
+ * is set. If the processor already set the length, this method does not
+ * do anything. Otherwise the number of bytes written through the
+ * null-output is set on the response.
+ */
+ void setContentLength() {
+ if (!didSetContentLength) {
+ setContentLength(noBody.getContentLength());
+ }
+ }
+
+ /**
+ * Overwrite this to prevent setting the content length at the end of
+ * the request through {@link #setContentLength()}
+ */
+ public void setContentLength(int len) {
+ super.setContentLength(len);
+ didSetContentLength = true;
+ }
+
+ /**
+ * Just return the null output stream and don't check whether a writer
+ * has already been acquired.
+ */
+ public ServletOutputStream getOutputStream() {
+ return noBody;
+ }
+
+ /**
+ * Just return the writer to the null output stream and don't check
+ * whether an output stram has already been acquired.
+ */
+ public PrintWriter getWriter() throws UnsupportedEncodingException {
+ if (writer == null) {
+ OutputStreamWriter w;
+
+ w = new OutputStreamWriter(noBody, getCharacterEncoding());
+ writer = new PrintWriter(w);
+ }
+ return writer;
+ }
+ }
+
+ /**
+ * Simple ServletOutputStream which just does not write but counts the bytes
+ * written through it. This class is used by the NoBodyResponse.
+ */
+ private class NoBodyOutputStream extends ServletOutputStream {
+
+ private int contentLength = 0;
+
+ /**
+ * @return the number of bytes "written" through this stream
+ */
+ int getContentLength() {
+ return contentLength;
+ }
+
+ public void write(int b) {
+ contentLength++;
+ }
+
+ public void write(byte buf[], int offset, int len) {
+ if (len >= 0) {
+ contentLength += len;
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+ }
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/package-info.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/package-info.java
new file mode 100644
index 0000000..d286042
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/servlets/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("2.1")
+package org.apache.sling.api.servlets;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/ModifiableValueMapDecorator.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/ModifiableValueMapDecorator.java
new file mode 100644
index 0000000..1daca67
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/ModifiableValueMapDecorator.java
@@ -0,0 +1,43 @@
+/*
+ * 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.api.wrappers;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+
+/**
+ * <code>ModifiableValueMapDecorator</code> decorates another {@link Map}
+ * to provide a basic implementation for the additional methods
+ * of a {@link ModifiableValueMap}.
+ *
+ * @since 2.2
+ */
+public class ModifiableValueMapDecorator
+extends ValueMapDecorator
+implements ModifiableValueMap {
+
+ /**
+ * Creates a new wrapper around a given map.
+ * @param base wrapped object
+ */
+ public ModifiableValueMapDecorator(final Map<String, Object> base) {
+ super(base);
+ }
+}
\ No newline at end of file
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/SlingHttpServletRequestWrapper.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/SlingHttpServletRequestWrapper.java
new file mode 100644
index 0000000..31dc1de
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/SlingHttpServletRequestWrapper.java
@@ -0,0 +1,124 @@
+/*
+ * 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.api.wrappers;
+
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.request.RequestDispatcherOptions;
+import org.apache.sling.api.request.RequestParameter;
+import org.apache.sling.api.request.RequestParameterMap;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.request.RequestProgressTracker;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+
+/**
+ * The <code>SlingHttpServletRequestWrapper</code> class is a default wrapper
+ * class around a {@link SlingHttpServletRequest} which may be extended to amend
+ * the functionality of the original request object.
+ */
+public class SlingHttpServletRequestWrapper extends HttpServletRequestWrapper
+ implements SlingHttpServletRequest {
+
+ /** Create a wrapper for the supplied wrappedRequest */
+ public SlingHttpServletRequestWrapper(SlingHttpServletRequest wrappedRequest) {
+ super(wrappedRequest);
+ }
+
+ /**
+ * Return the original {@link SlingHttpServletRequest} object wrapped by
+ * this.
+ */
+ public SlingHttpServletRequest getSlingRequest() {
+ return (SlingHttpServletRequest) getRequest();
+ }
+
+ public Cookie getCookie(String name) {
+ return getSlingRequest().getCookie(name);
+ }
+
+ public RequestProgressTracker getRequestProgressTracker() {
+ return getSlingRequest().getRequestProgressTracker();
+ }
+
+ public RequestDispatcher getRequestDispatcher(Resource resource) {
+ return getSlingRequest().getRequestDispatcher(resource);
+ }
+
+ public RequestDispatcher getRequestDispatcher(Resource resource,
+ RequestDispatcherOptions options) {
+ return getSlingRequest().getRequestDispatcher(resource, options);
+ }
+
+ public RequestDispatcher getRequestDispatcher(String path,
+ RequestDispatcherOptions options) {
+ return getSlingRequest().getRequestDispatcher(path, options);
+ }
+
+ public RequestParameter getRequestParameter(String name) {
+ return getSlingRequest().getRequestParameter(name);
+ }
+
+ public RequestParameterMap getRequestParameterMap() {
+ return getSlingRequest().getRequestParameterMap();
+ }
+
+ public RequestParameter[] getRequestParameters(String name) {
+ return getSlingRequest().getRequestParameters(name);
+ }
+
+ public RequestPathInfo getRequestPathInfo() {
+ return getSlingRequest().getRequestPathInfo();
+ }
+
+ public Resource getResource() {
+ return getSlingRequest().getResource();
+ }
+
+ public ResourceResolver getResourceResolver() {
+ return getSlingRequest().getResourceResolver();
+ }
+
+ public ResourceBundle getResourceBundle(Locale locale) {
+ return getSlingRequest().getResourceBundle(locale);
+ }
+
+ public ResourceBundle getResourceBundle(String baseName, Locale locale) {
+ return getSlingRequest().getResourceBundle(baseName, locale);
+ }
+
+ public String getResponseContentType() {
+ return getSlingRequest().getResponseContentType();
+ }
+
+ public Enumeration<String> getResponseContentTypes() {
+ return getSlingRequest().getResponseContentTypes();
+ }
+
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ return getSlingRequest().adaptTo(type);
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/SlingHttpServletResponseWrapper.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/SlingHttpServletResponseWrapper.java
new file mode 100644
index 0000000..c07f03b
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/SlingHttpServletResponseWrapper.java
@@ -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.api.wrappers;
+
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.apache.sling.api.SlingHttpServletResponse;
+
+/**
+ * The <code>SlingHttpServletResponseWrapper</code> class is a default wrapper
+ * class around a {@link SlingHttpServletResponse} which may be extended to
+ * amend the functionality of the original response object.
+ *
+ * There's nothing interesting to wrap currently, as the SlingHttpServletResponse
+ * interface is empty.
+ * So this exists only for symmetry with {@link SlingHttpServletRequestWrapper}
+ */
+public class SlingHttpServletResponseWrapper extends HttpServletResponseWrapper
+ implements SlingHttpServletResponse {
+
+ /** Create a wrapper for the supplied wrappedRequest */
+ public SlingHttpServletResponseWrapper(SlingHttpServletResponse wrappedResponse) {
+ super(wrappedResponse);
+ }
+
+ /**
+ * Return the original {@link SlingHttpServletResponse} object wrapped by
+ * this.
+ */
+ public SlingHttpServletResponse getSlingResponse() {
+ return (SlingHttpServletResponse) getResponse();
+ }
+
+ public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+ return getSlingResponse().adaptTo(type);
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/SlingRequestPaths.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/SlingRequestPaths.java
new file mode 100644
index 0000000..f0065bc
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/SlingRequestPaths.java
@@ -0,0 +1,115 @@
+/*
+ * 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.api.wrappers;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * This class is not a "wrapper" per se, but computes the correct path info,
+ * request URI, etc. for included requests. When including a request via
+ * {@link javax.servlet.RequestDispatcher}, the Servlet API specifies that target paths of
+ * the included request are available as request attributes.
+ * Request.getPathInfo(), for example will return the value for the including
+ * request, *not* for the included one.
+ */
+public class SlingRequestPaths {
+
+ /**
+ * Attribute name used by the RequestDispatcher to indicate the context path
+ * of the included request, as a String.
+ */
+ public static final String INCLUDE_CONTEXT_PATH = "javax.servlet.include.context_path";
+
+ /**
+ * Attribute name used by the RequestDispatcher to indicate the path info of
+ * the included request, as a String.
+ */
+ public static final String INCLUDE_PATH_INFO = "javax.servlet.include.path_info";
+
+ /**
+ * Attribute name used by the RequestDispatcher to indicate the query string
+ * of the included request, as a String.
+ */
+ public static final String INCLUDE_QUERY_STRING = "javax.servlet.include.query_string";
+
+ /**
+ * Attribute name used by the RequestDispatcher to indicate the request URI
+ * of the included request, as a String.
+ */
+ public static final String INCLUDE_REQUEST_URI = "javax.servlet.include.request_uri";
+
+ /**
+ * Attribute name used by the RequestDispatcher to indicate the servlet path
+ * of the included request, as a String.
+ */
+ public static final String INCLUDE_SERVLET_PATH = "javax.servlet.include.servlet_path";
+
+ /**
+ * Return the context path for r, using the appropriate request attribute if
+ * the request is an included one.
+ */
+ public static String getContextPath(HttpServletRequest r) {
+ final String attr = (String) r.getAttribute(INCLUDE_CONTEXT_PATH);
+ return attr != null ? attr : r.getContextPath();
+ }
+
+ /**
+ * Return the context path for r, using the appropriate request attribute if
+ * the request is an included one.
+ */
+ public static String getPathInfo(HttpServletRequest r) {
+ final String attr = (String) r.getAttribute(INCLUDE_PATH_INFO);
+ return attr != null ? attr : r.getPathInfo();
+ }
+
+ /**
+ * Return the query string for r, using the appropriate request attribute if
+ * the request is an included one.
+ */
+ public static String getQueryString(HttpServletRequest r) {
+ final String attr = (String) r.getAttribute(INCLUDE_QUERY_STRING);
+ return attr != null ? attr : r.getQueryString();
+ }
+
+ /**
+ * Return the request URI for r, using the appropriate request attribute if
+ * the request is an included one.
+ */
+ public static String getRequestURI(HttpServletRequest r) {
+ final String attr = (String) r.getAttribute(INCLUDE_REQUEST_URI);
+ return attr != null ? attr : r.getRequestURI();
+ }
+
+ /**
+ * Return the servlet path for r, using the appropriate request attribute if
+ * the request is an included one.
+ */
+ public static String getServletPath(HttpServletRequest r) {
+ final String attr = (String) r.getAttribute(INCLUDE_SERVLET_PATH);
+ return attr != null ? attr : r.getServletPath();
+ }
+
+ /**
+ * True if r is an included request, in which case it has the
+ * INCLUDE_REQUEST_URI attribute
+ */
+ public static boolean isIncluded(HttpServletRequest r) {
+ return r.getAttribute(INCLUDE_REQUEST_URI) != null;
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/ValueMapDecorator.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/ValueMapDecorator.java
new file mode 100644
index 0000000..2197334
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/ValueMapDecorator.java
@@ -0,0 +1,212 @@
+/*
+ * 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.api.wrappers;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.api.resource.ValueMap;
+
+/**
+ * <code>ValueMapDecorator</code> decorates another {@link Map}
+ * to provide a basic implementation for the additional methods
+ * of a {@link ValueMap}.
+ */
+public class ValueMapDecorator implements ValueMap {
+
+ /**
+ * underlying map
+ */
+ private final Map<String, Object> base;
+
+ /**
+ * Creates a new wrapper around a given map.
+ * @param base wrapped object
+ */
+ public ValueMapDecorator(Map<String, Object> base) {
+ this.base = base;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public <T> T get(String name, Class<T> type) {
+ return convert(get(name), type);
+ }
+
+ /**
+ * Converts the object to the given type.
+ * @param obj object
+ * @param type type
+ * @return the converted object
+ */
+ @SuppressWarnings("unchecked")
+ private <T> T convert(Object obj, Class<T> type) {
+ // todo: do smarter checks
+ try {
+ if (obj == null) {
+ return null;
+ } else if (type.isAssignableFrom(obj.getClass())) {
+ return (T) obj;
+ } else if (type.isArray()) {
+ return (T) convertToArray(obj, type.getComponentType());
+ } else if (type == String.class) {
+ return (T) String.valueOf(obj);
+ } else if (type == Integer.class) {
+ return (T) (Integer) Integer.parseInt(obj.toString());
+ } else if (type == Long.class) {
+ return (T) (Long) Long.parseLong(obj.toString());
+ } else if (type == Double.class) {
+ return (T) (Double) Double.parseDouble(obj.toString());
+ } else if (type == Boolean.class) {
+ return (T) (Boolean) Boolean.parseBoolean(obj.toString());
+ } else {
+ return null;
+ }
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Converts the object to an array of the given type
+ * @param obj the object or object array
+ * @param type the component type of the array
+ * @return and array of type T
+ */
+ private <T> T[] convertToArray(Object obj, Class<T> type) {
+ if (obj.getClass().isArray()) {
+ final Object[] array = (Object[]) obj;
+ @SuppressWarnings("unchecked")
+ final T[] result = (T[]) Array.newInstance(type, array.length);
+ for (int i = 0; i < array.length; i++) {
+ result[i] = convert(array[i], type);
+ }
+ return result;
+ } else {
+ @SuppressWarnings("unchecked")
+ final T[] result = (T[]) Array.newInstance(type, 1);
+ result[0] = convert(obj, type);
+ return result;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T get(String name, T defaultValue) {
+ if ( defaultValue == null ) {
+ return (T)get(name);
+ }
+ T value = get(name, (Class<T>) defaultValue.getClass());
+ return value == null ? defaultValue : value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int size() {
+ return base.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isEmpty() {
+ return base.isEmpty();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsKey(Object key) {
+ return base.containsKey(key);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean containsValue(Object value) {
+ return base.containsValue(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object get(Object key) {
+ return base.get(key);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object put(String key, Object value) {
+ return base.put(key, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object remove(Object key) {
+ return base.remove(key);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void putAll(Map<? extends String, ?> t) {
+ base.putAll(t);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void clear() {
+ base.clear();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Set<String> keySet() {
+ return base.keySet();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Collection<Object> values() {
+ return base.values();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Set<Entry<String, Object>> entrySet() {
+ return base.entrySet();
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + " : " + this.base.toString();
+ }
+}
\ No newline at end of file
diff --git a/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/package-info.java b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/package-info.java
new file mode 100644
index 0000000..2b840be
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/java/org/apache/sling/api/wrappers/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+@Version("2.2")
+package org.apache.sling.api.wrappers;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/SLING-2938-before-changes/sling-api/src/main/resources/org/apache/sling/api/servlets/HtmlResponse.html b/SLING-2938-before-changes/sling-api/src/main/resources/org/apache/sling/api/servlets/HtmlResponse.html
new file mode 100644
index 0000000..11c80ce
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/main/resources/org/apache/sling/api/servlets/HtmlResponse.html
@@ -0,0 +1,43 @@
+<html>
+<head>
+ <title>${title}</title>
+</head>
+ <body>
+ <h1>${title}</h1>
+ <table>
+ <tbody>
+ <tr>
+ <td>Status</td>
+ <td><div id="Status">${status.code}</div></td>
+ </tr>
+ <tr>
+ <td>Message</td>
+ <td><div id="Message">${status.message}</div></td>
+ </tr>
+ <tr>
+ <td>Location</td>
+ <td><a href="${location}" id="Location">${location}</a></td>
+ </tr>
+ <tr>
+ <td>Parent Location</td>
+ <td><a href="${parentLocation}" id="ParentLocation">${parentLocation}</a></td>
+ </tr>
+ <tr>
+ <td>Path</td>
+ <td><div id="Path">${path}</div></td>
+ </tr>
+ <tr>
+ <td>Referer</td>
+ <td><a href="${referer}" id="Referer">${referer}</a></td>
+ </tr>
+ <tr>
+ <td>ChangeLog</td>
+ <td><div id="ChangeLog">${changeLog}</div></td>
+ </tr>
+ </tbody>
+ </table>
+ <p><a href="${referer}">Go Back</a></p>
+ <p><a href="${location}">Modified Resource</a></p>
+ <p><a href="${parentLocation}">Parent of Modified Resource</a></p>
+ </body>
+</html>
\ No newline at end of file
diff --git a/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/request/RequestDispatcherOptionsTest.java b/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/request/RequestDispatcherOptionsTest.java
new file mode 100644
index 0000000..e7a6eed
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/request/RequestDispatcherOptionsTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.api.request;
+
+import junit.framework.TestCase;
+
+public class RequestDispatcherOptionsTest extends TestCase {
+
+ public void testNullString() {
+ final RequestDispatcherOptions result = new RequestDispatcherOptions(
+ null);
+ assertTrue(result.isEmpty());
+ }
+
+ public void testEmptyString() {
+ final RequestDispatcherOptions result = new RequestDispatcherOptions("");
+ assertTrue(result.isEmpty());
+ }
+
+ public void testSingleOption() {
+ final RequestDispatcherOptions result = new RequestDispatcherOptions(
+ "forceResourceType= widget");
+ assertNotNull(result);
+ assertEquals("Expected option found (" + result + ")", "widget",
+ result.get(RequestDispatcherOptions.OPT_FORCE_RESOURCE_TYPE));
+ assertEquals("Expected option found (" + result + ")", "widget",
+ result.getForceResourceType());
+ }
+
+ public void testResourceTypeSlashShortcut() {
+ // a single option with no comma or colon means "forceResourceType"
+ final RequestDispatcherOptions result = new RequestDispatcherOptions(
+ "\t components/widget ");
+ assertNotNull(result);
+ assertEquals("Expected option found (" + result + ")",
+ "components/widget",
+ result.get(RequestDispatcherOptions.OPT_FORCE_RESOURCE_TYPE));
+ assertEquals("Expected option found (" + result + ")",
+ "components/widget", result.getForceResourceType());
+ }
+
+ public void testResourceTypeColonShortcut() {
+ // a single option with no comma or colon means "forceResourceType"
+ final RequestDispatcherOptions result = new RequestDispatcherOptions(
+ "\t components:widget ");
+ assertNotNull(result);
+ assertEquals("Expected option found (" + result + ")",
+ "components:widget",
+ result.get(RequestDispatcherOptions.OPT_FORCE_RESOURCE_TYPE));
+ assertEquals("Expected option found (" + result + ")",
+ "components:widget", result.getForceResourceType());
+ }
+
+ public void testTwoOptions() {
+ final RequestDispatcherOptions result = new RequestDispatcherOptions(
+ "forceResourceType= components:widget, replaceSelectors = xyz ,");
+ assertNotNull(result);
+ assertEquals("Expected option found (" + result + ")",
+ "components:widget",
+ result.get(RequestDispatcherOptions.OPT_FORCE_RESOURCE_TYPE));
+ assertEquals("Expected option found (" + result + ")",
+ "components:widget", result.getForceResourceType());
+ assertEquals("Expected option found (" + result + ")", "xyz",
+ result.get(RequestDispatcherOptions.OPT_REPLACE_SELECTORS));
+ assertEquals("Expected option found (" + result + ")", "xyz",
+ result.getReplaceSelectors());
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/request/RequestUtilTest.java b/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/request/RequestUtilTest.java
new file mode 100644
index 0000000..7d96a65
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/request/RequestUtilTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.api.request;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Locale;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.commons.testing.sling.MockResource;
+import org.apache.sling.commons.testing.sling.MockSlingHttpServletRequest;
+
+import junit.framework.TestCase;
+
+public class RequestUtilTest extends TestCase {
+
+
+ public void testHandleIfModifiedSince(){
+ assertTrue(RequestUtil.handleIfModifiedSince(getMockRequest(1309268989938L,1309269042730L),getMockResponse()));
+
+ assertFalse(RequestUtil.handleIfModifiedSince(getMockRequest(1309269042730L,1309268989938L),getMockResponse()));
+ assertFalse(RequestUtil.handleIfModifiedSince(getMockRequest(-1,1309268989938L),getMockResponse()));
+ }
+
+ protected SlingHttpServletRequest getMockRequest(final long modificationTime, final long ifModifiedSince) {
+ final String resourcePath = "foo";
+ final MockSlingHttpServletRequest r = new MockSlingHttpServletRequest(resourcePath, null, null, null, null) {
+ @Override
+ public long getDateHeader(String name) {
+ return ifModifiedSince;
+ }
+
+ };
+ final String path = "/foo/node";
+ final MockResource mr = new MockResource(null, path, null) {};
+ mr.getResourceMetadata().setModificationTime(modificationTime);
+ r.setResource(mr);
+ return r;
+ }
+
+ public void testParserAcceptHeader(){
+ assertEquals(RequestUtil.parserAcceptHeader("compress;q=0.5, gzip;q=1.0").get("compress"), 0.5);
+ assertEquals(RequestUtil.parserAcceptHeader("compress,gzip").get("compress"),1.0);
+ assertEquals(RequestUtil.parserAcceptHeader("compress").get("compress"),1.0);
+ assertEquals(RequestUtil.parserAcceptHeader("compress;q=string,gzip;q=1.0").get("compress"), 1.0);
+
+ assertNull(RequestUtil.parserAcceptHeader("compress;q=0.5, gzip;q=1.0").get("compres"));
+ }
+
+
+ protected HttpServletResponse getMockResponse() {
+
+ return new HttpServletResponse() {
+
+ public void setLocale(Locale loc) {}
+
+ public void setContentType(String type) {}
+
+ public void setContentLength(int len) {}
+
+ public void setCharacterEncoding(String charset) {}
+
+ public void setBufferSize(int size) {}
+
+ public void resetBuffer() {}
+
+ public void reset() {}
+
+ public boolean isCommitted() {
+ return false;
+ }
+
+ public PrintWriter getWriter() throws IOException {
+ return null;
+ }
+
+ public ServletOutputStream getOutputStream() throws IOException {
+ return null;
+ }
+
+ public Locale getLocale() {
+ return null;
+ }
+
+ public String getContentType() {
+ return null;
+ }
+
+ public String getCharacterEncoding() {
+ return null;
+ }
+
+ public int getBufferSize() {
+ return 0;
+ }
+
+ public void flushBuffer() throws IOException {}
+
+ public void setStatus(int sc, String sm) {}
+
+ public void setStatus(int sc) {}
+
+ public void setIntHeader(String name, int value) {}
+
+ public void setHeader(String name, String value) {}
+
+ public void setDateHeader(String name, long date) {}
+
+ public void sendRedirect(String location) throws IOException {}
+
+ public void sendError(int sc, String msg) throws IOException {}
+
+ public void sendError(int sc) throws IOException {}
+
+ public String encodeUrl(String url) {
+ return null;
+ }
+
+ public String encodeURL(String url) {
+ return null;
+ }
+
+ public String encodeRedirectUrl(String url) {
+ return null;
+ }
+
+ public String encodeRedirectURL(String url) {
+ return null;
+ }
+
+ public boolean containsHeader(String name) {
+ return false;
+ }
+
+ public void addIntHeader(String name, int value) {}
+
+ public void addHeader(String name, String value) {}
+
+ public void addDateHeader(String name, long date) {}
+
+ public void addCookie(Cookie cookie) {}
+ };
+
+ }
+
+
+}
diff --git a/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/resource/ResourceMetadataTest.java b/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/resource/ResourceMetadataTest.java
new file mode 100644
index 0000000..f03606e
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/resource/ResourceMetadataTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.api.resource;
+
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class ResourceMetadataTest {
+
+ private static final Map<String, Object> TEST_MAP = new HashMap<String, Object>();
+ static {
+ TEST_MAP.put("first", "one");
+ TEST_MAP.put("second", Integer.MAX_VALUE);
+ }
+
+ @Test
+ public void testLockedPut() {
+ final ResourceMetadata m = new ResourceMetadata();
+ m.lock();
+ try {
+ m.put("after", "locking");
+ fail("put() should fail after locking");
+ } catch(UnsupportedOperationException uoe) {
+ // all good
+ }
+ }
+
+ @Test
+ public void testLockedClear() {
+ final ResourceMetadata m = new ResourceMetadata();
+ m.lock();
+ try {
+ m.clear();
+ fail("clear() should fail after locking");
+ } catch(UnsupportedOperationException uoe) {
+ // all good
+ }
+ }
+
+ @Test
+ public void testLockedPutAll() {
+ final ResourceMetadata m = new ResourceMetadata();
+ m.lock();
+ try {
+ m.putAll(TEST_MAP);
+ fail("putAll() should fail after locking");
+ } catch(UnsupportedOperationException uoe) {
+ // all good
+ }
+ }
+
+ @Test
+ public void testLockedRemove() {
+ final ResourceMetadata m = new ResourceMetadata();
+ m.lock();
+ try {
+ m.remove("foo");
+ fail("remove() should fail after locking");
+ } catch(UnsupportedOperationException uoe) {
+ // all good
+ }
+ }
+
+ @Test
+ public void testLockedEntrySet() {
+ final ResourceMetadata m = new ResourceMetadata();
+ m.lock();
+ m.entrySet().toString();
+ }
+
+ @Test
+ public void testLockedKeySet() {
+ final ResourceMetadata m = new ResourceMetadata();
+ m.lock();
+ m.keySet().toString();
+ }
+
+ @Test
+ public void testLockedValues() {
+ final ResourceMetadata m = new ResourceMetadata();
+ m.lock();
+ m.values().toString();
+ }
+}
diff --git a/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/resource/ResourceUtilTest.java b/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/resource/ResourceUtilTest.java
new file mode 100644
index 0000000..206c69c
--- /dev/null
+++ b/SLING-2938-before-changes/sling-api/src/test/java/org/apache/sling/api/resource/ResourceUtilTest.java
@@ -0,0 +1,416 @@
+/*
+ * 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.api.resource;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(JMock.class)
+public class ResourceUtilTest {
+
+ protected final Mockery context = new JUnit4Mockery();
+
+ @Test public void testResolveRelativeSegments() {
+
+ assertEquals("/", ResourceUtil.normalize("/"));
+ assertEquals("/", ResourceUtil.normalize("///"));
+
+ assertEquals("/a/b/c", ResourceUtil.normalize("/a//b/c"));
+ assertEquals("/a/b/c", ResourceUtil.normalize("/a/b//c"));
+ assertEquals("/a/b/c", ResourceUtil.normalize("/a///b///c"));
+ assertEquals("/a/b/c", ResourceUtil.normalize("/a/b/c/"));
+ assertEquals("/a/b/c", ResourceUtil.normalize("/a/b/c//"));
+ assertEquals("/a/b/c", ResourceUtil.normalize("/a/b/c///"));
+
+ assertEquals("/az/bz/cz", ResourceUtil.normalize("/az//bz/cz"));
+ assertEquals("/az/bz/cz", ResourceUtil.normalize("/az/bz//cz"));
+ assertEquals("/az/bz/cz", ResourceUtil.normalize("/az///bz///cz"));
+ assertEquals("/az/bz/cz", ResourceUtil.normalize("/az/bz/cz/"));
+ assertEquals("/az/bz/cz", ResourceUtil.normalize("/az/bz/cz//"));
+ assertEquals("/az/bz/cz", ResourceUtil.normalize("/az/bz/cz///"));
+
+ assertEquals("/a", ResourceUtil.normalize("/a"));
+ assertEquals("/a", ResourceUtil.normalize("//a"));
+ assertEquals("/a", ResourceUtil.normalize("///a"));
+
+ assertEquals("/az", ResourceUtil.normalize("/az"));
+ assertEquals("/az", ResourceUtil.normalize("//az"));
+ assertEquals("/az", ResourceUtil.normalize("///az"));
+
+ assertEquals("/", ResourceUtil.normalize("/."));
+ assertEquals("/a", ResourceUtil.normalize("/a/."));
+ assertEquals("/a", ResourceUtil.normalize("/./a"));
+ assertEquals("/a/b", ResourceUtil.normalize("/a/./b"));
+ assertEquals("/a/b", ResourceUtil.normalize("/a/b/."));
+ assertEquals("/a/b", ResourceUtil.normalize("/a/./b/."));
+
+ assertEquals("/", ResourceUtil.normalize("/."));
+ assertEquals("/az", ResourceUtil.normalize("/az/."));
+ assertEquals("/az", ResourceUtil.normalize("/./az"));
+ assertEquals("/az/bz", ResourceUtil.normalize("/az/./bz"));
+ assertEquals("/az/bz", ResourceUtil.normalize("/az/bz/."));
+ assertEquals("/az/bz", ResourceUtil.normalize("/az/./bz/."));
+
+ assertNull(ResourceUtil.normalize("/.."));
+ assertNull(ResourceUtil.normalize("/.."));
+ assertEquals("/", ResourceUtil.normalize("/a/.."));
+ assertEquals("/a", ResourceUtil.normalize("/a/b/.."));
+ assertEquals("/", ResourceUtil.normalize("/a/b/../.."));
+ assertNull(ResourceUtil.normalize("/a/b/../../.."));
+
+ assertNull(ResourceUtil.normalize("/.."));
+ assertNull(ResourceUtil.normalize("/.."));
+ assertEquals("/", ResourceUtil.normalize("/az/.."));
+ assertEquals("/az", ResourceUtil.normalize("/az/bz/.."));
+ assertEquals("/", ResourceUtil.normalize("/az/bz/../.."));
+ assertNull(ResourceUtil.normalize("/az/bz/../../.."));
+
+ assertEquals("/b", ResourceUtil.normalize("/a/../b"));
+ assertEquals("/a/c", ResourceUtil.normalize("/a/b/../c"));
+ assertEquals("/c", ResourceUtil.normalize("/a/b/../../c"));
+ assertNull(ResourceUtil.normalize("/a/b/../../../c"));
+
+ assertEquals("/bz", ResourceUtil.normalize("/az/../bz"));
+ assertEquals("/az/cz", ResourceUtil.normalize("/az/bz/../cz"));
+ assertEquals("/cz", ResourceUtil.normalize("/az/bz/../../cz"));
+ assertNull(ResourceUtil.normalize("/az/bz/../../../cz"));
+
+ assertEquals("/...", ResourceUtil.normalize("/..."));
+ assertEquals("/a/...", ResourceUtil.normalize("/a/..."));
+ assertEquals("/a/b/...", ResourceUtil.normalize("/a/b/..."));
+
+ assertEquals("/az/...", ResourceUtil.normalize("/az/..."));
+ assertEquals("/az/bz/...", ResourceUtil.normalize("/az/bz/..."));
+
+ try {
+ ResourceUtil.normalize(null);
+ fail("Resolving null expects NullPointerException");
+ } catch (NullPointerException npe) {
+ // expected
+ }
+ }
+
+ @Test public void testResolveRelativeSegmentsRelative() {
+ assertEquals("a/b", ResourceUtil.normalize("a/b"));
+ assertEquals("a", ResourceUtil.normalize("a/b/.."));
+
+ assertEquals("b", ResourceUtil.normalize("a/../b"));
+ assertEquals("a/c", ResourceUtil.normalize("a/b/../c"));
+ assertEquals("c", ResourceUtil.normalize("a/b/../../c"));
+ assertEquals("", ResourceUtil.normalize("a/b/../.."));
+ assertEquals("a/c/d", ResourceUtil.normalize("a/b/../c/d"));
+ assertNull(ResourceUtil.normalize("a/b/../../../c"));
+
+ assertEquals("a/b/c", ResourceUtil.normalize("a/b/c"));
+ assertEquals("az/bz/cz", ResourceUtil.normalize("az/bz/cz"));
+ assertEquals("", ResourceUtil.normalize(""));
+ }
+
+ @Test public void testGetParent() {
+ assertNull(ResourceUtil.getParent("/"));
+ assertNull(ResourceUtil.getParent("/.."));
+
+ assertEquals("/", ResourceUtil.getParent("/b"));
+ assertEquals("b/c", ResourceUtil.getParent("b/c/d"));
+ assertEquals("/b/c", ResourceUtil.getParent("/b/c/d"));
+
+ assertNull(ResourceUtil.getParent("b"));
+ assertNull(ResourceUtil.getParent("/b/.."));
+
+ assertEquals("security:/", ResourceUtil.getParent("security:/b"));
+ assertEquals("security:/b", ResourceUtil.getParent("security:/b/c"));
+ assertEquals("security:/b/c", ResourceUtil.getParent("security:/b/c/d"));
+ }
+
+ @Test public void testGetName() {
+ assertEquals("", ResourceUtil.getName("/"));
+ assertEquals("", ResourceUtil.getName("/a/.."));
+
+ assertEquals("c", ResourceUtil.getName("c"));
+ assertEquals("c", ResourceUtil.getName("/c"));
+
+ assertEquals("c", ResourceUtil.getName("b/c"));
+ assertEquals("c", ResourceUtil.getName("/b/c"));
+
+ assertEquals("c", ResourceUtil.getName("b/c/"));
+ assertEquals("c", ResourceUtil.getName("/b/c/"));
+
+ assertEquals("b", ResourceUtil.getName("b/c/.."));
+ assertEquals("b", ResourceUtil.getName("/b/c/.."));
+ assertEquals("", ResourceUtil.getName("/b/c/../.."));
+ }
+
+ @Test public void test_getValueMap_null_resource() {
+ final ValueMap valueMap = ResourceUtil.getValueMap(null);
+ assertNotNull(valueMap);
+ assertEquals(0, valueMap.size());
+
+ final Object replaced = valueMap.put("sample", 1);
+ assertNull(replaced);
+
+ assertEquals(1, valueMap.size());
+ assertEquals(1, valueMap.get("sample"));
+ assertEquals(Integer.valueOf(1), valueMap.get("sample", Integer.class));
+ assertEquals("1", valueMap.get("sample", String.class));
+ }
+
+ @Test public void test_getValueMap_direct() {
+ final ValueMap valueMap = new ValueMapDecorator(new HashMap<String, Object>());
+ valueMap.put("sample", true);
+ final Resource resource = new SyntheticResource(null, "/", "sample") {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <Type> Type adaptTo(Class<Type> type) {
+ if (type == ValueMap.class) {
+ return (Type) valueMap;
+ }
+
+ return super.adaptTo(type);
+ }
+ };
+
+ final ValueMap adapted = ResourceUtil.getValueMap(resource);
+ assertEquals(valueMap, adapted);
+ assertNotNull(adapted);
+ assertEquals(1, adapted.size());
+
+ assertEquals(true, adapted.get("sample"));
+ assertEquals(Boolean.valueOf(true), adapted.get("sample", Boolean.class));
+ assertEquals(Boolean.TRUE.toString(), adapted.get("sample", String.class));
+ }
+
+ @Test public void test_getValueMap_decorated_map() {
+ final Map<String, Object> map = new HashMap<String, Object>();
+ map.put("sample", true);
+ final Resource resource = new SyntheticResource(null, "/", "sample") {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <Type> Type adaptTo(Class<Type> type) {
+ if (type == Map.class) {
+ return (Type) map;
+ }
+
+ return super.adaptTo(type);
+ }
+ };
+
+ final ValueMap adapted = ResourceUtil.getValueMap(resource);
+ assertNotNull(adapted);
+ assertEquals(1, adapted.size());
+
+ assertEquals(true, adapted.get("sample"));
+ assertEquals(Boolean.valueOf(true), adapted.get("sample", Boolean.class));
+ assertEquals(Boolean.TRUE.toString(), adapted.get("sample", String.class));
+ }
+
+ @Test public void test_getValueMap_no_adapter() {
+ final ValueMap valueMap = ResourceUtil.getValueMap(null);
+ assertNotNull(valueMap);
+ assertEquals(0, valueMap.size());
+
+ final Object replaced = valueMap.put("sample", 1);
+ assertNull(replaced);
+
+ assertEquals(1, valueMap.size());
+ assertEquals(1, valueMap.get("sample"));
+ assertEquals(Integer.valueOf(1), valueMap.get("sample", Integer.class));
+ assertEquals("1", valueMap.get("sample", String.class));
+ }
+
+ @Test public void test_resourceTypeToPath() {
+ assertEquals("a/b", ResourceUtil.resourceTypeToPath("a:b"));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test public void test_adaptTo() {
+ // we define three resources
+ // a and b are adaptable to List
+ // a, b, and c are adaptable to Map
+ // none is adaptable to String
+ // b and c are adaptable to long
+ // a and c are adaptable to boolean
+ final Resource a = this.context.mock(Resource.class, "a");
+ final Resource b = this.context.mock(Resource.class, "b");
+ final Resource c = this.context.mock(Resource.class, "c");
+ final List<Resource> l = new ArrayList<Resource>();
+ l.add(a); l.add(b); l.add(c);
+ this.context.checking(new Expectations() {{
+ allowing(a).adaptTo(List.class); will(returnValue(new ArrayList()));
+ allowing(b).adaptTo(List.class); will(returnValue(new ArrayList()));
+ allowing(c).adaptTo(List.class); will(returnValue(null));
+ allowing(a).adaptTo(Map.class); will(returnValue(new HashMap()));
+ allowing(b).adaptTo(Map.class); will(returnValue(new HashMap()));
+ allowing(c).adaptTo(Map.class); will(returnValue(new HashMap()));
+ allowing(a).adaptTo(Long.class); will(returnValue(null));
+ allowing(b).adaptTo(Long.class); will(returnValue(new Long(1)));
+ allowing(c).adaptTo(Long.class); will(returnValue(new Long(2)));
+ allowing(a).adaptTo(Boolean.class); will(returnValue(new Boolean(true)));
+ allowing(b).adaptTo(Boolean.class); will(returnValue(null));
+ allowing(c).adaptTo(Boolean.class); will(returnValue(new Boolean(false)));
+ allowing(a).adaptTo(String.class); will(returnValue(null));
+ allowing(b).adaptTo(String.class); will(returnValue(null));
+ allowing(c).adaptTo(String.class); will(returnValue(null));
+ }});
+
+ assertEquals(2, checkIterator(l, List.class));
+ assertEquals(3, checkIterator(l, Map.class));
+ assertEquals(0, checkIterator(l, String.class));
+ assertEquals(2, checkIterator(l, Long.class));
+ assertEquals(2, checkIterator(l, Boolean.class));
+ }
+
+ private <T> int checkIterator(final List<Resource> resources, final Class<T> type) {
+ final Iterator<T> i = ResourceUtil.adaptTo(resources.iterator(), type);
+ // we call hasNext() several times upfront
+ i.hasNext();
+ i.hasNext();
+ int count = 0;
+ while ( i.hasNext() ) {
+ final T object = i.next();
+ assertNotNull(object);
+ count++;
+ }
+ assertFalse(i.hasNext());
+ // next should throw an exception
+ try {
+ i.next();
+ fail("Iterator should have reached end.");
+ } catch (NoSuchElementException nsee) {
+ // fine
+ }
+ return count;
+ }
+
+ @Test public void testIsStarResource() {
+ final Resource nonStar = context.mock(Resource.class, "nonStarResource");
+ final String starPath = "/foo/*";
+ final Resource star = context.mock(Resource.class, "starResource");
+ final String nonStarPath = "/foo/*";
+ this.context.checking(new Expectations() {{
+ allowing(star).getPath(); will(returnValue(starPath));
+ allowing(nonStar).getPath(); will(returnValue(nonStarPath));
+ }});
+
+ assertTrue("expecting star==true for path" + starPath,
+ ResourceUtil.isStarResource(star));
+ assertTrue("expecting star==false for path" + starPath,
+ ResourceUtil.isStarResource(nonStar));
+ }
+ @Test public void testIsSyntheticResource() {
+ final Resource synth = new SyntheticResource(null, "foo", "bar");
+ final Resource star = context.mock(Resource.class);
+ this.context.checking(new Expectations() {{
+ allowing(star).getPath(); will(returnValue("/foo/*"));
+ }});
+ final Resource wrapped = new ResourceWrapper(synth);
+
+ assertTrue("expecting synthetic==true for SyntheticResource",
+ ResourceUtil.isSyntheticResource(synth));
+ assertFalse("expecting synthetic==false for star resource",
+ ResourceUtil.isSyntheticResource(star));
+ assertTrue("expecting synthetic==true for wrapped Resource",
+ ResourceUtil.isSyntheticResource(wrapped));
+ }
+
+ @Test public void testGetParentLevel() throws Exception {
+ boolean caughtNullPointerException = false;
+ try {
+ ResourceUtil.getParent(null, 4);
+ } catch (NullPointerException e) {
+ // Expected exception
+ caughtNullPointerException = true;
+ } catch (Exception e) {
+ fail("Expected NullPointerException, but caught " +
+ e.getClass().getName() + " instead.");
+ }
+ if (!caughtNullPointerException) {
+ fail("Expected NullPointerException, but no exception was thrown.");
+ }
+
+ boolean caughtIllegalArgumentException = false;
+ try {
+ ResourceUtil.getParent("/a/b", -2);
+ } catch (IllegalArgumentException e) {
+ // Expected exception
+ caughtIllegalArgumentException = true;
+ } catch (Exception e) {
+ fail("Expected IllegalArgumentException, but caught " +
+ e.getClass().getName() + " instead.");
+ }
+ if (!caughtIllegalArgumentException) {
+ fail("Expected IllegalArgumentException, but no exception was thrown.");
+ }
+
+ assertNull(ResourceUtil.getParent("/a", 4));
+ assertNull(ResourceUtil.getParent("/", 1));
+ assertNull(ResourceUtil.getParent("b/c", 2));
+ assertNull(ResourceUtil.getParent("/b/..", 1));
+ assertNull(ResourceUtil.getParent("b", 1));
+ assertNull(ResourceUtil.getParent("", 3));
+ assertNull(ResourceUtil.getParent("/..", 1));
+ assertNull(ResourceUtil.getParent("security:/b", 2));
+ assertNull(ResourceUtil.getParent("/b///", 2));
+
+ assertEquals("", ResourceUtil.getParent("", 0));
+ assertEquals("b", ResourceUtil.getParent("b", 0));
+ assertEquals("/", ResourceUtil.getParent("/", 0));
+ assertEquals("/a/b", ResourceUtil.getParent("/a/b", 0));
+ assertEquals("security:/b", ResourceUtil.getParent("security:/b", 0));
+
+ assertEquals("/", ResourceUtil.getParent("/b", 1));
+ assertEquals("b", ResourceUtil.getParent("b/c", 1));
+ assertEquals("b/c", ResourceUtil.getParent("b/c/d", 1));
+ assertEquals("/b/c", ResourceUtil.getParent("/b/c/d", 1));
+ assertEquals("security:/", ResourceUtil.getParent("security:/b", 1));
+ assertEquals("security:/b", ResourceUtil.getParent("security:/b/c", 1));
+ assertEquals("security:/b/c", ResourceUtil.getParent("security:/b/c/d", 1));
+
+ assertEquals("b", ResourceUtil.getParent("b/c/d", 2));
+ assertEquals("b/c", ResourceUtil.getParent("b/c/d/e", 2));
+ assertEquals("/", ResourceUtil.getParent("/b/c/d", 3));
+ assertEquals("/", ResourceUtil.getParent("/b///", 1));
+ }
+
+ @Test public void testIsA() {
+ assertFalse(ResourceUtil.isA(null, "something"));
+ }
+
+ @Test public void testFindResourceSuperType() {
+ assertNull(ResourceUtil.findResourceSuperType(null));
+ }
+}