| /** |
| * |
| * Copyright 2009-2011 Rickard Öberg AB |
| * |
| * Licensed 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.qi4j.library.rest.server.api; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| 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.qi4j.library.rest.server.restlet.ResponseWriterDelegator; |
| import org.qi4j.library.rest.server.spi.CommandResult; |
| 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; |
| |
| /** |
| * JAVADOC |
| */ |
| public abstract class ContextRestlet |
| extends Restlet |
| { |
| @Structure |
| protected Module module; |
| |
| @Service |
| private CommandResult commandResult; |
| |
| @Service |
| private 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 ) ) |
| .withMetaInfo( request.getMethod().isSafe() ? CacheOptions.ALWAYS : CacheOptions.NEVER ) |
| .newUsecase(); |
| UnitOfWork uow = module.newUnitOfWork( usecase ); |
| |
| ObjectSelection.newSelection(); |
| |
| 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 ) |
| { |
| |
| ResourceValidity validity = (ResourceValidity) Request.getCurrent() |
| .getAttributes() |
| .get( ContextResource.RESOURCE_VALIDITY ); |
| if( validity != null ) |
| { |
| validity.updateResponse( response ); |
| } |
| } |
| |
| // 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 = ObjectSelection.type( 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.writeResponse( 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.newUnitOfWork(); |
| |
| try |
| { |
| validity.updateEntity( lastModifiedUoW ); |
| |
| validity.updateResponse( response ); |
| } |
| finally |
| { |
| lastModifiedUoW.discard(); |
| } |
| } |
| } |
| } |
| return; |
| } |
| return; |
| } |
| catch( ConcurrentEntityModificationException ex ) |
| { |
| uow.discard(); |
| |
| // Try again |
| ObjectSelection.newSelection(); |
| } |
| catch( Throwable e ) |
| { |
| uow.discard(); |
| handleException( response, e ); |
| return; |
| } |
| } // Try again |
| } |
| finally |
| { |
| MDC.clear(); |
| } |
| } |
| |
| protected abstract Uniform createRoot( Request request, Response response ); |
| |
| // Callbacks used from resources |
| public void subResource( Class<? extends ContextResource> subResourceClass ) |
| { |
| Uniform subResource = subResources.get( subResourceClass ); |
| |
| if( subResource == null ) |
| { |
| // Instantiate and store subresource instance |
| subResource = module.newObject( subResourceClass, this ); |
| subResources.put( subResourceClass, subResource ); |
| } |
| |
| subResource.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 ); |
| } |
| } |
| } |