/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.isis.viewer.restfulobjects.server.resources;
import java.util.List;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.isis.applib.annotation.ActionSemantics;
import org.apache.isis.applib.annotation.Where;
import org.apache.isis.applib.services.xactn.TransactionService;
import org.apache.isis.core.commons.authentication.AuthenticationSession;
import org.apache.isis.core.commons.config.IsisConfiguration;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
import org.apache.isis.viewer.restfulobjects.rendering.RendererContext6;
import org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ActionResultReprRenderer;
import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.DomainObjectLinkTo;
import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.MemberReprMode;
import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAdapterLinkTo;
import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndAction;
import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation;
import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndCollection2;
import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndProperty2;
import org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService;
import org.apache.isis.viewer.restfulobjects.server.ResourceContext;
public class DomainResourceHelper {
static class RepresentationServiceContextAdapter implements RepresentationService.Context6 {
private final RendererContext6 rendererContext;
private final ObjectAdapterLinkTo adapterLinkTo;
private RepresentationService.Intent intent;
RepresentationServiceContextAdapter(
final RendererContext6 rendererContext,
final ObjectAdapterLinkTo adapterLinkTo) {
this.rendererContext = rendererContext;
this.adapterLinkTo = adapterLinkTo;
this.intent = rendererContext.getIntent();
}
@Override
public ObjectAdapterLinkTo getAdapterLinkTo() {
return adapterLinkTo;
}
@Override
public String urlFor(String url) {
return rendererContext.urlFor(url);
}
@Override
public AuthenticationSession getAuthenticationSession() {
return rendererContext.getAuthenticationSession();
}
@Override
public IsisConfiguration getConfiguration() {
return rendererContext.getConfiguration();
}
@Override
public PersistenceSession getPersistenceSession() {
return rendererContext.getPersistenceSession();
}
@Override
public AdapterManager getAdapterManager() {
return rendererContext.getPersistenceSession();
}
@Override
public Where getWhere() {
return rendererContext.getWhere();
}
@Override
public DeploymentCategory getDeploymentCategory() {
return rendererContext.getDeploymentCategory();
}
@Override
public List<List<String>> getFollowLinks() {
return rendererContext.getFollowLinks();
}
@Override
public List<MediaType> getAcceptableMediaTypes() {
return rendererContext.getAcceptableMediaTypes();
}
@Override
public boolean canEagerlyRender(ObjectAdapter objectAdapter) {
return rendererContext.canEagerlyRender(objectAdapter);
}
@Override
public boolean honorUiHints() {
return rendererContext.honorUiHints();
}
@Override
public boolean objectPropertyValuesOnly() {
return rendererContext.objectPropertyValuesOnly();
}
@Override
public boolean suppressDescribedByLinks() {
return rendererContext.suppressDescribedByLinks();
}
@Override
public boolean suppressUpdateLink() {
return rendererContext.suppressUpdateLink();
}
@Override
public boolean suppressMemberId() {
return rendererContext.suppressMemberId();
}
@Override
public boolean suppressMemberLinks() {
return rendererContext.suppressMemberLinks();
}
@Override
public boolean suppressMemberExtensions() {
return rendererContext.suppressMemberExtensions();
}
@Override
public boolean suppressMemberDisabledReason() {
return rendererContext.suppressMemberDisabledReason();
}
@Override
public InteractionInitiatedBy getInteractionInitiatedBy() {
return rendererContext.getInteractionInitiatedBy();
}
@Override
public SpecificationLoader getSpecificationLoader() {
return rendererContext.getSpecificationLoader();
}
@Override
public ServicesInjector getServicesInjector() {
return rendererContext.getServicesInjector();
}
@Override
public RepresentationService.Intent getIntent() {
return intent;
}
}
private final RepresentationServiceContextAdapter representationServiceContext;
private final RepresentationService representationService;
private final TransactionService transactionService;
public DomainResourceHelper(final ResourceContext resourceContext, final ObjectAdapter objectAdapter) {
this(resourceContext, objectAdapter, new DomainObjectLinkTo());
}
public DomainResourceHelper(
final ResourceContext resourceContext,
final ObjectAdapter objectAdapter,
final ObjectAdapterLinkTo adapterLinkTo) {
this.resourceContext = resourceContext;
this.objectAdapter = objectAdapter;
representationServiceContext = new RepresentationServiceContextAdapter(resourceContext, adapterLinkTo);
adapterLinkTo.usingUrlBase(this.resourceContext)
.with(this.objectAdapter);
representationService = lookupService(RepresentationService.class);
transactionService = lookupService(TransactionService.class);
}
private final ResourceContext resourceContext;
private final ObjectAdapter objectAdapter;
// //////////////////////////////////////
// Helpers (resource delegate here)
// //////////////////////////////////////
/**
* Simply delegates to the {@link org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService} to
* render a representation of the object.
*/
public Response objectRepresentation() {
transactionService.flushTransaction();
return representationService
.objectRepresentation(representationServiceContext, objectAdapter);
}
/**
* Simply delegates to the {@link org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService} to
* render a representation of the object.
*
* @deprecated - use {@link #objectRepresentation()}
*/
@Deprecated
public Response objectRepresentation(final RepresentationService.Intent intent) {
transactionService.flushTransaction();
return representationService
.objectRepresentation(representationServiceContext, objectAdapter, intent);
}
/**
* Obtains the property (checking it is visible) of the object and then delegates to the
* {@link org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService} to render a representation
* of that property.
*/
public Response propertyDetails(
final String propertyId,
final MemberReprMode memberMode) {
ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(representationServiceContext, objectAdapter);
final OneToOneAssociation property = accessHelper.getPropertyThatIsVisibleForIntent(propertyId, ObjectAdapterAccessHelper.Intent.ACCESS);
transactionService.flushTransaction();
return representationService.propertyDetails(representationServiceContext, new ObjectAndProperty2(objectAdapter, property, memberMode), memberMode);
}
/**
* Obtains the collection (checking it is visible) of the object and then delegates to the
* {@link org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService} to render a representation
* of that collection.
*/
public Response collectionDetails(
final String collectionId,
final MemberReprMode memberMode) {
ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(representationServiceContext, objectAdapter);
final OneToManyAssociation collection = accessHelper.getCollectionThatIsVisibleForIntent(collectionId, ObjectAdapterAccessHelper.Intent.ACCESS);
transactionService.flushTransaction();
return representationService.collectionDetails(representationServiceContext, new ObjectAndCollection2(objectAdapter, collection, memberMode), memberMode);
}
/**
* Obtains the action details (arguments etc), checking it is visible, of the object and then delegates to the
* {@link org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService} to render a representation
* of that object's action (arguments).
*/
public Response actionPrompt(final String actionId) {
ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(representationServiceContext, objectAdapter);
final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectAdapterAccessHelper.Intent.ACCESS);
transactionService.flushTransaction();
return representationService.actionPrompt(representationServiceContext, new ObjectAndAction(objectAdapter, action));
}
/**
* Invokes the action for the object (checking it is visible) and then delegates to the
* {@link org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService} to render a representation
* of the result of that action.
*
* <p>
* The action must have {@link ActionSemantics.Of#isSafeInNature()} safe/request-cacheable} semantics
* otherwise an error response is thrown.
* </p>
*/
public Response invokeActionQueryOnly(final String actionId, final JsonRepresentation arguments) {
final ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(representationServiceContext, objectAdapter);
final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectAdapterAccessHelper.Intent.MUTATE);
final ActionSemantics.Of actionSemantics = action.getSemantics();
if (! actionSemantics.isSafeInNature()) {
throw RestfulObjectsApplicationException.createWithMessage(RestfulResponse.HttpStatusCode.METHOD_NOT_ALLOWED, "Method not allowed; action '%s' does not have safe semantics", action.getId());
}
return invokeActionUsingAdapters(action, arguments, ActionResultReprRenderer.SelfLink.INCLUDED);
}
/**
* Invokes the action for the object (checking it is visible) and then delegates to the
* {@link org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService} to render a representation
* of the result of that action.
*
* <p>
* The action must have {@link org.apache.isis.applib.annotation.ActionSemantics.Of#IDEMPOTENT idempotent}
* semantics otherwise an error response is thrown.
* </p>
*/
public Response invokeActionIdempotent(final String actionId, final JsonRepresentation arguments) {
final ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(representationServiceContext, objectAdapter);
final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectAdapterAccessHelper.Intent.MUTATE);
final ActionSemantics.Of actionSemantics = action.getSemantics();
if (!actionSemantics.isIdempotentInNature()) {
throw RestfulObjectsApplicationException.createWithMessage(RestfulResponse.HttpStatusCode.METHOD_NOT_ALLOWED, "Method not allowed; action '%s' is not idempotent", action.getId());
}
return invokeActionUsingAdapters(action, arguments, ActionResultReprRenderer.SelfLink.EXCLUDED);
}
/**
* Invokes the action for the object (checking it is visible) and then delegates to the
* {@link org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService} to render a representation
* of the result of that action.
*/
public Response invokeAction(final String actionId, final JsonRepresentation arguments) {
ObjectAdapterAccessHelper accessHelper = new ObjectAdapterAccessHelper(representationServiceContext, objectAdapter);
final ObjectAction action = accessHelper.getObjectActionThatIsVisibleForIntent(actionId, ObjectAdapterAccessHelper.Intent.MUTATE);
return invokeActionUsingAdapters(action, arguments, ActionResultReprRenderer.SelfLink.EXCLUDED);
}
private Response invokeActionUsingAdapters(
final ObjectAction action,
final JsonRepresentation arguments,
final ActionResultReprRenderer.SelfLink selfLink) {
final RepresentationService.Context rendererContext = representationServiceContext;
final ObjectAdapter objectAdapter = this.objectAdapter;
final ObjectActionArgHelper argHelper = new ObjectActionArgHelper(rendererContext, objectAdapter, action);
final List<ObjectAdapter> argAdapters = argHelper.parseAndValidateArguments(arguments);
// invoke
final ObjectAdapter mixedInAdapter = null; // action will automatically fill in if a mixin
final ObjectAdapter[] argAdapterArr = argAdapters.toArray(new ObjectAdapter[argAdapters.size()]);
final ObjectAdapter returnedAdapter = action.execute(
objectAdapter, mixedInAdapter, argAdapterArr,
InteractionInitiatedBy.USER);
final ObjectAndActionInvocation objectAndActionInvocation =
new ObjectAndActionInvocation(objectAdapter, action, arguments, returnedAdapter, selfLink);
// response
transactionService.flushTransaction();
return representationService.actionResult(representationServiceContext, objectAndActionInvocation, selfLink);
}
// //////////////////////////////////////
// dependencies (from context)
// //////////////////////////////////////
private ServicesInjector getServicesInjector() {
return resourceContext.getServicesInjector();
}
private <T> T lookupService(Class<T> serviceType) {
return getServicesInjector().lookupService(serviceType);
}
}