/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-server-core:0.2.2
 *   Bundle      : ldp4j-server-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.server.data.spi;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ReflectPermission;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import javax.ws.rs.core.MediaType;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class RuntimeDelegate {

	private static final String INSTANTIATE_ACTION = "instantiate";

	private static final Logger LOGGER=LoggerFactory.getLogger(RuntimeDelegate.class);

	public static final String LDP4J_SERVER_DATA_SPI_RUNTIMEDELEGATE_FINDER = "org.ldp4j.server.data.spi.RuntimeDelegate.finder";

	/**
	 * Name of the configuration file where the
	 * {@link RuntimeDelegate#LDP4J_SERVER_DATA_SPI_PROPERTY} property that
	 * identifies the {@link RuntimeDelegate} implementation to be returned from
	 * {@link RuntimeDelegate#getInstance()} can be defined.
	 */
	public static final String LDP4J_SERVER_DATA_SPI_CFG = "ldp4j-server-data.properties";

	/**
	 * Name of the property identifying the {@link RuntimeDelegate} implementation
	 * to be returned from {@link RuntimeDelegate#getInstance()}.
	 */
	public static final String LDP4J_SERVER_DATA_SPI_PROPERTY = "org.ldp4j.server.data.spi.RuntimeDelegate";

	private static final AtomicReference<RuntimeDelegate> CACHED_DELEGATE=new AtomicReference<RuntimeDelegate>();

	private static ReflectPermission suppressAccessChecksPermission = new ReflectPermission("suppressAccessChecks");

	/**
	 * Allows custom implementations to extend the {@code RuntimeInstance} class.
	 */
	protected RuntimeDelegate() {
	}

	/**
	 * Obtain a {@code RuntimeInstance} instance using the method described in
	 * {@link #getInstance}.
	 *
	 * @return an instance of {@code RuntimeInstance}.
	 */
	private static RuntimeDelegate findDelegate() {
		try {
			RuntimeDelegate result=createRuntimeDelegateFromSPI();
			if(result==null) {
				result=createRuntimeDelegateFromConfigurationFile();
			}

			if(result==null) {
				String delegateClassName = System.getProperty(LDP4J_SERVER_DATA_SPI_PROPERTY);
				if(delegateClassName!=null) {
					result=createRuntimeDelegateForClassName(delegateClassName);
				}
			}

			if(result==null) {
				result=new DefaultRuntimeInstance();
			}

			return result;
		} catch (Exception ex) {
			throw new IllegalStateException("Could not find runtime delegate",ex);
		}
	}

	private static RuntimeDelegate createRuntimeDelegateFromConfigurationFile() {
		RuntimeDelegate result=null;
		File configFile = getConfigurationFile();
		if(configFile.canRead()) {
			InputStream is=null;
			try {
				is=new FileInputStream(configFile);
				Properties configProperties=new Properties();
				configProperties.load(is);
				String delegateClassName=configProperties.getProperty(LDP4J_SERVER_DATA_SPI_PROPERTY);
				if(delegateClassName!=null) {
					result=createRuntimeDelegateForClassName(delegateClassName);
				}
				if(delegateClassName==null && LOGGER.isWarnEnabled()) {
					LOGGER.warn("MutableConfiguration file '"+configFile.getAbsolutePath()+"' does not define a delegate class name");
				}
			} catch(FileNotFoundException e) {
				if(LOGGER.isDebugEnabled()) {
					LOGGER.debug("Could not find runtime instance configuration file '"+configFile.getAbsolutePath()+"'",e);
				}
			} catch(IOException e) {
				if(LOGGER.isWarnEnabled()) {
					LOGGER.warn("Could not load runtime instance configuration file '"+configFile.getAbsolutePath()+"'",e);
				}
			} finally {
				closeQuietly(is, "Could find close properties");
			}
		}
		return result;
	}

	/**
	 * Get the configuration file for the Runtime Instance: a file named
	 * {@value #LDP4J_SERVER_DATA_SPI_CFG} in the lib directory of
	 * current JAVA_HOME.
	 *
	 * @return The configuration file for the runtime instance.
	 */
	private static File getConfigurationFile() {
		return new File(new File(System.getProperty("java.home")),"lib"+File.separator+LDP4J_SERVER_DATA_SPI_CFG);
	}

	/**
	 * Close an input stream logging possible failures.
	 * @param is The input stream that is to be closed.
	 * @param message The message to log in case of failure.
	 */
	private static void closeQuietly(InputStream is, String message) {
		if(is!=null) {
		try {
			is.close();
		} catch (Exception e) {
			if(LOGGER.isWarnEnabled()) {
				LOGGER.warn(message,e);
			}
		}
		}
	}

	private static RuntimeDelegate createRuntimeDelegateFromSPI() {
		if(!"disable".equalsIgnoreCase(System.getProperty(LDP4J_SERVER_DATA_SPI_RUNTIMEDELEGATE_FINDER))) {
			for (RuntimeDelegate delegate : ServiceLoader.load(RuntimeDelegate.class)) {
				return delegate;
			}
		}
		return null;
	}

	private static RuntimeDelegate createRuntimeDelegateForClassName(String delegateClassName) {
		RuntimeDelegate result = null;
		try {
			Class<?> delegateClass = Class.forName(delegateClassName);
			if(RuntimeDelegate.class.isAssignableFrom(delegateClass)) {
				Object impl = delegateClass.newInstance();
				result = RuntimeDelegate.class.cast(impl);
			}
		} catch (ClassNotFoundException e) {
			handleFailure(delegateClassName, "find", e);
		} catch (InstantiationException e) {
			handleFailure(delegateClassName, INSTANTIATE_ACTION, e);
		} catch (IllegalAccessException e) {
			handleFailure(delegateClassName, INSTANTIATE_ACTION, e);
		}
		return result;
	}

	private static void handleFailure(String delegateClassName, String action,
			Exception failure) {
		if(LOGGER.isWarnEnabled()) {
			LOGGER.warn("Could not "+action+" delegate class "+delegateClassName,failure);
		}
	}

	/**
	 * Obtain a {@code RuntimeDelegate} instance. If an instance had not already
	 * been created and set via {@link #setInstance(RuntimeDelegate)}, the first
	 * invocation will create an instance which will then be cached for future
	 * use.
	 *
	 *

	 * The algorithm used to locate the RuntimeDelegate subclass to use consists
	 * of the following steps:
	 *

	 *

	 *

	 * If a resource with the name of
	 * {@code META-INF/services/org.ldp4j.server.data.spi.RuntimeDelegate} exists, then
	 * its first line, if present, is used as the UTF-8 encoded name of the
	 * implementation class.

	 *

	 * If the $java.home/lib/ldp4j-server-data.properties file exists and it is readable by
	 * the {@code java.util.Properties.load(InputStream)} method and it contains
	 * an entry whose key is {@code org.ldp4j.server.data.spi.RuntimeDelegate}, then the
	 * value of that entry is used as the name of the implementation class.

	 *

	 * If a system property with the name
	 * {@code org.ldp4j.server.data.spi.RuntimeDelegate} is defined, then its value is
	 * used as the name of the implementation class.

	 *

	 * Finally, a default implementation class name is used.

	 *

	 *
	 * @return an instance of {@code RuntimeDelegate}.
	 */
	public static RuntimeDelegate getInstance() {
		RuntimeDelegate result = RuntimeDelegate.CACHED_DELEGATE.get();
		if (result != null) {
			return result;
		}
		synchronized(RuntimeDelegate.CACHED_DELEGATE) {
			result=RuntimeDelegate.CACHED_DELEGATE.get();
			if(result==null) {
				RuntimeDelegate.CACHED_DELEGATE.set(findDelegate());
				result=RuntimeDelegate.CACHED_DELEGATE.get();
			}
			return result;
		}
	}

	/**
	 * Set the runtime delegate that will be used by Data API classe.
	 * If this method is not called prior to {@link #getInstance} then
	 * an implementation will be sought as described in {@link #getInstance}.
	 *
	 * @param delegate
	 *            the {@code RuntimeInstance} runtime delegate instance.
	 * @throws SecurityException
	 *             if there is a security manager and the permission
	 *             ReflectPermission("suppressAccessChecks") has not been
	 *             granted.
	 */
	public static void setInstance(final RuntimeDelegate delegate) {
		SecurityManager security = System.getSecurityManager();
		if (security != null) {
			security.checkPermission(suppressAccessChecksPermission);
		}
		RuntimeDelegate.CACHED_DELEGATE.set(delegate);
	}

	public abstract Set<MediaType> getSupportedMediaTypes();

	public abstract MediaTypeProvider getMediaTypeProvider(MediaType mediaType);

	public abstract void registerMediaTypeProvider(MediaTypeProvider provider);

	private static class DefaultRuntimeInstance extends RuntimeDelegate {

		private static final String ERROR_MESSAGE = String.format("No implementation for '%s' found",RuntimeDelegate.class);

		@Override
		public Set<MediaType> getSupportedMediaTypes() {
			throw new AssertionError(ERROR_MESSAGE);
		}

		@Override
		public MediaTypeProvider getMediaTypeProvider(MediaType mediaType) {
			throw new AssertionError(ERROR_MESSAGE);
		}

		@Override
		public void registerMediaTypeProvider(MediaTypeProvider provider) {
			throw new AssertionError(ERROR_MESSAGE);
		}

	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.commons:ldp4j-commons-core:0.2.2
 *   Bundle      : ldp4j-commons-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.commons.net;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.StringTokenizer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class URIUtils {

	private static final String TARGET_URI_CANNOT_BE_NULL = "Target URI cannot be null";

	private static final String BASE_URI_CANNOT_BE_NULL = "Base URI cannot be null";

	private static final String SLASH = "/";
	private static final String PARENT = "..";
	private static final String EMPTY = "";
	private static final Logger LOGGER=LoggerFactory.getLogger(URIUtils.class);

	private URIUtils() {
	}

	public static URL toURL(URI uri) throws MalformedURLException {
		Objects.requireNonNull(uri, "URI cannot be null");
		try {
			return uri.toURL();
		} catch(MalformedURLException e) {
			return tryFallback(uri, e);
		}
	}

	public static URI relativize(URI base, URI target) {
		Objects.requireNonNull(base,BASE_URI_CANNOT_BE_NULL);
		Objects.requireNonNull(target,TARGET_URI_CANNOT_BE_NULL);

		URI nTarget = target.normalize();
		if(areRelativizable(base,target)) {
			URI nBase=base.normalize();
			if(nBase.equals(nTarget)) {
				nTarget=URI.create(EMPTY);
			} else {
				URI walkthrough = absoluteRelativization(nBase,nTarget);
				if(!(walkthrough.getPath().startsWith(PARENT) && nTarget.getPath().isEmpty())) {
					nTarget=walkthrough;
				}
			}
		}
		return nTarget;
	}

	public static URI resolve(URI base, URI target) {
		Objects.requireNonNull(base,BASE_URI_CANNOT_BE_NULL);
		Objects.requireNonNull(target,TARGET_URI_CANNOT_BE_NULL);

		if(areOpaque(base, target)) {
			return target;
		}

		return relativeResolution(target, base).toURI();
	}

	private static boolean areRelativizable(URI base, URI target) {
		return
			!areOpaque(base, target) &&
			haveSameScheme(base, target) &&
			haveSameAuthority(base, target);
	}

	private static boolean haveSameAuthority(URI base, URI target) {
		return Objects.equals(base.getAuthority(),target.getAuthority());
	}

	private static boolean haveSameScheme(URI base, URI target) {
		return Objects.equals(base.getScheme(),target.getScheme());
	}

	private static boolean areOpaque(URI base, URI target) {
		return base.isOpaque() || target.isOpaque();
	}

	private static boolean defined(String value) {
		return value!=null && !value.isEmpty();
	}

	private static URL createFallback(URI uri) {
		URL fallback=null;
		String scheme = uri.getScheme();
		if(ProtocolHandlerConfigurator.isSupported(scheme)) {
			URLStreamHandler handler = ProtocolHandlerConfigurator.getHandler(scheme);
			try {
				fallback=new URL(scheme,null,0,uri.toString().replace(scheme+":",EMPTY),handler);
			} catch (MalformedURLException fatal) {
				if(LOGGER.isWarnEnabled()) {
					LOGGER.warn(String.format("Fallback solution for supported custom '%s' protocol failed. Full stack trace follows.",scheme),fatal);
				}
			}
		}
		if(LOGGER.isDebugEnabled()) {
			LOGGER.debug(
				String.format(
					fallback==null?
						"Support for custom '%s' protocol not configured":
						"Support for custom '%s' protocol was configured, but the handler was not instantiated automatically. Fallback solution provided.",
					scheme));
		}
		return fallback;
	}

	private static URL tryFallback(URI uri, MalformedURLException e) throws MalformedURLException {
		URL fallback = null;
		if(e.getMessage().startsWith("unknown protocol")) {
			fallback=createFallback(uri);
		}
		if(fallback!=null) {
			return fallback;
		} else {
			throw e;
		}
	}

	/**
	 * 5.2.  Relative Resolution
	 *
	 *    This section describes an algorithm for converting a URI reference
	 *    that might be relative to a given base URI into the parsed components
	 *    of the reference's target.  The components can then be recomposed, as
	 *    described in Section 5.3, to form the target URI.  This algorithm
	 *    provides definitive results that can be used to test the output of
	 *    other implementations.  Applications may implement relative reference
	 *    resolution by using some other algorithm, provided that the results
	 *    match what would be given by this one.
	 *
	 * 5.2.1.  Pre-parse the Base URI
	 *
	 *    The base URI (Base) is established according to the procedure of
	 *    Section 5.1 and parsed into the five main components described in
	 *    Section 3.  Note that only the scheme component is required to be
	 *    present in a base URI; the other components may be empty or
	 *    undefined.  A component is undefined if its associated delimiter does
	 *    not appear in the URI reference; the path component is never
	 *    undefined, though it may be empty.
	 *
	 *    Normalization of the base URI, as described in Sections 6.2.2 and
	 *    6.2.3, is optional.  A URI reference must be transformed to its
	 *    target URI before it can be normalized.
	 *
	 * 5.2.2.  Transform References
	 *
	 *    For each URI reference (R), the following pseudocode describes an
	 *    algorithm for transforming R into its target URI (T):
	 *       -- The URI reference is parsed into the five URI components
	 *       --
	 *       (R.scheme, R.authority, R.path, R.query, R.fragment) = parse(R);
	 *
	 *       -- A non-strict parser may ignore a scheme in the reference
	 *       -- if it is identical to the base URI's scheme.
	 *       --
	 *       if ((not strict) and (R.scheme == Base.scheme)) then
	 *          undefine(R.scheme);
	 *       endif;
	 *
	 *       if defined(R.scheme) then
	 *          T.scheme    = R.scheme;
	 *          T.authority = R.authority;
	 *          T.path      = remove_dot_segments(R.path);
	 *          T.query     = R.query;
	 *       else
	 *          if defined(R.authority) then
	 *             T.authority = R.authority;
	 *             T.path      = remove_dot_segments(R.path);
	 *             T.query     = R.query;
	 *          else
	 *             if (R.path == "") then
	 *                T.path = Base.path;
	 *                if defined(R.query) then
	 *                   T.query = R.query;
	 *                else
	 *                   T.query = Base.query;
	 *                endif;
	 *             else
	 *                if (R.path starts-with "/") then
	 *                   T.path = remove_dot_segments(R.path);
	 *                else
	 *                   T.path = merge(Base.path, R.path);
	 *                   T.path = remove_dot_segments(T.path);
	 *                endif;
	 *                T.query = R.query;
	 *             endif;
	 *             T.authority = Base.authority;
	 *          endif;
	 *          T.scheme = Base.scheme;
	 *       endif;
	 *
	 *       T.fragment = R.fragment;
	 */
	private static URIRef relativeResolution(URI target, URI base) {
		URIRef Base=URIRef.create(base); // NOSONAR
		URIRef R=URIRef.create(target); // NOSONAR
		URIRef T=URIRef.create(); // NOSONAR
		if(defined(R.scheme)) {
			T.scheme    = R.scheme;
			T.authority = R.authority;
			T.path      = removeDotSegments(R.path);
			T.query     = R.query;
		} else {
			if(defined(R.authority)) {
				T.authority = R.authority;
				T.path      = removeDotSegments(R.path);
				T.query     = R.query;
			} else {
				resolvePathOnlyTarget(Base, R, T);
			}
			T.scheme = Base.scheme;
		}
		T.fragment = R.fragment;
		return T;
	}

	private static void resolvePathOnlyTarget(URIRef Base, URIRef R, URIRef T) { // NOSONAR
		if(R.path.isEmpty()) {
			T.path=Base.path;
			if(defined(R.query)) {
				T.query=R.query;
			} else {
				T.query=Base.query;
			}
		} else {
			if(R.path.startsWith(SLASH)) {
				T.path=removeDotSegments(R.path);
			} else {
				T.path=merge(Base.path,R.path,defined(Base.authority));
				T.path=removeDotSegments(T.path);
			}
			T.query=R.query;
		}
		T.authority = Base.authority;
	}

	/**
	 * 5.2.3.  Merge Paths
	 *
	 *    The pseudocode above refers to a "merge" routine for merging a
	 *    relative-path reference with the path of the base URI.  This is
	 *    accomplished as follows:
	 *
	 *    o  If the base URI has a defined authority component and an empty
	 *       path, then return a string consisting of "/" concatenated with the
	 *       reference's path; otherwise,
	 *    o  return a string consisting of the reference's path component
	 *       appended to all but the last segment of the base URI's path (i.e.,
	 *       excluding any characters after the right-most "/" in the base URI
	 *       path, or excluding the entire base URI path if it does not contain
	 *       any "/" characters).
	 *
	 * @param hasAuthority
	 */
	private static String merge(String path, String relativePath, boolean hasAuthority) {
		String parent=path;
		if(hasAuthority && parent.isEmpty()) {
			parent=SLASH;
		}
		return parent.substring(0,parent.lastIndexOf('/')+1).concat(relativePath);
	}

	/**
	 * 5.2.4.  Remove Dot Segments
	 *
	 *    The pseudocode also refers to a "remove_dot_segments" routine for
	 *    interpreting and removing the special "." and ".." complete path
	 *    segments from a referenced path.  This is done after the path is
	 *    extracted from a reference, whether or not the path was relative, in
	 *    order to remove any invalid or extraneous dot-segments prior to
	 *    forming the target URI.  Although there are many ways to accomplish
	 *    this removal process, we describe a simple method using two string
	 *    buffers.
	 *
	 *    1.  The input buffer is initialized with the now-appended path
	 *        components and the output buffer is initialized to the empty
	 *        string.
	 *
	 *    2.  While the input buffer is not empty, loop as follows:
	 *
	 *        A.  If the input buffer begins with a prefix of "../" or "./",
	 *            then remove that prefix from the input buffer; otherwise,
	 *
	 *        B.  if the input buffer begins with a prefix of "/./" or "/.",
	 *            where "." is a complete path segment, then replace that
	 *            prefix with "/" in the input buffer; otherwise,
	 *
	 *        C.  if the input buffer begins with a prefix of "/../" or "/..",
	 *            where ".." is a complete path segment, then replace that
	 *            prefix with "/" in the input buffer and remove the last
	 *            segment and its preceding "/" (if any) from the output
	 *            buffer; otherwise,
	 *
	 *        D.  if the input buffer consists only of "." or "..", then remove
	 *            that from the input buffer; otherwise,
	 *
	 *        E.  move the first path segment in the input buffer to the end of
	 *            the output buffer, including the initial "/" character (if
	 *            any) and any subsequent characters up to, but not including,
	 *            the next "/" character or the end of the input buffer.
	 *
	 *    3.  Finally, the output buffer is returned as the result of
	 *        remove_dot_segments.
	 */
	private static String removeDotSegments(String path) {
		Deque<String> outputBuffer=new LinkedList<String>();
		String input=path==null?EMPTY:path;
		while(!input.isEmpty()) {
			input=processInput(outputBuffer, input);
		}
		return assembleInOrder(outputBuffer);
	}

	private static String processInput(Deque<String> outputBuffer, String input) {
		String next=null;
		if(input.startsWith("../")) {
			next=input.substring(3);
		} else if(input.startsWith("./") || input.startsWith("/./")) {
			next=input.substring(2);
		} else if("/.".equals(input)) {
			next=SLASH;
		} else if(input.startsWith("/../")) {
			next=discardSegment(outputBuffer, input, "/../");
		} else if("/..".equals(input)) {
			next=discardSegment(outputBuffer, input, "/..");
		} else if(PARENT.equals(input) || ".".equals(input)) {
			next=EMPTY;
		} else {
			next=discardSegment(outputBuffer, input);
		}
		return next;
	}

	private static String discardSegment(Deque<String> outputBuffer,String input) {
		int nextSlash=0;
		if(input.startsWith(SLASH)) {
			nextSlash=input.indexOf('/',1);
		} else {
			nextSlash=input.indexOf('/',0);
		}
		String nextSegment=input;
		String next=EMPTY;
		if(nextSlash>=0) {
			nextSegment=input.substring(0,nextSlash);
			next=input.substring(nextSlash);
		}
		addSegment(outputBuffer, nextSegment);
		return next;
	}

	private static String assembleInOrder(Deque<String> outputBuffer) {
		Deque<String> reverse=new LinkedList<String>();
		for(String item:outputBuffer) {
			reverse.push(item);
		}
		StringBuilder builder=new StringBuilder();
		for(String item:reverse) {
			builder.append(item);
		}
		return builder.toString();
	}

	private static void addSegment(Deque<String> outputBuffer, String nextSegment) {
		outputBuffer.push(nextSegment);
	}

	private static String discardSegment(Deque<String> outputBuffer, String input, String prefix) {
		if(!outputBuffer.isEmpty()) {
			outputBuffer.pop();
		}
		if(!outputBuffer.isEmpty() && SLASH.equals(outputBuffer.peek())) {
			outputBuffer.pop();
		}
		return SLASH+input.substring(prefix.length());
	}

	private static URI absoluteRelativization(URI base, URI target) {
		URI relative = null;
		URIDescriptor dBase=URIDescriptor.create(base);
		URIDescriptor dTarget=URIDescriptor.create(target);
		if(dBase.getDir().equals(dTarget.getDir())) {
			String rawURI=EMPTY;
			if(!dBase.getFile().equals(dTarget.getFile())) {
				rawURI=dTarget.getFile();
			}
			rawURI+=makeSuffix(dTarget.getQuery(),dTarget.getFragment());
			relative = URI.create(rawURI);
		} else {
			String[] baseDirPathSegments=tokenize(dBase.getDir());
			String[] targetDirPathSegments=tokenize(dTarget.getDir());
			int common = findCommonSegments(baseDirPathSegments, targetDirPathSegments);
			List<String> segments=
				getRelativeSegments(
					targetDirPathSegments,
					common,
					baseDirPathSegments.length-common);

			relative = recreateFromSegments(segments, dTarget);
		}
		return relative;
	}

	private static String[] tokenize(String path) {
		StringTokenizer tokenizer=new StringTokenizer(path,SLASH);
		List<String> segments=new ArrayList<String>();
		if(path.contains(SLASH)) {
			segments.add(EMPTY);
			while(tokenizer.hasMoreTokens()) {
				segments.add(tokenizer.nextToken());
			}
		}
		return segments.toArray(new String[segments.size()]);
	}

	private static String makeSuffix(String query, String fragment) {
		StringBuilder suffix=new StringBuilder();
		if(defined(query)) {
			suffix.append("?").append(query);
		}
		if(defined(fragment)) {
			suffix.append("#").append(fragment);
		}
		return suffix.toString();
	}

	private static int findCommonSegments(String[] basePath, String[] targetPath) {
		int common=0;
		while(common<basePath.length && common<targetPath.length) {
			if(basePath[common].equals(targetPath[common])) {
				common++;
			} else {
				break;
			}
		}
		return common;
	}

	private static List<String> getRelativeSegments(
			String[] targetPath,
			int commonSegments,
			int discardedSegments) {
		List<String> segments=new ArrayList<String>();
		for(int j=0;j<discardedSegments;j++) {
			segments.add(PARENT);
		}
		segments.
			addAll(
				Arrays.asList(
					Arrays.copyOfRange(
						targetPath,
						commonSegments,
						targetPath.length)));
		return segments;
	}

	private static URI recreateFromSegments(List<String> segments, URIDescriptor target) {
		StringBuilder builder=new StringBuilder();
		boolean ancestor=false;
		for(Iterator<String> it=segments.iterator();it.hasNext();) {
			String last = it.next();
			builder.append(last);
			if(it.hasNext()) {
				builder.append(SLASH);
			} else {
				ancestor=last.equals(PARENT);
			}
		}

		String file=
			new StringBuilder().
				append(target.getFile()).
				append(makeSuffix(target.getQuery(),target.getFragment())).
				toString();

		if(!ancestor || !file.isEmpty()) {
			builder.append(SLASH);
		}
		builder.append(file);

		return URI.create(builder.toString());
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-kernel-mem:0.2.2
 *   Bundle      : ldp4j-application-kernel-mem-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.kernel.impl;

import static com.google.common.base.Preconditions.*;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.ldp4j.application.kernel.lifecycle.LifecycleException;
import org.ldp4j.application.kernel.lifecycle.Managed;
import org.ldp4j.application.kernel.resource.Container;
import org.ldp4j.application.kernel.resource.Resource;
import org.ldp4j.application.kernel.resource.ResourceId;
import org.ldp4j.application.kernel.resource.ResourceRepository;
import org.ldp4j.application.kernel.resource.ResourceVisitor;

final class InMemoryResourceRepository implements Managed, ResourceRepository {

	private final ReadWriteLock lock=new ReentrantReadWriteLock();
	private final Map<ResourceId,Resource> resources=new LinkedHashMap<ResourceId,Resource>();
	private final Map<ResourceId,Container> containers=new LinkedHashMap<ResourceId,Container>();

	InMemoryResourceRepository() {
	}

	private Resource find(ResourceId id) {
		lock.readLock().lock();
		try {
			Resource result=resources.get(id);
			if(result==null) {
				result=containers.get(id);
			}
			return result;
		} finally {
			lock.readLock().unlock();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <T extends Resource> T resourceById(ResourceId id, Class<? extends T> expectedResourceClass) {
		checkNotNull(expectedResourceClass,"Expected resource class cannot be null");
		Resource found=find(id);
		if(expectedResourceClass.isInstance(found)) {
			return expectedResourceClass.cast(found);
		}
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Resource resourceOfId(ResourceId id) {
		lock.readLock().lock();
		try {
			return resources.get(id);
		} finally {
			lock.readLock().unlock();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Container containerOfId(ResourceId id) {
		lock.readLock().lock();
		try {
			return containers.get(id);
		} finally {
			lock.readLock().unlock();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void add(Resource resource) {
		checkNotNull(resource,"Resource cannot be null");
		lock.writeLock().lock();
		try {
			checkState(find(resource.id())==null,"A resource with identifier '%s' already exists",resource.id());
			resource.accept(
				new ResourceVisitor() {
					@Override
					public void visitResource(Resource resource) {
						InMemoryResourceRepository.this.resources.put(resource.id(),resource);
					}
					@Override
					public void visitContainer(Container resource) {
						InMemoryResourceRepository.this.containers.put(resource.id(),resource);
					}
				}
			);
		} finally {
			lock.writeLock().unlock();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void remove(Resource resource) {
		checkNotNull(resource,"Resource cannot be null");
		lock.writeLock().lock();
		try {
			resources.remove(resource.id());
			containers.remove(resource.id());
		} finally {
			lock.writeLock().unlock();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void init() throws LifecycleException {
		// Nothing to do
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void shutdown() throws LifecycleException {
		lock.writeLock().lock();
		try {
			resources.clear();
			containers.clear();
		} finally {
			lock.writeLock().unlock();
		}
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-server-core:0.2.2
 *   Bundle      : ldp4j-server-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.server.controller;

import static com.google.common.base.Preconditions.checkState;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.Variant;

import org.ldp4j.application.data.DataSet;
import org.ldp4j.application.data.ManagedIndividualId;
import org.ldp4j.application.engine.context.ApplicationContext;
import org.ldp4j.application.engine.context.ApplicationContextOperation;
import org.ldp4j.application.engine.context.ContentPreferences;
import org.ldp4j.application.engine.context.CreationPreferences;
import org.ldp4j.application.engine.context.CreationPreferences.InteractionModel;
import org.ldp4j.application.engine.context.EntityTag;
import org.ldp4j.application.engine.context.HttpRequest.HttpMethod;
import org.ldp4j.application.engine.context.PublicContainer;
import org.ldp4j.application.engine.context.PublicResource;
import org.ldp4j.application.ext.Query;
import org.ldp4j.application.sdk.QueryBuilder;
import org.ldp4j.rdf.Namespaces;
import org.ldp4j.server.data.DataTransformator;
import org.ldp4j.server.data.ResourceResolver;
import org.ldp4j.server.data.UnsupportedMediaTypeException;
import org.ldp4j.server.utils.CharsetSelector;
import org.ldp4j.server.utils.VariantHelper;
import org.ldp4j.server.utils.VariantUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;

final class OperationContextImpl implements OperationContext {

	private final class OperationContextResourceResolver implements ResourceResolver {

		@Override
		public URI resolveResource(ManagedIndividualId id) {
			URI result=null;
			PublicResource resolvedResource=applicationContextOperation.resolveResource(id);
			if(resolvedResource!=null) {
				result=base().resolve(resolvedResource.path());
				LOGGER.trace("Resolved resource {} URI to '{}'",id,result);
			}
			return result;
		}

		@Override
		public ManagedIndividualId resolveLocation(URI path) {
			PublicResource resolvedResource =
				applicationContextOperation.
					resolveResource(base().relativize(path).toString());

			ManagedIndividualId result = null;
			if(resolvedResource!=null) {
				result=resolvedResource.individualId();
				LOGGER.trace("Resolved location '{}' to resource {}",path,result);
			}
			return result;
		}

	}

	private static final Logger LOGGER=LoggerFactory.getLogger(OperationContextImpl.class);

	private final ApplicationContext applicationContext;
	private final String             endpointPath;
	private final HttpMethod         method;
	private final UriInfo            uriInfo;
	private final HttpHeaders        headers;
	private final Request            request;
	private final String             entity;

	private ApplicationContextOperation applicationContextOperation;
	private PublicResource              resource;
	private DataSet                     dataSet;

	private CharsetSelector charsetSelector;

	OperationContextImpl(
		ApplicationContext applicationContext,
		String endpointPath,
		UriInfo uriInfo,
		HttpHeaders headers,
		Request request,
		String entity,
		HttpMethod method) {
		this.applicationContext = applicationContext;
		this.endpointPath = endpointPath;
		this.method = method;
		this.uriInfo=uriInfo;
		this.headers=headers;
		this.request=request;
		this.entity = entity;
		LOGGER.trace("Host.........: {}",headers.getHeaderString(HttpHeaders.HOST));
		LOGGER.trace("Request uri..: {}",this.uriInfo.getRequestUri());
		LOGGER.trace("Base.........: {}",base());
		LOGGER.trace("Endpoint.....: {}",endpoint());
	}

	private Variant contentVariant() {
		List<String> requestHeader=
			headers().
				getRequestHeader(HttpHeaders.CONTENT_ENCODING);

		List<Variant> variants=
			Variant.
				mediaTypes(headers().getMediaType()).
				encodings(requestHeader.toArray(new String[requestHeader.size()])).
				languages(headers().getLanguage()).
				add().
				build();

		return variants.get(0);
	}

	private String slug() {
		List<String> slugs = this.headers.getRequestHeader("Slug");
		String slug=null;
		if(!slugs.isEmpty()) {
			slug=slugs.get(0);
		}
		return slug;
	}

	private InteractionModel interactionModel() {
		InteractionModel result=null;
		for(String linkHeader:this.headers.getRequestHeader(HttpHeaders.LINK)) {
			result= InteractionModelUtils.fromLink(linkHeader);
			if(result!=null) {
				break;
			}
		}
		return result;
	}

	private ResourceResolver resourceResolver() {
		return new OperationContextResourceResolver();
	}

	private URI endpoint() {
		return URI.create(path());
	}

	private String normalizePath(String path) {
		String tPath=path;
		if(tPath==null) {
			tPath="";
		} else {
			tPath = tPath.trim();
		}
		return tPath;
	}

	private boolean isMethodAllowed() {
		boolean allowed;
		switch(this.method) {
			case GET:
			case HEAD:
			case OPTIONS:
				allowed=true;
				break;
			case DELETE:
				allowed=resource().capabilities().isDeletable();
				break;
			case PATCH:
				allowed=resource().capabilities().isPatchable();
				break;
			case POST:
				allowed=resource().capabilities().isFactory();
				break;
			case PUT:
				allowed=resource().capabilities().isModifiable();
				break;
			default:
				allowed=false;
		}
		return allowed;
	}

	private CharsetSelector charsetSelector() {
		if(this.charsetSelector==null) {
			final List<Variant> variants=VariantUtils.defaultVariants();
			final Variant variant = this.request.selectVariant(variants);
			this.charsetSelector=
				CharsetSelector.
					newInstance().
						mediaType(variant==null?null:variant.getMediaType()).
						acceptableCharsets(this.headers.getRequestHeader(HttpHeaders.ACCEPT_CHARSET)).
						supportedCharsets(supportedCharsets());
		}
		return this.charsetSelector;
	}

	UriInfo uriInfo() {
		return this.uriInfo;
	}

	HttpHeaders headers() {
		return this.headers;
	}

	Request request() {
		return this.request;
	}

	String entity() {
		return this.entity;
	}

	@Override
	public URI base() {
		return URI.create(this.uriInfo.getBaseUri().toString().concat("/")).normalize();
	}

	@Override
	public String path() {
		return this.endpointPath;
	}

	@Override
	public Query getQuery() {
		final MultivaluedMap<String,String> queryParameters = this.uriInfo.getQueryParameters();
		final QueryBuilder builder = QueryBuilder.newInstance();
		for(final Entry<String, List<String>> entry:queryParameters.entrySet()) {
			final String parameterName = entry.getKey();
			for(final String rawValue:entry.getValue()) {
				builder.withParameter(parameterName, rawValue);
			}
		}
		return builder.build();
	}

	@Override
	public boolean isResourceQueryable() {
		return resource().capabilities().isQueryable();
	}

	@Override
	public OperationContext checkContents() {
		List<Variant> supportedVariants=VariantUtils.defaultVariants();
		if(entity()==null || entity().isEmpty()) {
			throw new MissingContentException(this);
		}
		if(headers().getMediaType()==null) {
			throw new MissingContentTypeException(this);
		}
		if(!VariantHelper.
				forVariants(supportedVariants).
					isSupported(contentVariant())) {
			throw new UnsupportedContentException(this,contentVariant());
		}
		return this;
	}

	@Override
	public OperationContext checkPreconditions() {
		EntityTag entityTag=this.resource.entityTag();
		Date lastModified=this.resource.lastModified();
		if(HttpMethod.PUT.equals(this.method)) {
			List<String> requestHeader = this.headers.getRequestHeader(HttpHeaders.IF_MATCH);
			if(requestHeader==null || requestHeader.isEmpty()) {
				throw new PreconditionRequiredException(this);
			}
		}
		ResponseBuilder builder =
			request().
				evaluatePreconditions(
					lastModified,
					new javax.ws.rs.core.EntityTag(entityTag.getValue()));
		if(builder!=null) {
			Response response = builder.build();
			throw new PreconditionFailedException(this,response.getStatus());
		}
		return this;
	}

	@Override
	public OperationContext checkOperationSupport() {
		if(!isMethodAllowed()) {
			throw new MethodNotAllowedException(this,this.method);
		}
		return this;
	}

	@Override
	public DataSet dataSet() {
		if(this.dataSet==null) {
			MediaType mediaType=contentVariant().getMediaType();
			try {
				DataTransformator transformator =
					DataTransformator.
						create(base()).
						enableResolution(resourceResolver()).
						mediaType(mediaType);
				if(this.method.equals(HttpMethod.POST)) {
					transformator=transformator.surrogateEndpoint(endpoint());
				} else {
					transformator=transformator.permanentEndpoint(endpoint());
				}
				this.dataSet=transformator.unmarshall(this.entity);
			} catch(UnsupportedMediaTypeException e) {
				throw new UnsupportedContentException(this,contentVariant(),e);
			} catch(IOException e) {
				throw new InvalidRequestContentException("Entity cannot be parsed as '"+mediaType+"' ("+Throwables.getRootCause(e).getMessage()+")",e,this);
			}
		}
		return this.dataSet;
	}

	@Override
	public Variant expectedVariant() {
		List<Variant> variants=VariantUtils.defaultVariants();
		Variant variant=this.request.selectVariant(variants);
		if(variant==null) {
			throw new NotAcceptableException(this);
		}
		String acceptableCharset=acceptedCharset();
		if(acceptableCharset==null) {
			throw new NotAcceptableException(this);
		}
		return
			Variant.
				encodings(variant.getEncoding()).
				languages(variant.getLanguage()).
				mediaTypes(variant.getMediaType().withCharset(acceptableCharset)).
				add().
				build().
				get(0);
	}

	@Override
	public boolean expectsCharset() {
		return charsetSelector().requiresCharset();
	}

	@Override
	public String acceptedCharset() {
		return charsetSelector().select();
	}

	/**
	 * TODO: Add extension point to enable configuring the list of supported
	 * charsets
	 */
	@Override
	public List<Charset> supportedCharsets() {
		return
			ImmutableList.
				<Charset>builder().
					add(StandardCharsets.UTF_8).
					add(StandardCharsets.ISO_8859_1).
					add(StandardCharsets.US_ASCII).
					add(StandardCharsets.UTF_16).
					add(StandardCharsets.UTF_16LE).
					add(StandardCharsets.UTF_16BE).
					build();
	}

	@Override
	public CreationPreferences creationPreferences() {
		return
			CreationPreferences.
				builder().
					withInteractionModel(interactionModel()).
					withPath(slug()).
					build();
	}

	@Override
	public URI resolve(PublicResource resource) {
		return base().resolve(resource.path());
	}

	@Override
	public String serialize(DataSet representation, Namespaces namespaces, MediaType mediaType) {
		try {
			DataTransformator transformator =
				DataTransformator.
					create(base()).
					enableResolution(resourceResolver()).
					mediaType(mediaType).
					namespaces(namespaces).
					permanentEndpoint(endpoint());
			return transformator.marshall(representation);
		} catch(UnsupportedMediaTypeException e) {
			throw new UnsupportedContentException(this,contentVariant(),e);
		} catch(IOException e) {
			throw new ContentProcessingException("Resource representation cannot be parsed as '"+mediaType+"'. Expecting content matching ",e,this,Status.BAD_REQUEST);
		}
	}

	@Override
	public PublicResource resource() {
		if(this.resource==null) {
			this.resource=
				this.applicationContextOperation.
					findResource(
						normalizePath(this.endpointPath));
		}
		return this.resource;
	}

	@Override
	public PublicContainer container() {
		PublicResource tmp = resource();
		checkState(tmp instanceof PublicContainer,"Expected an instance of class %s but got an instance of class %s",PublicContainer.class.getCanonicalName(),tmp.getClass().getCanonicalName());
		return (PublicContainer)tmp;
	}

	@Override
	public ContentPreferences contentPreferences() {
		ContentPreferences result = null;
		List<String> requestHeader = this.headers.getRequestHeader(ContentPreferencesUtils.PREFER_HEADER);
		for(Iterator<String> it=requestHeader.iterator();it.hasNext() && result==null;) {
			String header = it.next();
			try {
				result=ContentPreferencesUtils.fromPreferenceHeader(header);
			} catch (InvalidPreferenceHeaderException e) {
				LOGGER.debug("Ignored prefer header {}",header,e);
			}
		}
		return result;
	}

	@Override
	public Namespaces applicationNamespaces() {
		org.ldp4j.application.ext.Namespaces applicationNamespaces = this.applicationContext.applicationNamespaces();
		Namespaces namespaces = new Namespaces();
		for(String prefix:applicationNamespaces.getDeclaredPrefixes()) {
			namespaces.addPrefix(prefix, applicationNamespaces.getNamespaceURI(prefix));
		}
		return namespaces;
	}

	@Override
	public void startOperation() {
		this.applicationContextOperation=
			this.applicationContext.
				createOperation(
					HttpRequestFactory.
						create(
							this.method,
							this.uriInfo,
							this.headers,
							this.entity));
	}

	@Override
	public void completeOperation() {
		checkState(this.applicationContextOperation!=null,"Operation not started");
		this.applicationContextOperation.dispose();
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-kernel-core:0.2.2
 *   Bundle      : ldp4j-application-kernel-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.kernel.session;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.ldp4j.application.session.AttachmentSnapshot;
import org.ldp4j.application.session.ResourceSnapshot;
import org.ldp4j.application.data.Name;
import org.ldp4j.application.kernel.resource.Attachment;
import org.ldp4j.application.kernel.resource.Resource;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;

final class AttachmentSnapshotCollection {

	static final class DelegatedAttachmentSnapshot implements AttachmentSnapshot {

		private final String id;
		private final DelegatedResourceSnapshot resource;

		private DelegatedAttachmentSnapshot(String attachmentId, DelegatedResourceSnapshot resource) {
			this.id = attachmentId;
			this.resource = resource;
		}

		@Override
		public final String id() {
			return id;
		}

		@Override
		public final DelegatedResourceSnapshot resource() {
			return this.resource;
		}

		@Override
		public int hashCode() {
			return
				System.identityHashCode(this.resource) +
				Objects.hashCode(this.id);
		}

		@Override
		public boolean equals(Object obj) {
			return this == obj;
		}

		@Override
		public String toString() {
			return
				MoreObjects.
					toStringHelper(AttachmentSnapshot.class).
						omitNullValues().
						add("id", this.id).
						add("resource.name()", this.resource.name()).
						toString();
		}

	}

	public enum Attachability {
		ATTACHABLE("Not attached") {
			@Override
			public boolean canAttach() {
				return true;
			}
		},
		ID_ALREADY_USED("AttachmentSnapshot identifier %1$s already used"),
		;

		private final String message;

		Attachability(String message) {
			this.message = message;
		}

		public boolean canAttach() {
			return false;
		}

		String description(AttachmentSnapshot attachment) {
			return String.format(this.message,attachment.id(),attachment.resource().name());
		}

	}

	private final Set<DelegatedAttachmentSnapshot> attachments;
	private final Map<String, DelegatedAttachmentSnapshot> attachmentsById;
	private final Map<Name<?>, DelegatedAttachmentSnapshot> attachmentsByName;

	private AttachmentSnapshotCollection() {
		this.attachments=new LinkedHashSet<DelegatedAttachmentSnapshot>();
		this.attachmentsByName=new LinkedHashMap<Name<?>, DelegatedAttachmentSnapshot>();
		this.attachmentsById=new LinkedHashMap<String, DelegatedAttachmentSnapshot>();
	}

	private void safeAttach(DelegatedAttachmentSnapshot attachment) {
		attachments.add(attachment);
		attachmentsByName.put(attachment.resource().name(),attachment);
		attachmentsById.put(attachment.id(),attachment);
	}

	void add(DelegatedAttachmentSnapshot attachment) {
		checkNotNull(attachment,"Attachment snapshot cannot be null");
		AttachmentSnapshotCollection.Attachability attachability = attachability(attachment.id(),attachment.resource().name());
		checkState(attachability.canAttach(),attachability.description(attachment));
		safeAttach((DelegatedAttachmentSnapshot)attachment);
	}

	boolean remove(DelegatedAttachmentSnapshot attachment) {
		checkNotNull(attachment,"Attachment snapshot cannot be null");
		boolean result = attachments.remove(attachment);
		if(result) {
			attachmentsByName.remove(attachment.resource().name());
			attachmentsById.remove(attachment.id());
		}
		return result;
	}

	Set<DelegatedAttachmentSnapshot> attachments() {
		return Collections.unmodifiableSet(new LinkedHashSet<DelegatedAttachmentSnapshot>(attachments));
	}

	DelegatedAttachmentSnapshot findByResource(ResourceSnapshot resource) {
		checkNotNull(resource,"Attached delegate cannot be null");
		DelegatedAttachmentSnapshot attachment = attachmentsByName.get(resource.name());
		if(attachment!=null && attachment.resource()!=resource) {
			attachment=null;
		}
		return attachment;
	}

	DelegatedAttachmentSnapshot findById(String attachmentId) {
		checkNotNull(attachmentId,"Attachment snapshot identifier cannot be null");
		return attachmentsById.get(attachmentId);
	}

	/**
	 * TODO: For the time being the name is not used in here, because the
	 * checking should not just be local: we should check that that name has not
	 * been used for any other resource of the same template, and that requires
	 * a global checking. Whenever the check is done somewhere else, get rid of
	 * this parameter
	 */
	Attachability attachability(String attachmentId, Name<?> name) { // NOSONAR
		Attachability result=null;
		if(this.attachmentsById.containsKey(attachmentId)) {
			result=Attachability.ID_ALREADY_USED;
		} else {
			result=Attachability.ATTACHABLE;
		}
		return result;
	}

	@Override
	public String toString() {
		return attachments.toString();
	}

	static DelegatedAttachmentSnapshot newAttachment(String attachmentId, DelegatedResourceSnapshot resource) {
		checkNotNull(attachmentId,"Attachment snapshot identifier cannot be null");
		checkNotNull(resource,"Attached resource cannot be null");
		checkNotNull(resource.name(),"Attached resource name cannot be null");
		return new DelegatedAttachmentSnapshot(attachmentId, resource);
	}

	static AttachmentSnapshotCollection createFromResource(Resource resource, DelegatedWriteSession session) {
		AttachmentSnapshotCollection repository=new AttachmentSnapshotCollection();
		for(Attachment attachment:resource.attachments()) {
			DelegatedResourceSnapshot snapshot = session.resolveResource(attachment.resourceId());
			checkState(snapshot!=null,"Could not resolve attached resource of attachment %s of resource %s",attachment,resource.id());
			DelegatedAttachmentSnapshot newAttachment=new DelegatedAttachmentSnapshot(attachment.id(),snapshot);
			repository.safeAttach(newAttachment);
		}
		return repository;
	}

	static AttachmentSnapshotCollection newInstance() {
		return new AttachmentSnapshotCollection();
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-kernel-core:0.2.2
 *   Bundle      : ldp4j-application-kernel-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.kernel.template;

import org.ldp4j.application.ext.ResourceHandler;
import org.ldp4j.application.kernel.template.BasicContainerTemplate;
import org.ldp4j.application.kernel.template.ContainerTemplate;
import org.ldp4j.application.kernel.template.DirectContainerTemplate;
import org.ldp4j.application.kernel.template.IndirectContainerTemplate;
import org.ldp4j.application.kernel.template.MembershipAwareContainerTemplate;
import org.ldp4j.application.kernel.template.ResourceTemplate;
import org.ldp4j.application.kernel.template.TemplateLibrary;
import org.ldp4j.application.kernel.template.TemplateVisitor;

final class ImmutableTemplateLibrary implements TemplateLibrary {

	private static final class SafeTemplateVisitor implements TemplateVisitor {

		private final TemplateVisitor visitor;

		private SafeTemplateVisitor(TemplateVisitor visitor) {
			this.visitor = visitor;
		}

		@Override
		public void visitResourceTemplate(ResourceTemplate template) {
			ImmutableTemplateFactory.newImmutable(template).accept(this.visitor);
		}

		@Override
		public void visitContainerTemplate(ContainerTemplate template) {
			ImmutableTemplateFactory.newImmutable(template).accept(this.visitor);
		}

		@Override
		public void visitBasicContainerTemplate(BasicContainerTemplate template) {
			ImmutableTemplateFactory.newImmutable(template).accept(this.visitor);
		}

		@Override
		public void visitMembershipAwareContainerTemplate(MembershipAwareContainerTemplate template) {
			ImmutableTemplateFactory.newImmutable(template).accept(this.visitor);
		}

		@Override
		public void visitDirectContainerTemplate(DirectContainerTemplate template) {
			ImmutableTemplateFactory.newImmutable(template).accept(this.visitor);
		}

		@Override
		public void visitIndirectContainerTemplate(IndirectContainerTemplate template) {
			ImmutableTemplateFactory.newImmutable(template).accept(this.visitor);
		}
	}

	private final TemplateLibrary delegate;

	ImmutableTemplateLibrary(TemplateLibrary delegate) {
		this.delegate = delegate;
	}

	private TemplateLibrary delegate() {
		return this.delegate;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ResourceTemplate findByHandler(Class<? extends ResourceHandler> handlerClass) {
		return ImmutableTemplateFactory.newImmutable(delegate().findByHandler(handlerClass));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ResourceTemplate findById(String templateId) {
		return ImmutableTemplateFactory.newImmutable(delegate().findById(templateId));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean contains(ResourceTemplate template) {
		return delegate().contains(template);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void accept(final TemplateVisitor visitor) {
		delegate().accept(new SafeTemplateVisitor(visitor));
	}
}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-engine-sdk:0.2.2
 *   Bundle      : ldp4j-application-engine-sdk-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.sdk;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Map;
import java.util.Set;

import org.ldp4j.application.ext.Parameter;
import org.ldp4j.application.ext.Query;

import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;

/**
 * An immutable {@code Query} implementation.
 */
final class ImmutableQuery implements Query {

	private static final long serialVersionUID = 663489687029161424L;

	private final ImmutableMap<String, ImmutableQueryParameter> parameters;

	private ImmutableQuery(Map<String, ImmutableQueryParameter> params) {
		this.parameters=ImmutableMap.copyOf(params);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isEmpty() {
		return this.parameters.isEmpty();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int size() {
		return this.parameters.size();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean hasParameter(String paramName) {
		return this.parameters.containsKey(paramName);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Parameter getParameter(String paramName) {
		checkNotNull(paramName,"Parameter name cannot be null");
		return this.parameters.get(paramName);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {
		return
			MoreObjects.
				toStringHelper(getClass()).
					add("parameters",this.parameters.values()).
					toString();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Set<String> parameterNames() {
		return this.parameters.keySet();
	}

	static ImmutableQuery create(Map<String,ImmutableQueryParameter> parameters) {
		return new ImmutableQuery(parameters);
	}
}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-kernel-jpa:0.2.2
 *   Bundle      : ldp4j-application-kernel-jpa-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.kernel.persistence.jpa;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.ldp4j.application.kernel.resource.Container;
import org.ldp4j.application.kernel.resource.ResourceId;
import org.ldp4j.application.kernel.resource.Slug;

import com.google.common.base.MoreObjects;

final class JPASlug implements Slug {

	private static final Pattern SLUG_PATH_PATTERN=Pattern.compile("(^.*)(_(\\d+)?$)");

	/**
	 * Persistent key required by JPA
	 */
	private long primaryKey;

	/**
	 * Surrogate key to guarantee DB portability
	 */
	private Key containerId;

	/**
	 * Not final to enable its usage in JPA
	 */
	private String preferredPath;

	/**
	 * Not final to enable its usage in JPA
	 */
	private long version;

	private JPASlug() {
		// JPA friendly
	}

	private JPASlug(ResourceId containerId, String preferredPath, long version) {
		this();
		this.containerId = Key.newInstance(containerId);
		this.preferredPath = preferredPath;
		this.version=version;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ResourceId containerId() {
		return this.containerId.resourceId();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String preferredPath() {
		return this.preferredPath;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized long version() {
		return this.version;
	}

	synchronized void setVersion(long version) {
		this.version=version;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized String nextPath() {
		long id=this.version++;
		String result = this.preferredPath;
		if(id>0) {
			result+="_"+id;
		}
		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {
		return
			MoreObjects.
				toStringHelper(getClass()).
					omitNullValues().
					add("primaryKey",this.primaryKey).
					add("containerId",this.containerId).
					add("preferredPath",this.preferredPath).
					add("version",version()).
					toString();
	}

	static JPASlug create(String slugPath, Container container) {
		Matcher matcher=SLUG_PATH_PATTERN.matcher(slugPath);
		String path=slugPath;
		long version=0;
		if(matcher.matches()) {
			String strVersion = matcher.group(3);
			if(strVersion!=null) {
				String strPath = matcher.group(1);
				if(!strPath.isEmpty()) {
					path=strPath;
					version=Long.parseLong(strVersion);
				}
			}
		}
		return new JPASlug(container.id(),path,version);
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-kernel-core:0.2.2
 *   Bundle      : ldp4j-application-kernel-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.kernel.lifecycle;

import java.util.Date;
import java.util.List;

import org.ldp4j.application.data.Name;
import org.ldp4j.application.engine.ApplicationConfigurationException;
import org.ldp4j.application.engine.context.EntityTag;
import org.ldp4j.application.ext.ResourceHandler;
import org.ldp4j.application.kernel.endpoint.Endpoint;
import org.ldp4j.application.kernel.endpoint.EndpointRepository;
import org.ldp4j.application.kernel.resource.Resource;
import org.ldp4j.application.kernel.resource.ResourceId;
import org.ldp4j.application.kernel.resource.ResourceRepository;
import org.ldp4j.application.kernel.spi.ModelFactory;
import org.ldp4j.application.kernel.template.ResourceTemplate;
import org.ldp4j.application.kernel.template.TemplateManagementService;
import org.ldp4j.application.lifecycle.LifecycleEnvironment;
import org.ldp4j.application.setup.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;

final class EnvironmentImpl implements Environment {

	private static final Logger LOGGER=LoggerFactory.getLogger(EnvironmentImpl.class);

	private final class RootResource {

		private final Name<?> resourceName;
		private final Class<? extends ResourceHandler> handlerClass;
		private final String path;
		private ResourceTemplate template;

		RootResource(Name<?> resourceName, Class<? extends ResourceHandler> handlerClass, String path) {
			this.resourceName = resourceName;
			this.handlerClass = handlerClass;
			this.path = path;
		}

		Name<?> name() {
			return this.resourceName;
		}

		String path() {
			return path;
		}

		ResourceTemplate template() {
			return this.template;
		}

		ResourceId resourceId() {
			return ResourceId.createId(this.resourceName, this.template.id());
		}

		void validate() throws ApplicationConfigurationException {
			assertNameNotNull();
			assertHandlerClassNotNull();
			this.template = EnvironmentImpl.this.templateManagementService.templateOfHandler(this.handlerClass);
			assertTemplateExists();
			assertValidPath();
		}

		/**
		 * TODO: What about URL encoding issues? Decide were do we take care of
		 * this, as it also impacts the creation of new endpoints during the
		 * normal operation of the LDP4j application
		 */
		private void assertValidPath() throws ApplicationConfigurationException {
			if(this.path==null) {
				throw new ApplicationConfigurationException("No path specified for resource '"+this.resourceName+"'");
			}
			if(this.path.startsWith("/") || !this.path.endsWith("/") || this.path.endsWith("//")) {
				throw new ApplicationConfigurationException("Invalid path '"+this.path+"' specified for resource '"+this.resourceName+"': it must end with a single '/' and have at least one segment");
			}
		}

		private void assertTemplateExists() throws ApplicationConfigurationException {
			if(this.template==null) {
				throw new ApplicationConfigurationException("Unknown resource handler '"+this.handlerClass.getCanonicalName()+"' specified for resource '"+this.resourceName+"'");
			}
		}

		private void assertHandlerClassNotNull() throws ApplicationConfigurationException {
			if(this.handlerClass==null) {
				throw new ApplicationConfigurationException("No handler class specified for resource '"+this.resourceName+"'");
			}
		}

		private void assertNameNotNull() throws ApplicationConfigurationException {
			if(this.resourceName==null) {
				throw new ApplicationConfigurationException("Resource name cannot be null");
			}
		}

	}

	private final List<RootResource> candidates;

	private final ModelFactory modelFactory;

	private final TemplateManagementService templateManagementService;

	private final ResourceRepository resourceRepository;
	private final EndpointRepository endpointRepository;

	private final LifecycleEnvironment lifecycleEnvironment;

	EnvironmentImpl(TemplateManagementService templateManagementService, ModelFactory persistencyManager, EndpointRepository endpointRepository, ResourceRepository resourceRepository, LifecycleEnvironment lifecycleEnvironment) {
		this.templateManagementService = templateManagementService;
		this.resourceRepository = resourceRepository;
		this.endpointRepository = endpointRepository;
		this.modelFactory = persistencyManager;
		this.candidates=Lists.newArrayList();
		this.lifecycleEnvironment=lifecycleEnvironment;
	}

	@Override
	public void publishResource(Name<?> resourceName, Class<? extends ResourceHandler> handlerClass, String path) {
		RootResource rootResource = new RootResource(resourceName,handlerClass,path);
		this.candidates.add(rootResource);
	}

	@Override
	public LifecycleEnvironment lifecycle() {
		return this.lifecycleEnvironment;
	}

	/**
	 * TODO: Check that root resources are published in different "branches"
	 */
	void configureRootResources() throws ApplicationConfigurationException {
		validateRootResources();
		final Date creationDate = new Date();
		for(RootResource entry:this.candidates) {
			publish(entry, creationDate);
			LOGGER.debug("Published resource '{}' at '{}'",entry.resourceId(),entry.path());
		}
	}

	private void publish(RootResource rootResource, Date creationDate) throws ApplicationConfigurationException {
		ResourceId resourceId = rootResource.resourceId();
		String path = rootResource.path();

		Resource prevResource = this.resourceRepository.resourceById(resourceId,Resource.class);
		Endpoint prevEndpoint = this.endpointRepository.endpointOfPath(path);

		if(prevEndpoint!=null && !prevEndpoint.resourceId().equals(resourceId)) {
			throw new ApplicationConfigurationException(String.format("Resource %s cannot be published at '%s' as that path is already in use by a resource %s",toString(resourceId),path,toString(prevEndpoint.resourceId())));
		}

		if(prevEndpoint==null) {
			if(prevResource!=null) {
				throw new ApplicationConfigurationException(String.format("Resource %s cannot be published at '%s' as it is already published at '%s'",toString(resourceId),path,this.endpointRepository.endpointOfResource(resourceId).path()));
			} else {
				Resource resource=this.modelFactory.createResource(rootResource.template(),rootResource.name());
				this.resourceRepository.add(resource);
				Endpoint endpoint=this.modelFactory.createEndpoint(path,resource,creationDate,EntityTag.createStrong(path));
				this.endpointRepository.add(endpoint);
			}
		}

	}

	private void validateRootResources() throws ApplicationConfigurationException {
		BiMap<ResourceId,String> rootResourceMap=HashBiMap.create();
		for(RootResource candidateResource:this.candidates) {
			addPublication(rootResourceMap,candidateResource);
		}
	}

	private void addPublication(final BiMap<ResourceId, String> rootResourceMap, RootResource candidateResource) throws ApplicationConfigurationException {
		candidateResource.validate();
		ResourceId resourceId = candidateResource.resourceId();
		String path = candidateResource.path();
		String prevPath=rootResourceMap.get(resourceId);
		if(prevPath!=null && !prevPath.equals(path)) {
			throw new ApplicationConfigurationException(String.format("Resource %s is already published (%s)",toString(resourceId),prevPath));
		}
		ResourceId prevResource=rootResourceMap.inverse().get(path);
		if(prevResource!=null && !prevResource.equals(resourceId)) {
			throw new ApplicationConfigurationException(String.format("Path '%s' is already used by resource %s",path,toString(prevResource)));
		}
		rootResourceMap.put(resourceId, path);
	}

	private Class<? extends ResourceHandler> resolveHandler(ResourceId resourceId) {
		ResourceTemplate template = this.templateManagementService.templateOfId(resourceId.templateId());
		return template!=null?template.handlerClass():null;
	}

	private String toString(ResourceId resourceId) {
		Class<?> handler=resolveHandler(resourceId);
		if(handler!=null) {
			return String.format("named '%s' with handler class '%s'",resourceId.name(),handler.getName());
		} else {
			return String.format("named '%s' of unknown template '%s'",resourceId.name(),resourceId.templateId());
		}
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-api:0.2.2
 *   Bundle      : ldp4j-application-api-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.data;

import java.io.Serializable;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;

import org.ldp4j.application.vocabulary.Term;

import com.google.common.collect.ImmutableMap;

public final class NamingScheme {

	public enum NameType {
		TERM(Term.class),
		URI(URI.class),
		QNAME(QName.class),
		STRING(String.class),
		NUMBER(Number.class),
		SERIALIZABLE(Serializable.class),
		;

		private final Class<?> clazz;

		private NameType(Class<?> clazz) {
			this.clazz = clazz;
		}

		private static NameType valueOf(Object t) {
			for(NameType candidate:values()) {
				if(candidate.clazz.isInstance(t)) {
					return candidate;
				}
			}
			throw new IllegalArgumentException("Invalid name type '"+t.getClass().getCanonicalName()+"'");
		}

		@Override
		public String toString() {
			return clazz.getCanonicalName();
		}

	}

	private enum NamingStrategy {
		LOCAL,
		GLOBAL,
		;
	}

	private interface NameFactory {

		<T extends Serializable> Name<T> create(T id);

	}

	private static final class GlobalNameFactory implements NameFactory {

		@Override
		public <T extends Serializable> Name<T> create(T id) {
			return ImmutableName.newGlobalName(id);
		}

	}

	private static final class LocalNameFactory implements NameFactory {

		@Override
		public <T extends Serializable> Name<T> create(T id) {
			return ImmutableName.newLocalName(id);
		}

	}

	public static final class NamingSchemeBuilder {

		private Map<NameType, NamingStrategy> configuration=new HashMap<NameType, NamingStrategy>();

		private void addMappings(NamingStrategy strategy, NameType type, NameType... rest) {
			this.configuration.put(type, strategy);
			for(NameType r:rest) {
				this.configuration.put(r, strategy);
			}
		}

		public NamingSchemeBuilder withLocal(NameType type, NameType... rest) {
			addMappings(NamingStrategy.LOCAL, type, rest);
			return this;
		}

		public NamingSchemeBuilder withGlobal(NameType type, NameType... rest) {
			addMappings(NamingStrategy.GLOBAL, type, rest);
			return this;
		}

		public NamingScheme build() {
			return new NamingScheme(this.configuration);
		}

	}

	private static final Map<NamingStrategy,NameFactory> FACTORIES=
		ImmutableMap.<NamingStrategy, NameFactory>
			builder().
				put(NamingStrategy.GLOBAL, new GlobalNameFactory()).
				put(NamingStrategy.LOCAL, new LocalNameFactory()).
				build();

	private final Map<NameType, NamingStrategy> configuration;

	private NamingScheme(Map<NameType,NamingStrategy> configuration) {
		this.configuration = configuration;
	}

	private NameFactory getFactory(NameType type) {
		NamingStrategy namingStrategy = configuration.get(type);
		if(namingStrategy==null) {
			namingStrategy=NamingStrategy.GLOBAL;
		}
		return FACTORIES.get(namingStrategy);
	}

	private static void append(StringBuilder builder, String part) {
		if (part != null && !part.isEmpty()) {
			if (builder.length() > 0) {
				builder.append('.');
			}
			builder.append(part);
		}
	}

	private static String assemble(String name, String... names) {
		StringBuilder builder=new StringBuilder();
		append(builder,name);
		if(names!=null) {
			for(String s:names) {
				append(builder,s);
			}
		}
		return builder.toString();
	}

	private <T extends Serializable> Name<T> createName(T id) {
		return getFactory(NameType.valueOf(id)).create(id);
	}

	public Name<URI> name(URI id) {
		return createName(id);
	}

	public Name<QName> name(QName id) {
		return createName(id);
	}

	public Name<Term> name(Term id) {
		return createName(id);
	}

	/**
	 * Concatenates elements to form a dotted name, discarding null values
	 * and empty strings.
	 *
	 * @param name
	 *            the first element of the name
	 * @param names
	 *            the remaining elements of the name
	 * @return {@code name} and {@code names} concatenated by periods
	 */
	public Name<String> name(String name, String... names) {
		return createName(assemble(name, names));
	}

	/**
	 * Concatenates a canonical class name and elements to form a dotted name, discarding any null values or empty strings
	 * any null values or empty strings.
	 *
	 * @param clazz
	 *            the first element of the name
	 * @param names
	 *            the remaining elements of the name
	 * @return {@code clazz} and {@code names} concatenated by periods
	 */
	public Name<String> name(Class<?> clazz, String... names) {
		return name(clazz.getCanonicalName(), names);
	}

	public <T extends Number> Name<T> name(T id) {
		return createName(id);
	}

	public <T extends Serializable> Name<T> name(T id) {
		return createName(id);
	}

	public static NamingSchemeBuilder builder() {
		return new NamingSchemeBuilder();
	}

	public static NamingScheme getDefault() {
		return builder().build();
	}
}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-kernel-core:0.2.2
 *   Bundle      : ldp4j-application-kernel-core-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.kernel.session;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import java.net.URI;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.ldp4j.application.data.Individual;
import org.ldp4j.application.data.ManagedIndividualId;
import org.ldp4j.application.data.Name;
import org.ldp4j.application.ext.ContainerHandler;
import org.ldp4j.application.ext.ResourceHandler;
import org.ldp4j.application.kernel.resource.Resource;
import org.ldp4j.application.kernel.resource.ResourceId;
import org.ldp4j.application.kernel.session.UnitOfWork.EventHandler;
import org.ldp4j.application.kernel.template.ResourceTemplate;
import org.ldp4j.application.kernel.template.TemplateIntrospector;
import org.ldp4j.application.session.ContainerSnapshot;
import org.ldp4j.application.session.ResourceSnapshot;
import org.ldp4j.application.session.SessionTerminationException;
import org.ldp4j.application.session.WriteSession;
import org.ldp4j.application.session.WriteSessionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

final class DelegatedWriteSession implements WriteSession {

	enum Status {
		ACTIVE,
		COMPLETED,
		ABORTED
	}

	private final class MembershipAwareContainerTargetCollector implements EventHandler {

		private final class TargetCollector implements DelegatedSnapshotVisitor {

			@Override
			public void visitDelegatedResourceSnapshot(final DelegatedResourceSnapshot child) {
				if(child.isRoot()) {
					return;
				}
				child.parent().accept(
					new DelegatedSnapshotVisitor() {
						@Override
						public void visitDelegatedResourceSnapshot(DelegatedResourceSnapshot resource) {
							// Nothing special to do
						}
						@Override
						public void visitDelegatedContainerSnapshot(DelegatedContainerSnapshot container) {
							if(!container.hasMember(child) || !TemplateIntrospector.newInstance(container.template()).isMembershipAwareContainer()) {
								return;
							}
							if(!container.isRoot()) {
								UnitOfWork.getCurrent().registerDirty(container.parent());
							}
						}
					}
				);
			}

			@Override
			public void visitDelegatedContainerSnapshot(DelegatedContainerSnapshot resource) {
				visitDelegatedResourceSnapshot(resource);
			}
		}

		@Override
		public void notifyObjectCreation(DelegatedResourceSnapshot obj) {
			obj.accept(new TargetCollector());
		}

		@Override
		public void notifyObjectUpdate(DelegatedResourceSnapshot obj) {
			// Not needed
		}

		@Override
		public void notifyObjectDeletion(DelegatedResourceSnapshot obj) {
			// Not needed
		}
	}

	private static final Logger LOGGER=LoggerFactory.getLogger(DelegatedWriteSession.class);
	private static final String RESOURCE_NAME_CANNOT_BE_NULL = "Resource snapshot name cannot be null";
	private static final String RESOURCE_CANNOT_BE_NULL      = "Resource snapshot cannot be null";
	private static final String WRITE_SESSION_NOT_ACTIVE     = "Write session is not active (%s)";

	private final Map<ResourceId,DelegatedResourceSnapshot> resourceCache;

	private final WriteSessionConfiguration configuration;
	private final WriteSessionService writeSessionService;

	private final SnapshotFactory snapshotFactory;

	private volatile Status status;

	protected DelegatedWriteSession(WriteSessionConfiguration configuration, WriteSessionService writeSessionService) {
		this.configuration = configuration;
		this.writeSessionService = writeSessionService;
		UnitOfWork.
			getCurrent().
				setEventHandler(new MembershipAwareContainerTargetCollector());
		this.status=Status.ACTIVE;
		this.resourceCache=new LinkedHashMap<ResourceId,DelegatedResourceSnapshot>();
		this.snapshotFactory=SnapshotFactory.newInstance(this);
	}

	private DelegatedResourceSnapshot resolveResource(ResourceId resourceId, ResourceTemplate template) {
		DelegatedResourceSnapshot resource=this.resourceCache.get(resourceId);
		if(resource==null) {
			Resource delegate=loadResource(resourceId);
			if(delegate!=null) {
				resource=snapshotFactory.newPersistent(delegate,template);
				resource.setSession(this);
				this.resourceCache.put(resourceId, resource);
			}
		}
		return resource;
	}

	private <S extends ResourceSnapshot> S find(
			Class<? extends S> snapshotClass,
			ResourceId resourceId) {
		ResourceTemplate template=loadTemplate(resourceId.templateId());
		checkArgument(template!=null,"Unknown template '%s' ",resourceId.templateId());
		checkArgument(Snapshots.areCompatible(snapshotClass,template),"Cannot wrap an object managed by '%s' with an snapshot of type '%s'",resourceId.templateId(),snapshotClass.getCanonicalName());
		DelegatedResourceSnapshot resource = resolveResource(resourceId, template);
		return snapshotClass.cast(resource);
	}

	/**
	 * @param individual
	 * @return
	 */
	private ResourceId getIdentifier(final Individual<?, ?> individual) {
		ResourceId result=null;
		Object rawId=individual.id();
		if(rawId instanceof ManagedIndividualId) {
			ManagedIndividualId id=(ManagedIndividualId)rawId;
			result=ResourceId.createId(id.name(),id.managerId());
		}
		return result;
	}

	private boolean isMainResource(DelegatedResourceSnapshot snapshot) {
		ResourceSnapshot targetSnapshot = getTargetSnapshot();
		if(!(targetSnapshot instanceof DelegatedContainerSnapshot)) {
			return false;
		}
		DelegatedContainerSnapshot targetContainerSnapshot=(DelegatedContainerSnapshot)targetSnapshot;
		List<DelegatedResourceSnapshot> newMembers=targetContainerSnapshot.newMembers();
		LOGGER.debug("New members: {}",newMembers);
		if(newMembers.isEmpty()) {
			return false;
		}
		return snapshot==newMembers.get(0);
	}

	private ResourceSnapshot getTargetSnapshot() {
		ResourceSnapshot targetSnapshot=null;
		Resource target=this.configuration.getTarget();
		if(target!=null) {
			targetSnapshot=find(ResourceSnapshot.class, target.id());
		}
		return targetSnapshot;
	}

	ResourceTemplate loadTemplate(String templateId) {
		return this.writeSessionService.templateManagementService().templateOfId(templateId);
	}

	Resource loadResource(ResourceId resourceId) {
		return this.writeSessionService.resourceOfId(resourceId);
	}

	DelegatedResourceSnapshot resolveResource(ResourceId resourceId) {
		return resolveResource(resourceId,loadTemplate(resourceId.templateId()));
	}

	String getDesiredPath(DelegatedResourceSnapshot snapshot) {
		String desiredPath=null;
		if(isMainResource(snapshot)) {
			desiredPath=this.configuration.getPath();
		}
		if(LOGGER.isDebugEnabled()) {
			if(desiredPath==null && this.configuration.getPath()!=null) {
				LOGGER.debug("Resource {} is not main resource",snapshot.resourceId());
			} else if(desiredPath!=null) {
				LOGGER.debug("Resource {} is the main resource and may be deployed at '{}'",snapshot.resourceId(),desiredPath);
			} else {
				LOGGER.debug("Resource {} is the main resource but does not have a deployment preference",snapshot.resourceId());
			}
		}
		return desiredPath;
	}

	URI getIndirectId(DelegatedResourceSnapshot snapshot) {
		URI indirectId=null;
		if(isMainResource(snapshot)) {
			indirectId=this.configuration.getIndirectId();
		}
		if(LOGGER.isDebugEnabled()) {
			if(indirectId==null && this.configuration.getIndirectId()!=null) {
				LOGGER.debug("Resource {} is not main resource",snapshot.resourceId());
			} else if(indirectId!=null) {
				LOGGER.debug("Resource {} is the main resource and may be alternative identified by '{}'",snapshot.resourceId(),indirectId);
			} else {
				LOGGER.debug("Resource {} is the main resource but does not have an alternative identifier",snapshot.resourceId());
			}
		}
		return indirectId;
	}

	Date getLastModified() {
		return this.configuration.getLastModified();
	}

	Status status() {
		return this.status;
	}

	<T extends DelegatedResourceSnapshot> T newTransient(ResourceId resourceId, DelegatedResourceSnapshot parent, Class<? extends T> clazz) {
		DelegatedResourceSnapshot newSnapshot = this.snapshotFactory.newTransient(resourceId, parent);
		checkState(clazz.isInstance(newSnapshot),"Incompatible types (%s cannot be assigned from %s)",clazz.getCanonicalName(),newSnapshot.getClass().getCanonicalName());
		checkState(this.resourceCache.put(resourceId, newSnapshot)==null,"Transient resource should be new");
		newSnapshot.setSession(this);
		return clazz.cast(newSnapshot);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <S extends ResourceSnapshot> S find(
			Class<? extends S> snapshotClass,
			Name<?> name,
			Class<? extends ResourceHandler> handlerClass) {
		checkNotNull(snapshotClass,"Resource snapshot class cannot be null");
		checkNotNull(name,RESOURCE_NAME_CANNOT_BE_NULL);
		checkNotNull(handlerClass,"Resource handler class cannot be null");
		checkArgument(!ContainerSnapshot.class.isAssignableFrom(snapshotClass) || ContainerHandler.class.isAssignableFrom(handlerClass),"Incompatible snapshot and handler classes ('%s' instances are not handled by '%s')",snapshotClass.getCanonicalName(),handlerClass.getCanonicalName());
		ResourceTemplate template=this.writeSessionService.templateManagementService().templateOfHandler(handlerClass);
		checkArgument(template!=null,"Handler class '%s' is not associated to any existing template",handlerClass.getCanonicalName());
		checkArgument(Snapshots.areCompatible(snapshotClass,template),"Cannot wrap an object managed by '%s' with an snapshot of type '%s'",handlerClass.getCanonicalName(),snapshotClass.getCanonicalName());
		checkState(this.status.equals(Status.ACTIVE),WRITE_SESSION_NOT_ACTIVE,this.status);
		ResourceId id=ResourceId.createId(name,template);
		DelegatedResourceSnapshot resource = resolveResource(id, template);
		return snapshotClass.cast(resource);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public <S extends ResourceSnapshot> S resolve(
			final Class<? extends S> snapshotClass,
			final Individual<?,?> individual) {
		checkNotNull(snapshotClass,"Resource snapshot class cannot be null");
		checkNotNull(individual,"Individual cannot be null");
		checkState(this.status.equals(Status.ACTIVE),WRITE_SESSION_NOT_ACTIVE,this.status);
		S result=null;
		ResourceId id = getIdentifier(individual);
		if(id!=null) {
			result=find(snapshotClass,id);
		}
		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void modify(ResourceSnapshot resource) {
		checkNotNull(resource,RESOURCE_CANNOT_BE_NULL);
		checkArgument(resource instanceof DelegatedResourceSnapshot,"Unknown resource '%s'",resource.name());
		checkState(this.status.equals(Status.ACTIVE),WRITE_SESSION_NOT_ACTIVE,status);
		DelegatedResourceSnapshot delegatedResource=(DelegatedResourceSnapshot)resource;
		delegatedResource.modify();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void delete(ResourceSnapshot resource) {
		checkNotNull(resource,RESOURCE_CANNOT_BE_NULL);
		checkArgument(resource instanceof DelegatedResourceSnapshot,"Unknown resource '%s'",resource.name());
		checkState(this.status.equals(Status.ACTIVE),WRITE_SESSION_NOT_ACTIVE,status);
		DelegatedResourceSnapshot delegatedResource=(DelegatedResourceSnapshot)resource;
		delegatedResource.delete();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void saveChanges() throws WriteSessionException {
		checkState(this.status.equals(Status.ACTIVE),WRITE_SESSION_NOT_ACTIVE,this.status);
		this.status=Status.COMPLETED;
		for(Entry<ResourceId, DelegatedResourceSnapshot> entry:this.resourceCache.entrySet()) {
			DelegatedResourceSnapshot resource = entry.getValue();
			resource.saveChanges();
		}
		this.writeSessionService.commitSession(this);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void discardChanges() throws WriteSessionException {
		checkState(this.status.equals(Status.ACTIVE),WRITE_SESSION_NOT_ACTIVE,this.status);
		this.status=Status.ABORTED;
		this.writeSessionService.rollbackSession(this);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void close() throws SessionTerminationException {
		this.writeSessionService.terminateSession(this);
	}

}


/**
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   This file is part of the LDP4j Project:
 *     http://www.ldp4j.org/
 *
 *   Center for Open Middleware
 *     http://www.centeropenmiddleware.com/
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Copyright (C) 2014-2016 Center for Open Middleware.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Licensed 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.
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 *   Artifact    : org.ldp4j.framework:ldp4j-application-kernel-jpa:0.2.2
 *   Bundle      : ldp4j-application-kernel-jpa-0.2.2.jar
 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
 */
package org.ldp4j.application.kernel.persistence.encoding;

import java.io.IOException;
import java.io.Serializable;

import org.ldp4j.application.data.Name;

/**
 * Name encoder.
 *
 * @author Miguel Esteban Gutiérrez
 */
final class NameEncoder extends AbstractEncoder {

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected Serializable prepare(Name<?> name) {
		return name;
	}

	/**
	 * {@inheritDoc}
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected <T extends Serializable> Name<T> assemble(Serializable subject) throws IOException {
		return (Name<T>)subject;
	}

}