package com.temenos.interaction.core.command;
/*
* #%L
* interaction-core
* %%
* Copyright (C) 2012 - 2013 Temenos Holdings N.V.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import com.temenos.interaction.core.resource.EntityResource;
import org.apache.wink.common.internal.MultivaluedMapImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.temenos.interaction.core.entity.EntityMetadata;
import com.temenos.interaction.core.entity.Metadata;
import com.temenos.interaction.core.hypermedia.Link;
import com.temenos.interaction.core.hypermedia.ResourceState;
import com.temenos.interaction.core.resource.RESTResource;
import com.temenos.interaction.core.rim.AcceptLanguageHeaderParser;
import com.temenos.interaction.core.rim.HTTPHypermediaRIM;
/**
* This object holds the execution context for processing of the
* interaction commands. {@link InteractionCommand}
* @author aphethean
*/
public class InteractionContext {
private final static Logger logger = LoggerFactory.getLogger(InteractionContext.class);
/**
* The default path element key used if no other is specified when defining the resource.
*/
public final static String DEFAULT_ID_PATH_ELEMENT = "id";
/* Execution context */
private final UriInfo uriInfo;
private final HttpHeaders headers;
private final MultivaluedMap<String, String> inQueryParameters;
private final MultivaluedMap<String, String> pathParameters;
private final ResourceState currentState;
private final Metadata metadata;
private MultivaluedMap<String, String> outQueryParameters = new MultivaluedMapImpl<String, String>();
private ResourceState targetState;
private Link linkUsed;
private InteractionException exception;
/* Command context */
private RESTResource resource;
private Map<String, Object> attributes = new HashMap<String, Object>();
private String preconditionIfMatch = null;
private List<String> preferredLanguages = new ArrayList<String>();
private final Map<String, String> responseHeaders = new HashMap<String, String>();
/**
* Construct the context for execution of an interaction.
* @see HTTPHypermediaRIM for pre and post conditions of this InteractionContext
* following the execution of a command.
* @invariant pathParameters not null
* @invariant queryParameters not null
* @invariant currentState not null
* @param uri used to relate responses to initial uri for caching purposes
* @param pathParameters
* @param queryParameters
*/
public InteractionContext(final UriInfo uri, final HttpHeaders headers, final MultivaluedMap<String, String> pathParameters, final MultivaluedMap<String, String> queryParameters, final ResourceState currentState, final Metadata metadata) {
this.uriInfo = uri;
this.headers = headers;
this.pathParameters = pathParameters;
this.inQueryParameters = queryParameters;
this.currentState = currentState;
this.metadata = metadata;
assert pathParameters != null;
assert queryParameters != null;
assert metadata != null;
}
/**
* Shallow copy constructor with extra parameters to override final attributes.
* Note uriInfo not retained since responses produced will not be valid for original request.
* @param ctx interaction context
* @param headers HttpHeaders
* @param pathParameters new path parameters or null to not override
* @param queryParameters new query parameters or null to not override
* @param currentState new current state or null to not override
*/
public InteractionContext(InteractionContext ctx, final HttpHeaders headers, final MultivaluedMap<String, String> pathParameters, final MultivaluedMap<String, String> queryParameters, final ResourceState currentState) {
this.uriInfo = null;
this.headers = headers != null ? headers : ctx.getHeaders();
this.pathParameters = pathParameters != null ? pathParameters : ctx.pathParameters;
this.inQueryParameters = queryParameters != null ? queryParameters : ctx.inQueryParameters;
this.outQueryParameters.putAll(ctx.outQueryParameters);
this.currentState = currentState != null ? currentState : ctx.currentState;
this.metadata = ctx.metadata;
this.resource = ctx.resource;
this.targetState = ctx.targetState;
this.linkUsed = ctx.linkUsed;
this.exception = ctx.exception;
this.attributes = ctx.attributes;
}
/**
* Uri for the request, used for caching
* @return
*/
public Object getRequestUri() {
return this.uriInfo==null ? null : this.uriInfo.getRequestUri();
}
/**
* <p>The query part of the uri (after the '?')</p>
* URI query parameters as a result of jax-rs {@link UriInfo#getQueryParameters(true)}
*/
public MultivaluedMap<String, String> getQueryParameters() {
return inQueryParameters;
}
/**
* <p>the path part of the uri (up to the '?')</p>
* URI path parameters as a result of jax-rs {@link UriInfo#getPathParameters(true)}
*/
public MultivaluedMap<String, String> getPathParameters() {
return pathParameters;
}
public MultivaluedMap<String, String> getEncodedParameters(MultivaluedMap<String, String> parameters) {
MultivaluedMap<String, String> encodedParameters = new com.temenos.interaction.core.MultivaluedMapImpl<String>();
for (String key : parameters.keySet()) {
String value = parameters.getFirst(key);
if (value != null) {
try {
String encodedValue = URLEncoder.encode(value.toString(), "UTF-8");
encodedParameters.add(key, encodedValue);
} catch (UnsupportedEncodingException e) {
logger.error("ERROR unable to encode " + key, e);
}
}
}
return encodedParameters;
}
/**
* The Uri of the current request
* @return
*/
public UriInfo getUriInfo() {
return uriInfo;
}
/**
* The HTTP headers of the current request.
* @return
*/
public HttpHeaders getHeaders() {
return headers;
}
/**
* The HTTP headers to be set in the response.
* @return
*/
public Map<String,String> getResponseHeaders() {
return responseHeaders;
}
/**
* The object form of the resource this interaction is dealing with.
* @return
*/
public RESTResource getResource() {
return resource;
}
/**
* The enity object of the resource this interaction is dealing with.
* @return
*/
public Object getResourceEntity() {
EntityResource<?> entityResource = (resource instanceof EntityResource<?>)
? (EntityResource<?>) resource
: new EntityResource<>(resource);
return entityResource.getEntity();
}
/**
* In terms of the hypermedia interactions this is the current application state.
* @return
*/
public ResourceState getCurrentState() {
return currentState;
}
/**
* @see InteractionContext#getResource()
* @param resource
*/
public void setResource(RESTResource resource) {
this.resource = resource;
}
public ResourceState getTargetState() {
return targetState;
}
public void setTargetState(ResourceState targetState) {
this.targetState = targetState;
}
public Link getLinkUsed() {
return linkUsed;
}
public void setLinkUsed(Link linkUsed) {
this.linkUsed = linkUsed;
}
public String getId() {
String id = null;
if (pathParameters != null) {
id = pathParameters.getFirst(DEFAULT_ID_PATH_ELEMENT);
if (id == null) {
if (getCurrentState().getPathIdParameter() != null) {
id = pathParameters.getFirst(getCurrentState().getPathIdParameter());
} else {
EntityMetadata entityMetadata = metadata.getEntityMetadata(getCurrentState().getEntityName());
if (entityMetadata != null) {
List<String> idFields = entityMetadata.getIdFields();
// TODO add support for composite ids
assert idFields.size() == 1 : "ERROR we currently only support simple ids";
if ( idFields.size() == 1 )
id = pathParameters.getFirst(idFields.get(0));
}
}
}
for (String pathParam : pathParameters.keySet()) {
logger.debug("PathParam " + pathParam + ":" + pathParameters.get(pathParam));
}
}
return id;
}
/**
* Store an attribute in this interaction context.
* @param name
* @param value
*/
public void setAttribute(String name, Object value) {
attributes.put(name, value);
}
/**
* Retrieve an attribute from this interaction context.
* @param name
* @return
*/
public Object getAttribute(String name) {
return attributes.get(name);
}
/**
* Retrieve a copy of all attributes from this interaction context.
* @return
*/
public Map<String, Object> getAttributes() {
Map<String, Object> attributesCopy = new HashMap<>();
attributesCopy.putAll(attributes);
return attributesCopy;
}
/**
* Returns the metadata from this interaction context
* @return metadata
*/
public Metadata getMetadata() {
return metadata;
}
/**
* Obtain the last exception
* @return exception
*/
public InteractionException getException() {
return exception;
}
/**
* Set an exception
* @param exception exception
*/
public void setException(InteractionException exception) {
this.exception = exception;
}
/**
* Get If-Match precondition
* @return If-Match precondition
*/
public String getPreconditionIfMatch() {
return preconditionIfMatch;
}
/**
* Set the If-Match precondition
* @param preconditionIfMatch If-Match precondition
*/
public void setPreconditionIfMatch(String preconditionIfMatch) {
this.preconditionIfMatch = preconditionIfMatch;
}
/**
* Sets the http header AcceptLanguage value to the context.
* @param acceptLanguageValue
*/
public void setAcceptLanguage(String acceptLanguageValue) {
preferredLanguages = new AcceptLanguageHeaderParser(acceptLanguageValue).getLanguageCodes();
}
/**
* Returns the list of language codes in the order of their preference.
* @return language codes
*/
public List<String> getLanguagePreference(){
return preferredLanguages;
}
/**
* Returns any query parameters, added by the resource's command, that should be dynamically
* added to the response links
*
* @return the outQueryParameters
*/
public MultivaluedMap<String, String> getOutQueryParameters() {
return outQueryParameters;
}
}