blob: 793125525ca7677fd8cce1e5e4b329e0f2de0c7d [file] [log] [blame]
/**
*
* 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.apache.zest.library.rest.server.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.zest.api.cache.CacheOptions;
import org.apache.zest.api.injection.scope.Service;
import org.apache.zest.api.injection.scope.Structure;
import org.apache.zest.api.structure.Module;
import org.apache.zest.api.unitofwork.ConcurrentEntityModificationException;
import org.apache.zest.api.unitofwork.UnitOfWork;
import org.apache.zest.api.usecase.Usecase;
import org.apache.zest.api.usecase.UsecaseBuilder;
import org.apache.zest.library.rest.server.restlet.ResponseWriterDelegator;
import org.apache.zest.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 );
}
}
}