protected <T extends RestModel> T getTypedModel(Class<T> modelIface, JSONObject jsonModel) throws Exception { if (jsonModel == null) { return null; } return CompositeUtil.instance().unmarshallClass(getLocale(), modelIface, jsonModel); }
/** * This is the base class for all composite resources. It provides all of the basic configuration * and utilities needed by composites. For top-level resources, the <code>@Path</code> and <code> * @Service</code> annotations are still required, though, in order for the resource to be located * and configured properly. * * @author jdlee */ @Produces(Constants.MEDIA_TYPE_JSON) public abstract class CompositeResource extends AbstractResource implements RestResource { // All methods that expect a request body should include the annotation: // @Consumes(CONSUMES_TYPE) public static final String CONSUMES_TYPE = Constants.MEDIA_TYPE_JSON; protected static final String DETACHED = "__detached"; protected static final String DETACHED_DEFAULT = "false"; protected static final String INCLUDE = "__includeFields"; protected static final String EXCLUDE = "__excludeFields"; // TODO: These should be configurable protected static final int THREAD_POOL_CORE = 5; protected static final int THREAD_POOL_MAX = 10; protected CompositeUtil compositeUtil = CompositeUtil.instance(); public void setSubjectRef(Ref<Subject> subjectRef) { this.subjectRef = subjectRef; } public CompositeUtil getCompositeUtil() { return compositeUtil; } /** * This method creates a sub-resource of the specified type. Since the JAX-RS does not allow for * injection into sub-resources (as it doesn't know or control the lifecycle of the object), this * method performs a manual "injection" of the various system objects the resource might need. If * the requested Class can not be instantiated (e.g., it does not have a no-arg public * constructor), the system will throw a <code>WebApplicationException</code> with an HTTP status * code of 500 (internal server error). * * @param clazz The Class of the desired sub-resource * @return */ public <T> T getSubResource(Class<T> clazz) { try { T resource = clazz.newInstance(); CompositeResource cr = (CompositeResource) resource; cr.locatorBridge = locatorBridge; cr.subjectRef = subjectRef; cr.uriInfo = uriInfo; cr.securityContext = securityContext; cr.requestHeaders = requestHeaders; cr.serviceLocator = serviceLocator; return resource; } catch (Exception ex) { throw new WebApplicationException(ex, Status.INTERNAL_SERVER_ERROR); } } // Convenience methods for creating models protected <T> T newModel(Class<T> modelIface) { return getCompositeUtil().getModel(modelIface); } protected <T extends RestModel> T newTemplate(Class<T> modelIface) { // We don't want any model trimming to happen on templates since the caller is supposed to // get the template, modify it, then POST it back and since POST should be getting full // entities. T template = newModel(modelIface); template.allFieldsSet(); return template; } protected <T extends RestModel> T getTypedModel(Class<T> modelIface, JSONObject jsonModel) throws Exception { if (jsonModel == null) { return null; } return CompositeUtil.instance().unmarshallClass(getLocale(), modelIface, jsonModel); } protected JSONObject getJsonModel(RestModel typedModel) throws Exception { return (JSONObject) JsonUtil.getJsonObject(typedModel, false); // include confidential properties } // Convenience methods for constructing URIs /** * Every resource that returns a collection will need to return the URI for each item in the * colleciton. This method handles the creation of that URI, ensuring a correct and consistent URI * pattern. * * @param name * @return */ protected URI getChildItemUri(String name) { return getSubUri("id/" + name); } protected URI getUri(String path) { return this.uriInfo.getBaseUriBuilder().path(path).build(); } protected URI getSubUri(String name) { return this.uriInfo.getAbsolutePathBuilder().path(name).build(); } // Convenience methods for adding links protected void addResourceLink(ResponseBody rb, String rel) throws Exception { rb.addResourceLink(rel, getSubUri(rel)); } protected void addActionResourceLink(ResponseBody rb, String action) throws Exception { rb.addActionResourceLink(action, getSubUri(action)); } protected boolean includeResourceLinks() { final String hdr = requestHeaders.getRequestHeaders().getFirst("X-Skip-Metadata"); // X-Skip-Resource-Links boolean skip = "true".equalsIgnoreCase(hdr); return !skip; } // Convenience methods for computing a resource's parent uri protected URI getParentUri() throws Exception { return getParentUri(false); } protected URI getCollectionChildParentUri() throws Exception { return getParentUri(true); } private URI getParentUri(boolean isCollectionChild) throws Exception { List<PathSegment> pathSegments = this.uriInfo.getPathSegments(); int count = pathSegments.size() - 1; // go up a level to get to the parent if (isCollectionChild) { count--; // collection children have the url pattern .../foos/id/myfoo. need to go up another // level } // [0] = 'javaservice', which is a resource if (count <= 0) { return null; // top level resource } UriBuilder bldr = this.uriInfo.getBaseUriBuilder(); for (int i = 0; i < count; i++) { bldr.path(pathSegments.get(i).getPath()); } return bldr.build(); } /** * Execute a delete <code>AdminCommand</code> with no parameters. * * @param command * @return */ protected ActionReporter executeDeleteCommand(String command) { return getCompositeUtil().executeDeleteCommand(getSubject(), command); } /** * Execute a delete <code>AdminCommand</code> with the specified parameters. * * @param command * @param parameters * @return */ protected ActionReporter executeDeleteCommand(String command, ParameterMap parameters) { return getCompositeUtil().executeDeleteCommand(getSubject(), command, parameters); } /** * Execute a delete <code>AdminCommand</code> with the specified parameters. * * @param command * @param parameters * @return */ protected ActionReporter executeDeleteCommandManaged(String command, ParameterMap parameters) { return getCompositeUtil().executeDeleteCommandManaged(getSubject(), command, parameters); } /** * Execute a writing <code>AdminCommand</code> with no parameters. * * @param command * @return */ protected ActionReporter executeWriteCommand(String command) { return getCompositeUtil().executeWriteCommand(getSubject(), command); } /** * Execute a writing <code>AdminCommand</code> with the specified parameters. * * @param command * @param parameters * @return */ protected ActionReporter executeWriteCommand(String command, ParameterMap parameters) { return getCompositeUtil().executeWriteCommand(getSubject(), command, parameters); } /** * Execute a writing <code>AdminCommand</code> with the specified parameters. * * @param command * @param parameters * @return */ protected ActionReporter executeWriteCommandManaged(String command, ParameterMap parameters) { return getCompositeUtil().executeWriteCommandManaged(getSubject(), command, parameters); } /** * Execute a read-only <code>AdminCommand</code> with the specified parameters. * * @param command * @param parameters * @return */ protected ActionReporter executeReadCommand(String command) { return getCompositeUtil().executeReadCommand(getSubject(), command); } /** * Execute a read-only <code>AdminCommand</code> with no parameters. * * @param command * @param parameters * @return */ protected ActionReporter executeReadCommand(String command, ParameterMap parameters) { return getCompositeUtil().executeReadCommand(getSubject(), command, parameters); } /** * Execute an <code>AdminCommand</code> with the specified parameters. * * @param command * @param parameters * @param status * @param includeFailureMessage * @param throwOnWarning (vs.ignore warning) * @return */ protected ActionReporter executeCommand( String command, ParameterMap parameters, Status status, boolean includeFailureMessage, boolean throwOnWarning) { return getCompositeUtil() .executeCommand( getSubject(), command, parameters, status, includeFailureMessage, throwOnWarning, false); } /** * Execute an <code>AdminCommand</code> via SSE, but provide an <code>ActionReportProcessor</code> * that allows the calling resource, via an <code>EntityBuilder</code> instance, to return a * <code>ResponseBody</code> that extra information such as the newly create entity, as well as * any messages returned by the subsystem. */ protected EventOutput executeSseCommand( final Subject subject, final String command, final ParameterMap parameters, final ResponseBodyBuilder builder) { return getCompositeUtil() .executeSseCommand( subject, command, parameters, new SseCommandHelper.ActionReportProcessor() { @Override public ActionReport process(ActionReport report, EventOutput ec) { if (report != null) { ResponseBody rb = builder.build(report); Properties props = new Properties(); props.put("response", rb); report.setExtraProperties(props); } return report; } }); } /** * Execute an <code>AdminCommand</code> with the specified parameters and return EventOutput * suitable for SSE. */ protected EventOutput executeSseCommand( final Subject subject, final String command, final ParameterMap parameters, final SseCommandHelper.ActionReportProcessor processor) { return getCompositeUtil().executeSseCommand(subject, command, parameters, processor); } /** * Execute an <code>AdminCommand</code> with the specified parameters and return EventOutput * suitable for SSE. */ protected EventOutput executeSseCommand( final Subject subject, final String command, final ParameterMap parameters) { return getCompositeUtil().executeSseCommand(subject, command, parameters); } /** * TBD - Jason Lee wants to move this into the defaults generators. * * <p>Finds an unused name given the list of currently used names and a name prefix. * * @param namePrefix * @param usedNames * @return a String containing an unused dname, or an empty string if all candidate names are * currently in use. */ protected String generateDefaultName(String namePrefix, Collection<String> usedNames) { for (int i = 1; i <= 100; i++) { String name = namePrefix + "-" + i; if (!usedNames.contains(name)) { return name; } } // All the candidate names are in use. Return an empty name. return ""; } // Convenience methods for 'create' method responses protected Response created(String name, String message) throws Exception { return created(responseBody(), name, message); } protected Response created(ResponseBody rb, String name, String message) throws Exception { rb.addSuccess(message); return created(rb, name); } protected Response created(ResponseBody rb, String name) throws Exception { return created(rb, getChildItemUri(name)); } protected Response created(ResponseBody rb, URI uri) throws Exception { return Response.created(uri).entity(rb).build(); } // Convenience methods for 'update' method responses protected Response updated(String message) { return updated(responseBody(), message); } protected Response updated(ResponseBody rb, String message) { rb.addSuccess(message); return updated(rb); } protected Response updated(ResponseBody rb) { return ok(rb); } // Convenience methods for 'delete' method responses protected Response deleted(String message) { return deleted(responseBody(), message); } protected Response deleted(ResponseBody rb, String message) { rb.addSuccess(message); return deleted(rb); } protected Response deleted(ResponseBody rb) { return ok(rb); } // Convenience methods for 'action' method responses protected Response acted(String message) { return acted(responseBody(), message); } protected Response acted(ResponseBody rb, String message) { rb.addSuccess(message); return acted(rb); } protected Response acted(ResponseBody rb) { return ok(rb); } // Convenience methods for detached method responses protected Response accepted(String message, URI jobUri, URI newItemUri) { return accepted(responseBody(), message, jobUri, newItemUri); } protected Response accepted(ResponseBody rb, String message, URI jobUri, URI newItemUri) { rb.addSuccess(message); return accepted(rb, jobUri, newItemUri); } protected Response accepted(ResponseBody rb, URI jobUri, URI newItemUri) { ResponseBuilder bldr = Response.status(Status.ACCEPTED).entity(rb); if (jobUri != null) { bldr.header("Location", jobUri); } if (newItemUri != null) { bldr.header("X-Location", newItemUri); } return bldr.build(); } protected Response accepted(String command, ParameterMap parameters, URI childUri) { return accepted(responseBody(), launchDetachedCommand(command, parameters), childUri); } protected URI launchDetachedCommand(String command, ParameterMap parameters) { CommandRunner cr = Globals.getDefaultHabitat().getService(CommandRunner.class); final RestActionReporter ar = new RestActionReporter(); final CommandRunner.CommandInvocation commandInvocation = cr.getCommandInvocation(command, ar, getSubject()).parameters(parameters); final String jobId = DetachedCommandHelper.invokeAsync(commandInvocation); return getUri("jobs/id/" + jobId); } protected Response ok(ResponseBody rb) { return Response.ok(rb).build(); } // Convenience methods for throwing common webapp exceptions protected Response badRequest(ResponseBody rb, String message) { rb.addFailure(message); return badRequest(rb); } protected Response badRequest(ResponseBody rb) { return Response.status(Status.BAD_REQUEST).entity(rb).build(); } protected WebApplicationException badRequest(Throwable cause) { return new WebApplicationException(cause, Status.BAD_REQUEST); } protected WebApplicationException badRequest(String message) { return new WebApplicationException(Response.status(Status.BAD_REQUEST).entity(message).build()); } protected WebApplicationException notFound(String message) { return new WebApplicationException(Response.status(Status.NOT_FOUND).entity(message).build()); } /* protected void internalServerError(Exception e) { ExceptionUtils.log(e); } */ // Convenience methods for creating response bodies protected <T extends RestModel> RestCollectionResponseBody<T> restCollectionResponseBody( Class<T> modelIface, String collectionName, URI parentUri) { RestCollectionResponseBody<T> rb = restCollectionResponseBody(modelIface, collectionName); rb.addParentResourceLink(parentUri); return rb; } protected <T extends RestModel> RestCollectionResponseBody<T> restCollectionResponseBody( Class<T> modelIface, String collectionName) { return new RestCollectionResponseBody<T>(includeResourceLinks(), this.uriInfo, collectionName); } protected <T extends RestModel> RestModelResponseBody<T> restModelResponseBody( Class<T> modelIface, URI parentUri, T entity) { RestModelResponseBody<T> rb = restModelResponseBody(modelIface, parentUri); rb.setEntity(entity); return rb; } protected <T extends RestModel> RestModelResponseBody<T> restModelResponseBody( Class<T> modelIface, URI parentUri) { RestModelResponseBody<T> rb = restModelResponseBody(modelIface); rb.addParentResourceLink(parentUri); return rb; } protected <T extends RestModel> RestModelResponseBody<T> restModelResponseBody( Class<T> modelIface) { return new RestModelResponseBody<T>(includeResourceLinks()); } protected ResponseBody responseBody() { return new ResponseBody(includeResourceLinks()); } // Convenience methods for creating responses from response bodies protected Response getResponse(ResponseBody responseBody) { return getResponse(Status.OK, responseBody); } protected Response getResponse(Status status, ResponseBody responseBody) { return Response.status(status).entity(responseBody).build(); } // Convenience methods to help filter returned data protected JsonFilter getFilter(String include, String exclude) throws Exception { return new JsonFilter(getLocale(), include, exclude); } protected JsonFilter getFilter(String include, String exclude, String identityAttr) throws Exception { return new JsonFilter(getLocale(), include, exclude, identityAttr); } protected <T extends RestModel> T filterModel( Class<T> modelIface, T unfilteredModel, String include, String exclude) throws Exception { return filterModel(modelIface, unfilteredModel, getFilter(include, exclude)); } protected <T extends RestModel> T filterModel( Class<T> modelIface, T unfilteredModel, String include, String exclude, String identityAttr) throws Exception { return filterModel(modelIface, unfilteredModel, getFilter(include, exclude, identityAttr)); } protected <T extends RestModel> T filterModel( Class<T> modelIface, T unfilteredModel, JsonFilter filter) throws Exception { JSONObject unfilteredJson = (JSONObject) JsonUtil.getJsonObject(unfilteredModel, false); // don't hide confidential properties JSONObject filteredJson = filter.trim(unfilteredJson); T filteredModel = getTypedModel(modelIface, filteredJson); filteredModel .trimmed(); // TBD - remove once the conversion to the new REST style guide is completed return filteredModel; } protected Locale getLocale() { return CompositeUtil.instance().getLocale(requestHeaders); } /** * Convenience method for getting a path parameter. Equivalent to * uriInfo.getPathParameters().getFirst(name) * * @param name * @return */ protected String getPathParam(String name) { return this.uriInfo.getPathParameters().getFirst(name); } protected ParameterMap parameterMap() { return Util.parameterMap(); } protected synchronized ExecutorService getExecutorService() { return ExecutorServiceHolder.INSTANCE; } private static class ExecutorServiceHolder { private static ExecutorService INSTANCE = new ThreadPoolExecutor( THREAD_POOL_CORE, // core thread pool size THREAD_POOL_MAX, // maximum thread pool size 1, // time to wait before resizing pool TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(THREAD_POOL_MAX, true), new ThreadPoolExecutor.CallerRunsPolicy()); } protected Response act(final CommandInvoker invoker, boolean detached) { if (detached) { return accepted(invoker.getCommand(), invoker.getParams(), null); } else { invoker.setResult( executeWriteCommand(invoker.getCommand(), invoker.getParams()).getExtraProperties()); return acted(invoker.getSuccessMessage()); } } protected Response actSse(final CommandInvoker invoker) { final boolean includeResourceLinks = includeResourceLinks(); EventOutput eo = executeSseCommand( getSubject(), invoker.getCommand(), invoker.getParams(), new ResponseBodyBuilderImpl() { @Override protected ResponseBody success(ActionReport report) { invoker.setResult(report.getExtraProperties()); SseResponseBody responseBody = new SseResponseBody(); responseBody.addSuccess(invoker.getSuccessMessage()); return responseBody; } @Override protected boolean includeResourceLinks() { return includeResourceLinks; } }); return Response.status(Status.ACCEPTED).entity(eo).build(); } protected Response create(final CreateCommandInvoker invoker, boolean detached) throws Exception { if (detached) { final String newItemName = invoker.getNewItemName(); final URI newItemUri = StringUtil.notEmpty(newItemName) ? getChildItemUri(newItemName) : null; return accepted(invoker.getCommand(), invoker.getParams(), newItemUri); } else { invoker.setResult( executeWriteCommand(invoker.getCommand(), invoker.getParams()).getExtraProperties()); return created(invoker.getNewItemName(), invoker.getSuccessMessage()); } } protected Response createSse(final CreateCommandInvoker invoker) throws Exception { final String collectionUri = uriInfo.getAbsolutePathBuilder().build().toString(); final boolean includeResourceLinks = includeResourceLinks(); EventOutput eo = executeSseCommand( getSubject(), invoker.getCommand(), invoker.getParams(), new ResponseBodyBuilderImpl() { @Override protected ResponseBody success(ActionReport report) { invoker.setResult(report.getExtraProperties()); SseResponseBody responseBody = new SseResponseBody(); responseBody .addHeader("Location", collectionUri + "/id/" + invoker.getNewItemName()) .addSuccess(invoker.getSuccessMessage()); return responseBody; } @Override protected boolean includeResourceLinks() { return includeResourceLinks; } }); return Response.status(Status.ACCEPTED).entity(eo).build(); } public class CommandInvoker { public CommandInvoker() {} public CommandInvoker(String command, ParameterMap params, String successMessage) { setCommand(command); setParams(params); setSuccessMessage(successMessage); } private String command; public void setCommand(String val) { this.command = val; } public String getCommand() { return this.command; } private ParameterMap params; public void setParams(ParameterMap val) { this.params = val; } public ParameterMap getParams() { return this.params; } private String successMsg; public void setSuccessMessage(String val) { this.successMsg = val; } public String getSuccessMessage() { return this.successMsg; } public void setResult(Properties extraProperties) {} } public class CreateCommandInvoker extends CommandInvoker { public CreateCommandInvoker() { super(); } public CreateCommandInvoker( String command, ParameterMap params, String successMessage, String newItemName) { super(command, params, successMessage); setNewItemName(newItemName); } private String newItemName; public void setNewItemName(String val) { this.newItemName = val; } public String getNewItemName() { return this.newItemName; } } }
protected Locale getLocale() { return CompositeUtil.instance().getLocale(requestHeaders); }