package org.qi4j.library.rest.server.restlet.requestreader; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Date; import org.qi4j.api.association.AssociationDescriptor; import org.qi4j.api.common.Optional; import org.qi4j.api.constraint.Name; import org.qi4j.api.entity.EntityReference; import org.qi4j.api.injection.scope.Service; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.property.PropertyDescriptor; import org.qi4j.api.service.qualifier.Tagged; import org.qi4j.api.structure.Module; import org.qi4j.api.util.Dates; import org.qi4j.api.value.ValueBuilder; import org.qi4j.api.value.ValueComposite; import org.qi4j.api.value.ValueDeserializer; import org.qi4j.api.value.ValueSerialization; import org.qi4j.api.value.ValueSerializationException; import org.qi4j.functional.Function; import org.qi4j.functional.Iterables; import org.qi4j.library.rest.server.spi.RequestReader; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.CharacterSet; import org.restlet.data.Form; import org.restlet.data.MediaType; import org.restlet.data.Parameter; import org.restlet.data.Status; import org.restlet.representation.EmptyRepresentation; import org.restlet.representation.Representation; import org.restlet.resource.ResourceException; import org.slf4j.LoggerFactory; import static org.qi4j.api.util.Annotations.isType; import static org.qi4j.functional.Iterables.filter; import static org.qi4j.functional.Iterables.first; import static org.qi4j.functional.Iterables.iterable; import static org.qi4j.functional.Iterables.matchesAny; /** * Convert request into method arguments. * * TODO: This should be split into many classes to handle the different cases. */ public class DefaultRequestReader implements RequestReader { @Structure private Module module; @Service @Tagged( ValueSerialization.Formats.JSON ) private ValueDeserializer valueDeserializer; @Override public Object[] readRequest( Request request, Method method ) throws ResourceException { if( request.getMethod().equals( org.restlet.data.Method.GET ) ) { Object[] args = new Object[ method.getParameterTypes().length ]; Form queryAsForm = Request.getCurrent().getResourceRef().getQueryAsForm(); Form entityAsForm = null; Representation representation = Request.getCurrent().getEntity(); if( representation != null && !EmptyRepresentation.class.isInstance( representation ) ) { entityAsForm = new Form( representation ); } else { entityAsForm = new Form(); } if( queryAsForm.isEmpty() && entityAsForm.isEmpty() ) { // Nothing submitted yet - show form return null; } if( args.length == 1 ) { if( ValueComposite.class.isAssignableFrom( method.getParameterTypes()[ 0 ] ) ) { Class<?> valueType = method.getParameterTypes()[ 0 ]; args[ 0 ] = getValueFromForm( (Class<ValueComposite>) valueType, queryAsForm, entityAsForm ); return args; } else if( Form.class.equals( method.getParameterTypes()[ 0 ] ) ) { args[ 0 ] = queryAsForm.isEmpty() ? entityAsForm : queryAsForm; return args; } else if( Response.class.equals( method.getParameterTypes()[ 0 ] ) ) { args[ 0 ] = Response.getCurrent(); return args; } } parseMethodArguments( method, args, queryAsForm, entityAsForm ); return args; } else { Object[] args = new Object[ method.getParameterTypes().length ]; Class<? extends ValueComposite> commandType = (Class<? extends ValueComposite>) method.getParameterTypes()[ 0 ]; if( method.getParameterTypes()[ 0 ].equals( Response.class ) ) { return new Object[]{ Response.getCurrent() }; } Representation representation = Request.getCurrent().getEntity(); MediaType type = representation.getMediaType(); if( type == null ) { Form queryAsForm = Request.getCurrent().getResourceRef().getQueryAsForm( CharacterSet.UTF_8 ); if( ValueComposite.class.isAssignableFrom( method.getParameterTypes()[ 0 ] ) ) { args[ 0 ] = getValueFromForm( commandType, queryAsForm, new Form() ); } else { parseMethodArguments( method, args, queryAsForm, new Form() ); } return args; } else { if( method.getParameterTypes()[ 0 ].equals( Representation.class ) ) { // Command method takes Representation as input return new Object[]{ representation }; } else if( method.getParameterTypes()[ 0 ].equals( Form.class ) ) { // Command method takes Form as input return new Object[]{ new Form( representation ) }; } else if( ValueComposite.class.isAssignableFrom( method.getParameterTypes()[ 0 ] ) ) { // Need to parse input into ValueComposite if( type.equals( MediaType.APPLICATION_JSON ) ) { String json = Request.getCurrent().getEntityAsText(); if( json == null ) { LoggerFactory.getLogger( getClass() ) .error( "Restlet bugg http://restlet.tigris.org/issues/show_bug.cgi?id=843 detected. Notify developers!" ); throw new ResourceException( Status.SERVER_ERROR_INTERNAL, "Bug in Tomcat encountered; notify developers!" ); } Object command = module.newValueFromSerializedState( commandType, json ); args[ 0 ] = command; return args; } else if( type.equals( MediaType.TEXT_PLAIN ) ) { String text = Request.getCurrent().getEntityAsText(); if( text == null ) { LoggerFactory.getLogger( getClass() ) .error( "Restlet bugg http://restlet.tigris.org/issues/show_bug.cgi?id=843 detected. Notify developers!" ); throw new ResourceException( Status.SERVER_ERROR_INTERNAL, "Bug in Tomcat encountered; notify developers!" ); } args[ 0 ] = text; return args; } else if( type.equals( ( MediaType.APPLICATION_WWW_FORM ) ) ) { Form queryAsForm = Request.getCurrent().getResourceRef().getQueryAsForm(); Form entityAsForm; if( representation != null && !EmptyRepresentation.class.isInstance( representation ) && representation .isAvailable() ) { entityAsForm = new Form( representation ); } else { entityAsForm = new Form(); } Class<?> valueType = method.getParameterTypes()[ 0 ]; args[ 0 ] = getValueFromForm( (Class<ValueComposite>) valueType, queryAsForm, entityAsForm ); return args; } else { throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST, "Command has to be in JSON format" ); } } else if( method.getParameterTypes()[ 0 ].isInterface() && method.getParameterTypes().length == 1 ) { Form queryAsForm = Request.getCurrent().getResourceRef().getQueryAsForm(); Form entityAsForm; if( representation != null && !EmptyRepresentation.class.isInstance( representation ) && representation .isAvailable() ) { entityAsForm = new Form( representation ); } else { entityAsForm = new Form(); } args[ 0 ] = module.currentUnitOfWork() .get( method.getParameterTypes()[ 0 ], getValue( "entity", queryAsForm, entityAsForm ) ); return args; } else { Form queryAsForm = Request.getCurrent().getResourceRef().getQueryAsForm(); Form entityAsForm; if( representation != null && !EmptyRepresentation.class.isInstance( representation ) && representation .isAvailable() ) { entityAsForm = new Form( representation ); } else { entityAsForm = new Form(); } parseMethodArguments( method, args, queryAsForm, entityAsForm ); return args; } } } } private ValueComposite getValueFromForm( Class<? extends ValueComposite> valueType, final Form queryAsForm, final Form entityAsForm ) { ValueBuilder<? extends ValueComposite> builder = module.newValueBuilderWithState( valueType, new Function<PropertyDescriptor, Object>() { @Override public Object map( PropertyDescriptor propertyDescriptor ) { Parameter param = queryAsForm .getFirst( propertyDescriptor .qualifiedName() .name() ); if( param == null ) { param = entityAsForm .getFirst( propertyDescriptor .qualifiedName() .name() ); } if( param != null ) { String value = param .getValue(); if( value != null ) { try { return valueDeserializer.deserialize( propertyDescriptor.valueType(), value ); } catch( ValueSerializationException e ) { throw new IllegalArgumentException( "Query parameter has invalid JSON format", e ); } } } return null; } }, new Function<AssociationDescriptor, EntityReference>() { @Override public EntityReference map( AssociationDescriptor associationDescriptor ) { Parameter param = queryAsForm .getFirst( associationDescriptor .qualifiedName() .name() ); if( param == null ) { param = entityAsForm .getFirst( associationDescriptor .qualifiedName() .name() ); } if( param != null ) { return EntityReference .parseEntityReference( param .getValue() ); } else { return null; } } }, new Function<AssociationDescriptor, Iterable<EntityReference>>() { @Override public Iterable<EntityReference> map( AssociationDescriptor associationDescriptor ) { // TODO return Iterables.empty(); } } ); return builder.newInstance(); } private void parseMethodArguments( Method method, Object[] args, Form queryAsForm, Form entityAsForm ) { // Parse each argument separately using the @Name annotation as help int idx = 0; for( Annotation[] annotations : method.getParameterAnnotations() ) { Name name = (Name) first( filter( isType( Name.class ), iterable( annotations ) ) ); if( name == null ) { throw new IllegalStateException( "No @Name annotation found on parameter of method:" + method ); } String argString = getValue( name.value(), queryAsForm, entityAsForm ); // Parameter conversion Class<?> parameterType = method.getParameterTypes()[ idx ]; Object arg = null; if( parameterType.equals( String.class ) ) { arg = argString; } else if( parameterType.equals( EntityReference.class ) ) { arg = EntityReference.parseEntityReference( argString ); } else if( parameterType.isEnum() ) { arg = Enum.valueOf( (Class<Enum>) parameterType, argString ); } else if( Integer.TYPE.isAssignableFrom( parameterType ) ) { arg = Integer.valueOf( argString ); } else if( Integer.class.isAssignableFrom( parameterType ) ) { if( argString != null ) { arg = Integer.valueOf( argString ); } } else if( Long.TYPE.isAssignableFrom( parameterType ) ) { arg = Long.valueOf( argString ); } else if( Long.class.isAssignableFrom( parameterType ) ) { if( argString != null ) { arg = Long.valueOf( argString ); } } else if( Short.TYPE.isAssignableFrom( parameterType ) ) { arg = Short.valueOf( argString ); } else if( Short.class.isAssignableFrom( parameterType ) ) { if( argString != null ) { arg = Short.valueOf( argString ); } } else if( Double.TYPE.isAssignableFrom( parameterType ) ) { arg = Double.valueOf( argString ); } else if( Double.class.isAssignableFrom( parameterType ) ) { if( argString != null ) { arg = Double.valueOf( argString ); } } else if( Float.TYPE.isAssignableFrom( parameterType ) ) { arg = Float.valueOf( argString ); } else if( Float.class.isAssignableFrom( parameterType ) ) { if( argString != null ) { arg = Float.valueOf( argString ); } } else if( Character.TYPE.isAssignableFrom( parameterType ) ) { arg = argString.charAt( 0 ); } else if( Character.class.isAssignableFrom( parameterType ) ) { if( argString != null ) { arg = argString.charAt( 0 ); } } else if( Boolean.TYPE.isAssignableFrom( parameterType ) ) { arg = Boolean.valueOf( argString ); } else if( Boolean.class.isAssignableFrom( parameterType ) ) { if( argString != null ) { arg = Boolean.valueOf( argString ); } } else if( Date.class.isAssignableFrom( parameterType ) ) { arg = Dates.fromString( argString ); } else if( parameterType.isInterface() ) { arg = module.currentUnitOfWork().get( parameterType, argString ); } else { throw new IllegalArgumentException( "Don't know how to parse parameter " + name.value() + " of type " + parameterType .getName() ); } if( arg == null && !matchesAny( isType( Optional.class ), iterable( annotations ) ) ) { throw new IllegalArgumentException( "Parameter " + name.value() + " was not set" ); } args[ idx++ ] = arg; } } private String getValue( String name, Form queryAsForm, Form entityAsForm ) { String value = queryAsForm.getFirstValue( name ); if( value == null ) { value = entityAsForm.getFirstValue( name ); } return value; } }