/**
*
* Copyright
* 2009-2015 Jayway Products AB
* 2016-2017 Föreningen Sambruk
*
* Licensed under AGPL, Version 3.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.gnu.org/licenses/agpl.txt
*
* 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 se.streamsource.dci.restlet.server.requestreader;
import org.json.JSONException;
import org.qi4j.api.common.Optional;
import org.qi4j.api.common.QualifiedName;
import org.qi4j.api.constraint.Name;
import org.qi4j.api.entity.EntityReference;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.property.Property;
import org.qi4j.api.property.StateHolder;
import org.qi4j.api.util.DateFunctions;
import org.qi4j.api.value.ValueBuilder;
import org.qi4j.api.value.ValueComposite;
import org.qi4j.spi.Qi4jSPI;
import org.qi4j.spi.property.PropertyType;
import org.qi4j.spi.structure.ModuleSPI;
import org.qi4j.spi.value.ValueDescriptor;
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 se.streamsource.dci.restlet.server.RequestReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;
import static org.qi4j.api.util.Annotations.isType;
import static org.qi4j.api.util.Iterables.*;
/**
* Convert request into method arguments.
*
* TODO: This should be split into many classes to handle the different cases.
*/
public class DefaultRequestReader
implements RequestReader
{
@Structure
Qi4jSPI spi;
@Structure
ModuleSPI module;
public Object[] readRequest(Request request, Method method)
{
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.valueBuilderFactory().newValueFromJSON(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.unitOfWorkFactory().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.valueBuilderFactory().newValueBuilder(valueType);
final ValueDescriptor descriptor = spi.getValueDescriptor(builder.prototype());
builder.withState(new StateHolder()
{
public <T> Property<T> getProperty(QualifiedName name)
{
return null;
}
public <T> Property<T> getProperty(Method propertyMethod)
{
return null;
}
public <ThrowableType extends Throwable> void visitProperties(StateVisitor<ThrowableType> visitor)
throws ThrowableType
{
for (PropertyType propertyType : descriptor.valueType().types())
{
Parameter param = queryAsForm.getFirst(propertyType.qualifiedName().name());
if (param == null)
param = entityAsForm.getFirst(propertyType.qualifiedName().name());
if (param != null)
{
String value = param.getValue();
if (value == null) {
value = "";
}
try
{
Object valueObject = propertyType.type().fromQueryParameter(value, module);
visitor.visitProperty(propertyType.qualifiedName(), valueObject);
} catch (JSONException e)
{
throw new IllegalArgumentException("Query parameter has invalid JSON format", e);
}
}
}
}
});
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) )
{
if( hasParamWithName( name.value(), queryAsForm, entityAsForm ) && argString == null )
{
arg = "";
} else
{
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 = DateFunctions.fromString(argString);
} else if (parameterType.isInterface())
{
arg = module.unitOfWorkFactory().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;
}
private boolean hasParamWithName( String name, Form queryAsForm, Form entityAsForm )
{
Parameter param = queryAsForm.getFirst( name );
if( param == null )
param = entityAsForm.getFirst( name );
return param != null;
}
}