/*
* Copyright (c) 2010-2011 Lockheed Martin Corporation
*
* 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.
*/
package org.eurekastreams.server.service.restlets;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Date;
import java.util.List;
import javax.resource.spi.IllegalStateException;
import net.sf.json.JSONObject;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.eurekastreams.commons.actions.context.Principal;
import org.eurekastreams.commons.actions.context.service.ServiceActionContext;
import org.eurekastreams.commons.actions.service.ServiceAction;
import org.eurekastreams.commons.actions.service.TaskHandlerServiceAction;
import org.eurekastreams.commons.exceptions.ExecutionException;
import org.eurekastreams.commons.server.service.ActionController;
import org.eurekastreams.server.persistence.mappers.cache.Transformer;
import org.eurekastreams.server.service.actions.strategies.ApplicationContextHolder;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Status;
import org.restlet.resource.Representation;
import org.restlet.resource.ResourceException;
import org.restlet.resource.StringRepresentation;
import org.restlet.resource.Variant;
import org.springframework.util.Assert;
/**
* Endpoint for the API (invoking actions).
*/
public class ActionResource extends SmpResource
{
/** Logger. */
private final Log log = LogFactory.getLog(ActionResource.class);
/** Service Action Controller. */
private final ActionController serviceActionController;
/** Principal populators. */
private final List<Transformer<Request, Principal>> principalExtractors;
/** JSON Factory for building JSON Generators. */
private final JsonFactory jsonFactory;
/** Holds the app context. */
private final ApplicationContextHolder applicationContextHolder;
/** Only allow read-only actions. */
private final boolean readOnly;
/**
* User principal.
*/
private Principal principal;
/**
* The action to execute.
*/
private String actionKey;
/**
* The request type.
*/
private String requestType;
/**
* Parameters to the restlet in JSON. Contains the request and any other needed data.
*/
private String paramsJSON;
/**
* Default constructor.
*
* @param inServiceActionController
* the action controller.
* @param inPrincipalExtractors
* the principal extractors.
* @param inJsonFactory
* the json factory.
* @param inApplicationContextHolder
* app context holder.
* @param inReadOnly
* Only allow read-only actions.
*/
public ActionResource(final ActionController inServiceActionController,
final List<Transformer<Request, Principal>> inPrincipalExtractors, final JsonFactory inJsonFactory,
final ApplicationContextHolder inApplicationContextHolder, final boolean inReadOnly)
{
serviceActionController = inServiceActionController;
principalExtractors = inPrincipalExtractors;
jsonFactory = inJsonFactory;
applicationContextHolder = inApplicationContextHolder;
readOnly = inReadOnly;
}
/**
* init the params.
*
* @param request
* the request object.
*/
@Override
protected void initParams(final Request request)
{
principal = getPrincipal(request);
Assert.notNull(principal, "Principal object cannot be null.");
actionKey = (String) request.getAttributes().get("action");
requestType = (String) request.getAttributes().get("requestType");
try
{
paramsJSON = URLDecoder.decode((String) request.getAttributes().get("paramsJSON"), "UTF-8");
}
catch (UnsupportedEncodingException ex)
{
throw new ExecutionException(ex);
}
}
/**
* GET the activites.
*
* @param variant
* the variant.
* @return the JSON.
* @throws ResourceException
* the exception.
*/
@SuppressWarnings("unchecked")
@Override
public Representation represent(final Variant variant) throws ResourceException
{
String jsString = "";
try
{
// get the action
Object springBean = applicationContextHolder.getContext().getBean(actionKey);
// get parameter
Serializable actionParameter = getRequestObject();
// create ServiceActionContext.
ServiceActionContext actionContext = new ServiceActionContext(actionParameter, principal);
actionContext.setActionId(actionKey);
log.debug("executing action: " + actionKey + " for user: " + principal.getAccountId());
Serializable result = "empty result";
if (springBean instanceof ServiceAction)
{
ServiceAction action = (ServiceAction) springBean;
if (readOnly && !action.isReadOnly())
{
throw new IllegalStateException("Action requested is not read-only.");
}
result = serviceActionController.execute(actionContext, action);
}
else if (springBean instanceof TaskHandlerServiceAction)
{
TaskHandlerServiceAction action = (TaskHandlerServiceAction) springBean;
if (readOnly && !action.isReadOnly())
{
throw new IllegalStateException("Action requested is not read-only.");
}
result = serviceActionController.execute(actionContext, action);
}
StringWriter writer = new StringWriter();
JsonGenerator jsonGenerator = jsonFactory.createJsonGenerator(writer);
jsonGenerator.writeObject(result);
writer.close();
jsString = writer.toString();
}
catch (Exception ex)
{
log.error("Error excecuting action " + actionKey + " from restlet.", ex);
throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Error executing action.");
}
Representation rep = new StringRepresentation(jsString, MediaType.TEXT_PLAIN);
rep.setExpirationDate(new Date(0L));
return rep;
}
/**
* Returns Principal for given account id.
*
* @param inRequest
* Request to get principal from.
* @return Principal for given account id.
*/
private Principal getPrincipal(final Request inRequest)
{
for (Transformer<Request, Principal> extractor : principalExtractors)
{
Principal result = extractor.transform(inRequest);
if (result != null)
{
return result;
}
}
return null;
}
/**
* Go from JSON to Request object.
*
* @return the request object.
* @throws Exception
* possible exceptions.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private Serializable getRequestObject() throws Exception
{
if (requestType.toLowerCase().equals("null"))
{
return null;
}
final JSONObject paramsAsObject = JSONObject.fromObject(paramsJSON);
if (requestType.toLowerCase().equals("long"))
{
return paramsAsObject.getLong("request");
}
else if (requestType.toLowerCase().equals("int"))
{
return paramsAsObject.getInt("request");
}
else if (requestType.toLowerCase().equals("string"))
{
return paramsAsObject.getString("request");
}
else if (requestType.toLowerCase().equals("boolean"))
{
return paramsAsObject.getBoolean("request");
}
else
{
Class returnType = Class.forName(requestType);
String requestAsString = paramsAsObject.getString("request");
ObjectMapper objMapper = new ObjectMapper();
Object requestAsObject = objMapper.readValue(requestAsString, returnType);
/*
* This is nicer, but some of our types don't deserialize properly with it. JSONObject requestAsJson =
* paramsAsObject.getJSONObject("request"); Object requestAsObject = JSONObject.toBean(requestAsJson,
* returnType);
*/
return (Serializable) returnType.cast(requestAsObject);
}
}
}