/** * * Copyright * 2009-2015 Jayway Products AB * 2016-2017 Föreningen Sambruk * * Licensed under AGPL, Version 3.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.gnu.org/licenses/agpl.txt * * 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 se.streamsource.dci.restlet.server; import org.qi4j.api.cache.CacheOptions; import org.qi4j.api.injection.scope.Service; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.structure.Module; import org.qi4j.api.unitofwork.ConcurrentEntityModificationException; import org.qi4j.api.unitofwork.UnitOfWork; import org.qi4j.api.usecase.Usecase; import org.qi4j.api.usecase.UsecaseBuilder; import org.restlet.Request; import org.restlet.Response; import org.restlet.Restlet; import org.restlet.Uniform; import org.restlet.data.CharacterSet; import org.restlet.data.Language; import org.restlet.data.Method; import org.restlet.data.Reference; import org.restlet.data.Status; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.resource.ResourceException; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import se.streamsource.dci.api.RoleMap; import se.streamsource.dci.restlet.server.api.ResourceValidity; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * JAVADOC */ public abstract class CommandQueryRestlet extends Restlet { protected @Structure Module module; @Service CommandResult commandResult; @Service ResponseWriterDelegator responseWriter; private Map<Class, Uniform> subResources = Collections.synchronizedMap(new HashMap<Class, Uniform>()); @Override public void handle(Request request, Response response) { super.handle(request, response); MDC.put("url", request.getResourceRef().toString()); try { int tries = 0; // TODO Make this number configurable while (tries < 10) { tries++; // Root of the call Reference ref = request.getResourceRef(); List<String> segments = ref.getScheme().equals("riap") ? ref.getRelativeRef(new Reference("riap://application/")).getSegments() : ref.getRelativeRef().getSegments(); // Handle conversion of verbs into standard interactions if (segments.get(segments.size() - 1).equals("")) { if (request.getMethod().equals(Method.DELETE)) { // Translate DELETE into command "delete" segments.set(segments.size() - 1, "delete"); } else if (request.getMethod().equals(Method.PUT)) { // Translate PUT into command "update" segments.set(segments.size() - 1, "update"); } } request.getAttributes().put("segments", segments); request.getAttributes().put("template", new StringBuilder("/rest/")); Usecase usecase = UsecaseBuilder.buildUsecase(getUsecaseName(request)).with(request.getMethod().isSafe() ? CacheOptions.ALWAYS : CacheOptions.NEVER).newUsecase(); UnitOfWork uow = module.unitOfWorkFactory().newUnitOfWork(usecase); RoleMap.newCurrentRoleMap(); try { // Start handling the build-up for the context Uniform resource = createRoot(request, response); resource.handle(request, response); if (response.getEntity() != null) { if (response.getEntity().getModificationDate() == null) { try { ResourceValidity validity = RoleMap.role(ResourceValidity.class); validity.updateResponse(response); } catch (IllegalArgumentException e) { // Ignore } } // Check if characterset is set if (response.getEntity().getCharacterSet() == null) { response.getEntity().setCharacterSet(CharacterSet.UTF_8); } // Check if language is set if (response.getEntity().getLanguages().isEmpty()) { response.getEntity().getLanguages().add(Language.ENGLISH); } uow.discard(); } else { // Check if last modified and tag is set ResourceValidity validity = null; try { validity = RoleMap.role(ResourceValidity.class); } catch (IllegalArgumentException e) { // Ignore } uow.complete(); Object result = commandResult.getResult(); if (result != null) { if (result instanceof Representation) response.setEntity((Representation) result); else { if (!responseWriter.write(result, response)) throw new ResourceException(Status.SERVER_ERROR_INTERNAL, "Could not write result of type " + result.getClass().getName()); } if (response.getEntity() != null) { // Check if characterset is set if (response.getEntity().getCharacterSet() == null) { response.getEntity().setCharacterSet(CharacterSet.UTF_8); } // Check if language is set if (response.getEntity().getLanguages().isEmpty()) { response.getEntity().getLanguages().add(Language.ENGLISH); } // Check if last modified and tag should be set if (validity != null) { UnitOfWork lastModifiedUoW = module.unitOfWorkFactory().newUnitOfWork(); try { validity.updateEntity(lastModifiedUoW); validity.updateResponse(response); } finally { lastModifiedUoW.discard(); } } } } return; } return; } catch (ConcurrentEntityModificationException ex) { uow.discard(); // Try again } catch (Throwable e) { uow.discard(); handleException(response, e); return; } finally { RoleMap.clearCurrentRoleMap(); } } } finally { MDC.clear(); } } protected abstract Uniform createRoot(Request request, Response response); // Callbacks used from resources public void subResource(Class<? extends CommandQueryResource> subResourceClass) { Uniform subResource = subResources.get(subResourceClass); if (subResource == null) { // Instantiate and store subresource instance subResource = module.objectBuilderFactory().newObjectBuilder(subResourceClass).use(this).newInstance(); subResources.put(subResourceClass, subResource); } subResource.handle(Request.getCurrent(), Response.getCurrent()); } public void subResourceContexts(Class<?>[] contextClasses) { module.objectBuilderFactory().newObjectBuilder(DefaultCommandQueryResource.class).use(new Object[]{contextClasses}).newInstance().handle(Request.getCurrent(), Response.getCurrent()); } private String getUsecaseName(Request request) { if (request.getMethod().equals(org.restlet.data.Method.DELETE)) return "delete"; else return request.getResourceRef().getLastSegment(); } private void handleException(Response response, Throwable ex) { try { throw ex; } catch (ResourceException e) { // IAE (or subclasses) are considered client faults LoggerFactory.getLogger(getClass()).debug("ResourceException thrown during processing", e); response.setEntity(new StringRepresentation(e.getMessage())); response.setStatus(e.getStatus()); } catch (IllegalArgumentException e) { // IAE (or subclasses) are considered client faults LoggerFactory.getLogger(getClass()).debug("IllegalArgumentsException thrown during processing", e); response.setEntity(new StringRepresentation(e.getMessage())); response.setStatus(Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY); } catch (RuntimeException e) { // RuntimeExceptions are considered server faults LoggerFactory.getLogger(getClass()).warn("Exception thrown during processing", e); response.setEntity(new StringRepresentation(e.getMessage())); response.setStatus(Status.SERVER_ERROR_INTERNAL); } catch (Exception e) { // Checked exceptions are considered client faults LoggerFactory.getLogger(getClass()).debug("Checked exception thrown during processing", e); response.setEntity(new StringRepresentation(e.getMessage())); response.setStatus(Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY); } catch (Throwable e) { // Anything else are considered server faults LoggerFactory.getLogger(getClass()).error("Exception thrown during processing", e); response.setEntity(new StringRepresentation(e.getMessage())); response.setStatus(Status.SERVER_ERROR_INTERNAL); } } }