/** * $Id: EntityActionsManager.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $ * $URL: https://source.sakaiproject.org/svn/entitybroker/trunk/rest/src/java/org/sakaiproject/entitybroker/rest/EntityActionsManager.java $ * EntityActionsManager.java - entity-broker - Jul 26, 2008 9:58:00 AM - azeckoski ************************************************************************** * Copyright (c) 2008, 2009 The Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.entitybroker.rest; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.sakaiproject.entitybroker.EntityReference; import org.sakaiproject.entitybroker.EntityView; import org.sakaiproject.entitybroker.entityprovider.EntityProviderMethodStore; import org.sakaiproject.entitybroker.entityprovider.capabilities.ActionsExecutable; import org.sakaiproject.entitybroker.entityprovider.capabilities.ActionsExecutionControllable; import org.sakaiproject.entitybroker.entityprovider.extension.ActionReturn; import org.sakaiproject.entitybroker.entityprovider.extension.CustomAction; import org.sakaiproject.entitybroker.entityprovider.extension.EntityData; import org.sakaiproject.entitybroker.entityprovider.extension.Formats; import org.sakaiproject.entitybroker.entityprovider.search.Search; import org.sakaiproject.entitybroker.exception.EntityException; import org.sakaiproject.entitybroker.exception.EntityNotFoundException; import org.sakaiproject.entitybroker.exception.FormatUnsupportedException; import org.sakaiproject.entitybroker.util.EntityDataUtils; import org.sakaiproject.entitybroker.util.http.LazyResponseOutputStream; import org.sakaiproject.entitybroker.util.request.RequestStorageImpl; import org.sakaiproject.entitybroker.util.request.RequestUtils; /** * Handles everything related to the custom actions registration and execution * * @author Aaron Zeckoski (azeckoski @ gmail.com) */ public class EntityActionsManager { protected EntityActionsManager() { } /** * Full constructor * @param entityProviderMethodStore the provider method store service */ public EntityActionsManager(EntityProviderMethodStore entityProviderMethodStore) { this.entityProviderMethodStore = entityProviderMethodStore; } private EntityProviderMethodStore entityProviderMethodStore; public void setEntityProviderMethodStore(EntityProviderMethodStore entityProviderMethodStore) { this.entityProviderMethodStore = entityProviderMethodStore; } /** * Execute a custom action request * @param actionProvider * @param entityView * @param action * @param request * @param response * @return an action return (may be null) * @throws IllegalArgumentException if any args are invalid * @throws UnsupportedOperationException if the action is not valid for this prefix * @throws IllegalStateException if a failure occurs */ public ActionReturn handleCustomActionRequest(ActionsExecutable actionProvider, EntityView entityView, String action, HttpServletRequest request, HttpServletResponse response, Map<String, Object> searchParams) { if (actionProvider == null || entityView == null || action == null || request == null || response == null) { throw new IllegalArgumentException("actionProvider and view and action and request and response must not be null"); } // get the action params out of the request first Map<String, Object> actionParams = RequestStorageImpl.getRequestValues(request, true, true, true); EntityReference ref = entityView.getEntityReference(); OutputStream outputStream = new LazyResponseOutputStream(response); ActionReturn actionReturn = handleCustomActionExecution(actionProvider, ref, action, actionParams, outputStream, entityView, searchParams); // now process the return into the request or response as needed if (actionReturn != null) { if (actionReturn.output != null || actionReturn.outputString != null) { if (actionReturn.output == null) { // write the string into the response outputstream try { outputStream.write( actionReturn.outputString.getBytes() ); } catch (IOException e) { throw new RuntimeException("Failed encoding for outputstring: " + actionReturn.outputString); } actionReturn.output = outputStream; } // now set the encoding, mimetype into the response actionReturn.format = entityView.getExtension(); if (actionReturn.encoding == null || actionReturn.mimeType == null) { // use default if not set if (actionReturn.format == null) { actionReturn.format = Formats.TXT; } RequestUtils.setResponseEncoding(actionReturn.format, response); } else { response.setCharacterEncoding(actionReturn.encoding); response.setContentType(actionReturn.mimeType); } } // also sets the response code when handling the action if (actionReturn.responseCode > 0) { response.setStatus(actionReturn.responseCode); } else { response.setStatus(HttpServletResponse.SC_OK); } // other returns require no extra work here } else { // no failure so set the status code response.setStatus(HttpServletResponse.SC_OK); } return actionReturn; } /** * Handles the execution of custom actions based on a request for execution * @throws IllegalArgumentException if any args are invalid * @throws UnsupportedOperationException if the action is not valid for this prefix */ public ActionReturn handleCustomActionExecution(ActionsExecutable actionProvider, EntityReference ref, String action, Map<String, Object> actionParams, OutputStream outputStream, EntityView view, Map<String, Object> searchParams) { if (actionProvider == null || ref == null || action == null || "".equals(action)) { throw new IllegalArgumentException("actionProvider and ref and action must not be null"); } if (outputStream == null) { // create an outputstream to hold the data outputStream = new ByteArrayOutputStream(); } String prefix = ref.getPrefix(); CustomAction customAction = entityProviderMethodStore.getCustomAction(prefix, action); if (customAction == null) { throw new UnsupportedOperationException("Invalid action ("+action+"), this action is not a supported custom action for prefix ("+prefix+")"); } ActionReturn actionReturn = null; Object result = null; if (ActionsExecutionControllable.class.isAssignableFrom(actionProvider.getClass())) { // execute the action result = ((ActionsExecutionControllable)actionProvider).executeActions(new EntityView(ref, null, null), action, actionParams, outputStream); } else { if (customAction.methodName == null) { throw new IllegalStateException("The custom action must have the method name set, null is not allowed: " + customAction); } Method method = customAction.getMethod(); if (method == null) { try { // Note: this is really expensive, need to cache the Method lookup method = actionProvider.getClass().getMethod(customAction.methodName, customAction.methodArgTypes); } catch (SecurityException e1) { throw new RuntimeException("Fatal error trying to get custom action method: " + customAction, e1); } catch (NoSuchMethodException e1) { throw new RuntimeException("Fatal error trying to get custom action method: " + customAction, e1); } customAction.setMethod(method); // cache the method } Object[] args = new Object[customAction.methodArgTypes.length]; for (int i = 0; i < customAction.methodArgTypes.length; i++) { Class<?> argType = customAction.methodArgTypes[i]; if (EntityReference.class.equals(argType)) { args[i] = ref; } else if (EntityView.class.equals(argType)) { if (view == null) { view = new EntityView(ref, customAction.viewKey, null); } args[i] = view; } else if (String.class.equals(argType)) { args[i] = actionProvider.getEntityPrefix(); } else if (Search.class.equals(argType)) { Search search = null; if (searchParams == null || searchParams.isEmpty()) { search = new Search(); } else { search = RequestUtils.makeSearchFromRequestParams(searchParams); } args[i] = search; } else if (OutputStream.class.equals(argType)) { args[i] = outputStream; } else if (Map.class.equals(argType)) { args[i] = actionParams; } else { throw new IllegalStateException("custom action ("+customAction+") contains an invalid methodArgTypes, " + "only valid types allowed: EntityReference, EntityView, Search, String, OutputStream, Map"); } } try { result = method.invoke(actionProvider, args); } catch (IllegalArgumentException e) { throw new RuntimeException("Fatal error trying to execute custom action method: " + customAction, e); } catch (IllegalAccessException e) { throw new RuntimeException("Fatal error trying to execute custom action method: " + customAction, e); } catch (InvocationTargetException e) { if (e.getCause() != null) { if (e.getCause().getClass().isAssignableFrom(IllegalArgumentException.class)) { throw new IllegalArgumentException(e.getCause().getMessage() + " (rethrown)", e.getCause()); } else if (e.getCause().getClass().isAssignableFrom(EntityNotFoundException.class)) { throw new EntityNotFoundException(e.getCause().getMessage() + " (rethrown)", ref+"", e.getCause()); } else if (e.getCause().getClass().isAssignableFrom(FormatUnsupportedException.class)) { String format = ((FormatUnsupportedException)e.getCause()).format; throw new FormatUnsupportedException(e.getCause().getMessage() + " (rethrown)", e.getCause(), ref+"", format); } else if (e.getCause().getClass().isAssignableFrom(UnsupportedOperationException.class)) { throw new UnsupportedOperationException(e.getCause().getMessage() + " (rethrown)", e.getCause()); } else if (e.getCause().getClass().isAssignableFrom(EntityException.class)) { int code = ((EntityException)e.getCause()).responseCode; throw new EntityException(e.getCause().getMessage() + " (rethrown)", ref+"", code); } else if (e.getCause().getClass().isAssignableFrom(IllegalStateException.class)) { throw new IllegalStateException(e.getCause().getMessage() + " (rethrown)", e.getCause()); } else if (e.getCause().getClass().isAssignableFrom(SecurityException.class)) { throw new SecurityException(e.getCause().getMessage() + " (rethrown)", e.getCause()); } } throw new RuntimeException("Fatal error trying to execute custom action method: " + customAction, e); } } if (result != null) { Class<?> resultClass = result.getClass(); // package up the result in the ActionResult if (Boolean.class.isAssignableFrom(resultClass)) { // handle booleans specially boolean bool = ((Boolean) result).booleanValue(); if (bool) { result = null; } else { throw new EntityNotFoundException("Could not find data for ref ("+ref+") from custom action ("+action+"), (returned boolean false)", ref+""); } } else if (ActionReturn.class.isAssignableFrom(resultClass)) { actionReturn = (ActionReturn) result; } else if (OutputStream.class.isAssignableFrom(resultClass)) { actionReturn = new ActionReturn(outputStream); } else if (String.class.isAssignableFrom(resultClass)) { actionReturn = new ActionReturn((String) result); } else if (List.class.isAssignableFrom(resultClass)) { // convert the list to a list of EntityData List<EntityData> data = EntityDataUtils.convertToEntityData((List<?>) result, ref); actionReturn = new ActionReturn(data, (String) null); } else if (EntityData.class.isAssignableFrom(resultClass)) { actionReturn = new ActionReturn( (EntityData) result, (String) null); } else { // assume this is an entity object (not ED) EntityData ed = EntityDataUtils.makeEntityData(ref, result); actionReturn = new ActionReturn( ed, (String) null); } } return actionReturn; } /** * Get the {@link CustomAction} for a prefix and action if it exists * @param prefix an entity prefix * @param action an action key * @return the custom action OR null if none found */ public CustomAction getCustomAction(String prefix, String action) { return entityProviderMethodStore.getCustomAction(prefix, action); } }