예제 #1
0
 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);
 }
예제 #2
0
/**
 * 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;
    }
  }
}
예제 #3
0
 protected Locale getLocale() {
   return CompositeUtil.instance().getLocale(requestHeaders);
 }