| /** |
| * |
| * 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.io.UnsupportedEncodingException; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.net.URLDecoder; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import org.apache.zest.api.association.ManyAssociation; |
| import org.apache.zest.api.association.NamedAssociation; |
| import org.apache.zest.api.common.Optional; |
| import org.apache.zest.api.constraint.ConstraintViolation; |
| import org.apache.zest.api.constraint.ConstraintViolationException; |
| import org.apache.zest.api.constraint.Name; |
| import org.apache.zest.api.entity.EntityComposite; |
| import org.apache.zest.api.injection.scope.Service; |
| import org.apache.zest.api.injection.scope.Structure; |
| import org.apache.zest.api.injection.scope.Uses; |
| import org.apache.zest.api.property.PropertyDescriptor; |
| import org.apache.zest.api.structure.Module; |
| import org.apache.zest.api.unitofwork.EntityTypeNotFoundException; |
| import org.apache.zest.api.unitofwork.NoSuchEntityException; |
| import org.apache.zest.api.value.ValueBuilder; |
| import org.apache.zest.api.value.ValueComposite; |
| import org.apache.zest.api.value.ValueDescriptor; |
| import org.apache.zest.library.rest.common.Resource; |
| import org.apache.zest.library.rest.common.link.Link; |
| import org.apache.zest.library.rest.server.restlet.ConstraintViolationMessages; |
| import org.apache.zest.library.rest.server.restlet.InteractionConstraints; |
| import org.apache.zest.library.rest.server.restlet.RequestReaderDelegator; |
| import org.apache.zest.library.rest.server.restlet.ResponseWriterDelegator; |
| import org.apache.zest.library.rest.server.spi.ResultConverter; |
| import org.apache.zest.spi.Qi4jSPI; |
| import org.restlet.Request; |
| import org.restlet.Response; |
| import org.restlet.Uniform; |
| import org.restlet.data.Form; |
| import org.restlet.data.Language; |
| import org.restlet.data.Preference; |
| import org.restlet.data.Reference; |
| import org.restlet.data.Status; |
| import org.restlet.representation.EmptyRepresentation; |
| import org.restlet.representation.Representation; |
| import org.restlet.representation.StringRepresentation; |
| import org.restlet.resource.ResourceException; |
| import org.slf4j.LoggerFactory; |
| |
| import static org.apache.zest.api.util.Annotations.isType; |
| import static org.apache.zest.functional.Iterables.filter; |
| import static org.apache.zest.functional.Iterables.first; |
| import static org.apache.zest.functional.Iterables.iterable; |
| import static org.apache.zest.library.rest.server.api.ObjectSelection.current; |
| |
| /** |
| * JAVADOC |
| */ |
| public class ContextResource |
| implements Uniform |
| { |
| private static final String ARGUMENTS = "arguments"; |
| public static final String RESOURCE_VALIDITY = "validity"; |
| |
| // API fields |
| @Structure |
| protected Module module; |
| |
| // Private state |
| private final Map<String, Method> resourceMethodQueries = new HashMap<>(); |
| private final Map<String, Method> resourceMethodCommands = new HashMap<>(); |
| private final Map<String, Method> subResources = new LinkedHashMap<>(); |
| private final List<Method> resourceQueries = new ArrayList<>(); |
| private final List<Method> resourceCommands = new ArrayList<>(); |
| |
| @Structure |
| private Qi4jSPI spi; |
| |
| @Service |
| private ResponseWriterDelegator responseWriter; |
| |
| @Service |
| private RequestReaderDelegator requestReader; |
| |
| @Service |
| private InteractionConstraints constraints; |
| |
| @Optional |
| @Service |
| private ResultConverter converter; |
| |
| @Uses |
| private ContextRestlet restlet; |
| |
| public ContextResource() |
| { |
| // Resource method mappings |
| for( Method method : getClass().getMethods() ) |
| { |
| if( ContextResource.class.isAssignableFrom( method.getDeclaringClass() ) |
| && !ContextResource.class.equals( method.getDeclaringClass() ) |
| && !method.isSynthetic() ) |
| { |
| if( method.getAnnotation( SubResource.class ) == null ) |
| { |
| Method oldMethod; |
| |
| if( isCommand( method ) ) |
| { |
| oldMethod = resourceMethodCommands.put( method.getName().toLowerCase(), method ); |
| resourceCommands.add( method ); |
| } |
| else |
| { |
| oldMethod = resourceMethodQueries.put( method.getName().toLowerCase(), method ); |
| resourceQueries.add( method ); |
| } |
| |
| if( oldMethod != null ) |
| { |
| throw new IllegalStateException( "Two methods in resource " + getClass().getName() + " with same name " + oldMethod |
| .getName() + ", which is not allowed" ); |
| } |
| } |
| else |
| { |
| Method oldMethod = subResources.put( method.getName().toLowerCase(), method ); |
| |
| if( oldMethod != null ) |
| { |
| throw new IllegalStateException( "Two methods in resource " + getClass().getName() + " with same name " + oldMethod |
| .getName() + ", which is not allowed" ); |
| } |
| } |
| } |
| } |
| } |
| |
| // Uniform implementation |
| @Override |
| public final void handle( Request request, Response response ) |
| { |
| ObjectSelection objectSelection = current(); |
| |
| // Check constraints for this resource |
| if( !constraints.isValid( getClass(), objectSelection, module ) ) |
| { |
| throw new ResourceException( Status.CLIENT_ERROR_FORBIDDEN ); |
| } |
| |
| // Find remaining segments |
| List<String> segments = getSegments(); |
| |
| if( segments.size() > 0 ) |
| { |
| String segment = segments.remove( 0 ); |
| |
| if( segments.size() > 0 ) |
| { |
| handleSubResource( segment ); |
| } |
| else |
| { |
| handleResource( segment ); |
| } |
| } |
| } |
| |
| // API methods |
| protected void setResourceValidity( EntityComposite entity ) |
| { |
| Request request = Request.getCurrent(); |
| ResourceValidity validity = new ResourceValidity( entity, spi, request ); |
| request.getAttributes().put( RESOURCE_VALIDITY, validity ); |
| } |
| |
| protected void subResource( Class<? extends ContextResource> subResourceClass ) |
| { |
| restlet.subResource( subResourceClass ); |
| } |
| |
| protected <T> T select( Class<T> entityClass, String id ) |
| throws ResourceException |
| { |
| try |
| { |
| T composite = module.currentUnitOfWork().get( entityClass, id ); |
| current().select( composite ); |
| return composite; |
| } |
| catch( EntityTypeNotFoundException | NoSuchEntityException e ) |
| { |
| throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND ); |
| } |
| } |
| |
| protected <T> T selectFromManyAssociation( ManyAssociation<T> manyAssociation, String id ) |
| throws ResourceException |
| { |
| T entity = (T) module.currentUnitOfWork().get( Object.class, id ); |
| if( !manyAssociation.contains( entity ) ) |
| { |
| throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND ); |
| } |
| |
| current().select( entity ); |
| return entity; |
| } |
| |
| protected <T> T selectFromNamedAssociation( NamedAssociation<T> namedAssociation, String id ) |
| throws ResourceException |
| { |
| T entity = (T) module.currentUnitOfWork().get( Object.class, id ); |
| String name = namedAssociation.nameOf( entity ); |
| if(name == null) |
| { |
| throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND ); |
| } |
| |
| current().select( entity ); |
| return entity; |
| } |
| |
| protected void selectFromList( List<?> list, String indexString ) |
| { |
| Integer index = Integer.decode( indexString ); |
| |
| if( index < 0 || index >= list.size() ) |
| { |
| throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND ); |
| } |
| |
| current().select( index ); |
| |
| Object value = list.get( index ); |
| current().select( value ); |
| } |
| |
| protected Locale getLocale() |
| { |
| Request request = Request.getCurrent(); |
| |
| List<Preference<Language>> preferenceList = request.getClientInfo().getAcceptedLanguages(); |
| |
| if( preferenceList.isEmpty() ) |
| { |
| return Locale.getDefault(); |
| } |
| |
| Language language = preferenceList |
| .get( 0 ).getMetadata(); |
| String[] localeStr = language.getName().split( "-" ); |
| |
| Locale locale; |
| switch( localeStr.length ) |
| { |
| case 1: |
| locale = new Locale( localeStr[ 0 ] ); |
| break; |
| case 2: |
| locale = new Locale( localeStr[ 0 ], localeStr[ 1 ] ); |
| break; |
| case 3: |
| locale = new Locale( localeStr[ 0 ], localeStr[ 1 ], localeStr[ 2 ] ); |
| break; |
| default: |
| locale = Locale.getDefault(); |
| } |
| return locale; |
| } |
| |
| protected <T> T context( Class<T> contextClass ) |
| { |
| return module.newObject( contextClass, ObjectSelection.current().toArray() ); |
| } |
| |
| // Private implementation |
| private void handleSubResource( String segment ) |
| { |
| if( this instanceof SubResources ) |
| { |
| SubResources subResources = (SubResources) this; |
| try |
| { |
| StringBuilder template = (StringBuilder) Request.getCurrent().getAttributes().get( "template" ); |
| template.append( "resource/" ); |
| subResources.resource( URLDecoder.decode( segment, "UTF-8" ) ); |
| } |
| catch( UnsupportedEncodingException e ) |
| { |
| subResources.resource( segment ); |
| } |
| } |
| else |
| { |
| // Find @SubResource annotated method |
| try |
| { |
| Method method = getSubResourceMethod( segment ); |
| |
| StringBuilder template = (StringBuilder) Request.getCurrent().getAttributes().get( "template" ); |
| template.append( segment ).append( "/" ); |
| |
| method.invoke( this ); |
| } |
| catch( Throwable e ) |
| { |
| handleException( Response.getCurrent(), e ); |
| } |
| } |
| } |
| |
| private Method getSubResourceMethod( String resourceName ) |
| throws ResourceException |
| { |
| Method method = subResources.get( resourceName ); |
| if( method != null ) |
| { |
| return method; |
| } |
| |
| throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND ); |
| } |
| |
| private void resource() |
| { |
| Request request = Request.getCurrent(); |
| Response response = Response.getCurrent(); |
| if( !request.getMethod().equals( org.restlet.data.Method.GET ) ) |
| { |
| response.setStatus( Status.CLIENT_ERROR_METHOD_NOT_ALLOWED ); |
| return; |
| } |
| |
| ObjectSelection objectSelection = current(); |
| |
| // Check for interaction->method mappings |
| if( ResourceDelete.class.isAssignableFrom( getClass() ) ) |
| { |
| response.getAllowedMethods().add( org.restlet.data.Method.DELETE ); |
| } |
| if( ResourceUpdate.class.isAssignableFrom( getClass() ) ) |
| { |
| response.getAllowedMethods().add( org.restlet.data.Method.PUT ); |
| } |
| |
| // Construct resource |
| ValueBuilder<Resource> builder = module.newValueBuilder( Resource.class ); |
| |
| List<Link> queriesProperty = builder.prototype().queries().get(); |
| for( Method query : resourceQueries ) |
| { |
| if( constraints.isValid( query, objectSelection, module ) ) |
| { |
| ValueBuilder<Link> linkBuilder = module.newValueBuilder( Link.class ); |
| Link prototype = linkBuilder.prototype(); |
| prototype.classes().set( "query" ); |
| prototype.text().set( humanReadable( query.getName() ) ); |
| prototype.href().set( query.getName().toLowerCase() ); |
| prototype.rel().set( query.getName().toLowerCase() ); |
| prototype.id().set( query.getName().toLowerCase() ); |
| queriesProperty.add( linkBuilder.newInstance() ); |
| } |
| } |
| |
| List<Link> commandsProperty = builder.prototype().commands().get(); |
| for( Method command : resourceCommands ) |
| { |
| if( constraints.isValid( command, objectSelection, module ) ) |
| { |
| ValueBuilder<Link> linkBuilder = module.newValueBuilder( Link.class ); |
| Link prototype = linkBuilder.prototype(); |
| prototype.classes().set( "command" ); |
| prototype.text().set( humanReadable( command.getName() ) ); |
| prototype.href().set( command.getName().toLowerCase() ); |
| prototype.rel().set( command.getName().toLowerCase() ); |
| prototype.id().set( command.getName().toLowerCase() ); |
| commandsProperty.add( linkBuilder.newInstance() ); |
| } |
| } |
| |
| List<Link> resourcesProperty = builder.prototype().resources().get(); |
| for( Method subResource : subResources.values() ) |
| { |
| if( constraints.isValid( subResource, objectSelection, module ) ) |
| { |
| ValueBuilder<Link> linkBuilder = module.newValueBuilder( Link.class ); |
| Link prototype = linkBuilder.prototype(); |
| prototype.classes().set( "resource" ); |
| prototype.text().set( humanReadable( subResource.getName() ) ); |
| prototype.href().set( subResource.getName().toLowerCase() + "/" ); |
| prototype.rel().set( subResource.getName().toLowerCase() ); |
| prototype.id().set( subResource.getName().toLowerCase() ); |
| resourcesProperty.add( linkBuilder.newInstance() ); |
| } |
| } |
| |
| try |
| { |
| Method indexMethod = resourceMethodQueries.get( "index" ); |
| if( indexMethod != null ) |
| { |
| Object index = convert( indexMethod.invoke( this ) ); |
| |
| if( index != null && index instanceof ValueComposite ) |
| { |
| builder.prototype().index().set( (ValueComposite) index ); |
| } |
| } |
| } |
| catch( Throwable e ) |
| { |
| // Ignore |
| } |
| |
| try |
| { |
| responseWriter.writeResponse( builder.newInstance(), response ); |
| } |
| catch( Throwable e ) |
| { |
| handleException( response, e ); |
| } |
| } |
| |
| private boolean isCommand( Method method ) |
| { |
| return method.getReturnType().equals( Void.TYPE ) || method.getName().equals( "create" ); |
| } |
| |
| /** |
| * Transform a Java name to a human readable string by replacing uppercase characters |
| * with space+toLowerCase(char). |
| * <p> |
| * Example: |
| * <pre><code> |
| * changeDescription -> Change description |
| * doStuffNow -> Do stuff now |
| * </code></pre> |
| * |
| * @param name Java name |
| * |
| * @return Human readable name |
| */ |
| private String humanReadable( String name ) |
| { |
| StringBuilder humanReadableString = new StringBuilder(); |
| |
| for( int i = 0; i < name.length(); i++ ) |
| { |
| char character = name.charAt( i ); |
| if( i == 0 ) |
| { |
| // Capitalize first character |
| humanReadableString.append( Character.toUpperCase( character ) ); |
| } |
| else if( Character.isLowerCase( character ) ) |
| { |
| humanReadableString.append( character ); |
| } |
| else |
| { |
| humanReadableString.append( ' ' ).append( Character.toLowerCase( character ) ); |
| } |
| } |
| |
| return humanReadableString.toString(); |
| } |
| |
| private void result( Object resultValue ) |
| throws Exception |
| { |
| if( resultValue != null ) |
| { |
| if( !responseWriter.writeResponse( resultValue, Response.getCurrent() ) ) |
| { |
| throw new ResourceException( Status.SERVER_ERROR_INTERNAL, "No result writer for type " + resultValue.getClass() |
| .getName() ); |
| } |
| } |
| } |
| |
| private List<String> getSegments() |
| { |
| return (List<String>) Request.getCurrent().getAttributes().get( "segments" ); |
| } |
| |
| private void handleResource( String segment ) |
| { |
| Request request = Request.getCurrent(); |
| if( segment.isEmpty() || segment.equals( "." ) ) |
| { |
| StringBuilder template = (StringBuilder) request.getAttributes().get( "template" ); |
| template.append( "resource" ); |
| |
| // Index for this resource |
| resource(); |
| } |
| else |
| { |
| StringBuilder template = (StringBuilder) request.getAttributes().get( "template" ); |
| template.append( segment ); |
| |
| if( resourceMethodCommands.containsKey( segment ) ) |
| { |
| handleCommand( segment ); |
| } |
| else |
| { |
| handleQuery( segment ); |
| } |
| } |
| } |
| |
| private void handleCommand( String segment ) |
| { |
| Request request = Request.getCurrent(); |
| Response response = Response.getCurrent(); |
| |
| // Check if this is a request to show the form for this command |
| Method interactionMethod = resourceMethodCommands.get( segment ); |
| if( shouldShowCommandForm( interactionMethod ) ) |
| { |
| // Show form |
| |
| // TODO This should check if method is idempotent |
| response.getAllowedMethods().add( org.restlet.data.Method.POST ); |
| |
| try |
| { |
| // Check if there is a query with this name - if so invoke it |
| Method queryMethod = resourceMethodQueries.get( segment ); |
| if( queryMethod != null ) |
| { |
| result( queryMethod.invoke( this ) ); |
| } |
| else |
| { |
| request.setMethod( org.restlet.data.Method.POST ); |
| response.setStatus( Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY ); |
| result( formForMethod( interactionMethod ) ); |
| } |
| } |
| catch( Exception e ) |
| { |
| handleException( response, e ); |
| } |
| } |
| else |
| { |
| // Check timestamps |
| ResourceValidity validity = (ResourceValidity) request.getAttributes().get( RESOURCE_VALIDITY ); |
| if( validity != null ) |
| { |
| validity.checkRequest(); |
| } |
| |
| // We have input data - do command |
| // Check method constraints |
| if( !constraints.isValid( interactionMethod, current(), module ) ) |
| { |
| throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND ); |
| } |
| |
| // Create argument |
| Object[] arguments = requestReader.readRequest( Request.getCurrent(), interactionMethod ); |
| Request.getCurrent().getAttributes().put( ARGUMENTS, arguments ); |
| |
| // Invoke method |
| try |
| { |
| Object result = interactionMethod.invoke( this, arguments ); |
| |
| if( result != null ) |
| { |
| if( result instanceof Representation ) |
| { |
| response.setEntity( (Representation) result ); |
| } |
| else |
| { |
| result( convert( result ) ); |
| } |
| } |
| } |
| catch( Throwable e ) |
| { |
| handleException( response, e ); |
| } |
| } |
| } |
| |
| private boolean shouldShowCommandForm( Method interactionMethod ) |
| { |
| // Show form on GET/HEAD |
| if( Request.getCurrent().getMethod().isSafe() ) |
| { |
| return true; |
| } |
| |
| if( interactionMethod.getParameterTypes().length > 0 ) |
| { |
| return !( interactionMethod.getParameterTypes()[ 0 ].equals( Response.class ) || Request.getCurrent() |
| .getEntity() |
| .isAvailable() || Request.getCurrent().getEntityAsText() != null || Request.getCurrent() |
| .getResourceRef() |
| .getQuery() != null ); |
| } |
| |
| return false; |
| } |
| |
| private void handleQuery( String segment ) |
| { |
| Request request = Request.getCurrent(); |
| Response response = Response.getCurrent(); |
| |
| // Query |
| |
| // Try to locate either the query method or command method that should be used |
| Method queryMethod = resourceMethodQueries.get( segment ); |
| if( queryMethod == null ) |
| { |
| queryMethod = resourceMethodCommands.get( segment ); |
| } |
| |
| if( queryMethod == null ) |
| { |
| // Not found as interaction, try SubResource |
| Method resourceMethod = subResources.get( segment ); |
| if( resourceMethod != null && resourceMethod.getAnnotation( SubResource.class ) != null ) |
| { |
| // Found it! Redirect to it |
| response.setStatus( Status.REDIRECTION_FOUND ); |
| response |
| .setLocationRef( new Reference( request |
| .getResourceRef() |
| .toString() + "/" ).toString() ); |
| return; |
| } |
| else |
| { |
| // 404 |
| throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND ); |
| } |
| } |
| |
| // Check if this is a request to show the form for this interaction |
| if( ( request |
| .getMethod() |
| .isSafe() && queryMethod.getParameterTypes().length != 0 && request |
| .getResourceRef() |
| .getQuery() == null ) || |
| ( !request |
| .getMethod() |
| .isSafe() && queryMethod.getParameterTypes().length != 0 && !( request |
| .getEntity() |
| .isAvailable() || request |
| .getResourceRef() |
| .getQuery() != null || queryMethod |
| .getParameterTypes()[ 0 ].equals( Response.class ) ) ) ) |
| { |
| // Show form |
| try |
| { |
| // Tell client to try again |
| response.setStatus( Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY ); |
| response.getAllowedMethods().add( org.restlet.data.Method.GET ); |
| response.getAllowedMethods().add( org.restlet.data.Method.POST ); |
| result( formForMethod( queryMethod ) ); |
| } |
| catch( Exception e ) |
| { |
| handleException( response, e ); |
| } |
| } |
| else |
| { |
| // Check timestamps |
| ResourceValidity validity = (ResourceValidity) request.getAttributes().get( RESOURCE_VALIDITY ); |
| if( validity != null ) |
| { |
| validity.checkRequest(); |
| } |
| |
| // We have input data - do query |
| // Check method constraints |
| if( !constraints.isValid( queryMethod, current(), module ) ) |
| { |
| throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND ); |
| } |
| |
| try |
| { |
| // Create argument |
| Object[] arguments; |
| if( queryMethod.getParameterTypes().length > 0 ) |
| { |
| try |
| { |
| arguments = requestReader.readRequest( Request.getCurrent(), queryMethod ); |
| |
| if( arguments == null ) |
| { |
| // Show form |
| result( formForMethod( queryMethod ) ); |
| return; |
| } |
| } |
| catch( IllegalArgumentException e ) |
| { |
| // Still missing some values - show form |
| result( formForMethod( queryMethod ) ); |
| return; |
| } |
| } |
| else |
| { |
| // No arguments to this query |
| arguments = new Object[ 0 ]; |
| } |
| |
| // Invoke method |
| Request.getCurrent().getAttributes().put( ARGUMENTS, arguments ); |
| Object result = queryMethod.invoke( this, arguments ); |
| if( result != null ) |
| { |
| if( result instanceof Representation ) |
| { |
| response.setEntity( (Representation) result ); |
| } |
| else |
| { |
| result( convert( result ) ); |
| } |
| } |
| } |
| catch( Throwable e ) |
| { |
| handleException( response, e ); |
| } |
| } |
| } |
| |
| private Object convert( Object result ) |
| { |
| if( converter != null ) |
| { |
| result = converter.convert( result, Request.getCurrent(), (Object[]) Request.getCurrent() |
| .getAttributes() |
| .get( ARGUMENTS ) ); |
| } |
| |
| return result; |
| } |
| |
| private void handleException( Response response, Throwable ex ) |
| { |
| while( ex instanceof InvocationTargetException ) |
| { |
| ex = ex.getCause(); |
| } |
| |
| try |
| { |
| throw ex; |
| } |
| catch( ResourceException e ) |
| { |
| // IAE (or subclasses) are considered client faults |
| response.setEntity( new StringRepresentation( e.getMessage() ) ); |
| response.setStatus( e.getStatus() ); |
| } |
| catch( ConstraintViolationException e ) |
| { |
| try |
| { |
| ConstraintViolationMessages cvm = new ConstraintViolationMessages(); |
| |
| // CVE are considered client faults |
| String messages = ""; |
| Locale locale = ObjectSelection.type( Locale.class ); |
| for( ConstraintViolation constraintViolation : e.constraintViolations() ) |
| { |
| if( !messages.isEmpty() ) |
| { |
| messages += "\n"; |
| } |
| messages += cvm.getMessage( constraintViolation, locale ); |
| } |
| |
| response.setEntity( new StringRepresentation( messages ) ); |
| response.setStatus( Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY ); |
| } |
| catch( Exception e1 ) |
| { |
| response.setEntity( new StringRepresentation( e.getMessage() ) ); |
| response.setStatus( Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY ); |
| } |
| } |
| catch( IllegalArgumentException e ) |
| { |
| // IAE (or subclasses) are considered client faults |
| 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 |
| String s = e.getMessage(); |
| if( s == null ) |
| { |
| s = e.getClass().getSimpleName(); |
| } |
| response.setEntity( new StringRepresentation( s ) ); |
| 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 ); |
| } |
| } |
| |
| private Form formForMethod( Method interactionMethod ) |
| { |
| Form form = new Form(); |
| |
| Form queryAsForm = Request.getCurrent().getResourceRef().getQueryAsForm(); |
| Form entityAsForm; |
| Representation representation = Request.getCurrent().getEntity(); |
| if( representation != null && !EmptyRepresentation.class.isInstance( representation ) ) |
| { |
| entityAsForm = new Form( representation ); |
| } |
| else |
| { |
| entityAsForm = new Form(); |
| } |
| |
| Class<?> valueType = interactionMethod.getParameterTypes()[ 0 ]; |
| if( ValueComposite.class.isAssignableFrom( valueType ) ) |
| { |
| ValueDescriptor valueDescriptor = module.valueDescriptor( valueType.getName() ); |
| |
| for( PropertyDescriptor propertyDescriptor : valueDescriptor.state().properties() ) |
| { |
| String value = getValue( propertyDescriptor.qualifiedName().name(), queryAsForm, entityAsForm ); |
| |
| if( value == null ) |
| { |
| Object initialValue = propertyDescriptor.initialValue( module ); |
| if( initialValue != null ) |
| { |
| value = initialValue.toString(); |
| } |
| } |
| |
| form.add( propertyDescriptor.qualifiedName().name(), value ); |
| } |
| } |
| else if( valueType.isInterface() && interactionMethod.getParameterTypes().length == 1 ) |
| { |
| // Single entity as input |
| form.add( "entity", getValue( "entity", queryAsForm, entityAsForm ) ); |
| } |
| else |
| { |
| // Construct form out of individual parameters instead |
| for( Annotation[] annotations : interactionMethod.getParameterAnnotations() ) |
| { |
| Name name = (Name) first( filter( isType( Name.class ), iterable( annotations ) ) ); |
| form.add( name.value(), getValue( name.value(), queryAsForm, entityAsForm ) ); |
| } |
| } |
| |
| return form; |
| } |
| |
| private String getValue( String name, Form queryAsForm, Form entityAsForm ) |
| { |
| String value = queryAsForm.getFirstValue( name ); |
| if( value == null ) |
| { |
| value = entityAsForm.getFirstValue( name ); |
| } |
| return value; |
| } |
| } |