blob: 62dd3189d8e5178b8d828959e3dd37b8cc35bb1f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.restlet.requestreader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import org.apache.zest.api.association.AssociationDescriptor;
import org.apache.zest.api.common.Optional;
import org.apache.zest.api.constraint.Name;
import org.apache.zest.api.entity.EntityReference;
import org.apache.zest.api.injection.scope.Service;
import org.apache.zest.api.injection.scope.Structure;
import org.apache.zest.api.property.PropertyDescriptor;
import org.apache.zest.api.service.qualifier.Tagged;
import org.apache.zest.api.structure.Module;
import org.apache.zest.api.util.Dates;
import org.apache.zest.api.value.ValueBuilder;
import org.apache.zest.api.value.ValueComposite;
import org.apache.zest.api.value.ValueDeserializer;
import org.apache.zest.api.value.ValueSerialization;
import org.apache.zest.api.value.ValueSerializationException;
import org.apache.zest.functional.Function;
import org.apache.zest.functional.Iterables;
import org.apache.zest.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.Logger;
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.functional.Iterables.matchesAny;
/**
* Convert request into method arguments.
*
* TODO: This should be split into many classes to handle the different cases.
* TODO: This does not support ManyAssociations
* TODO: This does not support NamedAssociations
*/
public class DefaultRequestReader
implements RequestReader
{
private static final Logger LOGGER = LoggerFactory.getLogger( DefaultRequestReader.class );
@Structure
private Module module;
@Service
@Tagged( ValueSerialization.Formats.JSON )
private ValueDeserializer valueDeserializer;
@Override
@SuppressWarnings( "unchecked" )
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;
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 )
{
LOGGER.error( "Restlet bug http://restlet.tigris.org/issues/show_bug.cgi?id=843 detected. "
+ "Notify developers!" );
throw new ResourceException( Status.SERVER_ERROR_INTERNAL,
"Bug in Restlet 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 )
{
LOGGER.error( "Restlet bug http://restlet.tigris.org/issues/show_bug.cgi?id=843 detected. "
+ "Notify developers!" );
throw new ResourceException( Status.SERVER_ERROR_INTERNAL,
"Bug in Restlet 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();
}
},
new Function<AssociationDescriptor, Map<String, EntityReference>>()
{
@Override
public Map<String, EntityReference> map( AssociationDescriptor from )
{
// TODO
return Collections.emptyMap();
}
}
);
return builder.newInstance();
}
@SuppressWarnings( "unchecked" )
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;
}
}