/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.engine.resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.restlet.Context;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
import org.restlet.data.Language;
import org.restlet.data.MediaType;
import org.restlet.data.Metadata;
import org.restlet.data.Method;
import org.restlet.data.Parameter;
import org.restlet.engine.util.StringUtils;
import org.restlet.representation.Representation;
import org.restlet.representation.Variant;
import org.restlet.service.MetadataService;
// [excludes gwt]
/**
* Descriptor for method annotations.
*
* @author Jerome Louvel
*/
public class MethodAnnotationInfo extends AnnotationInfo {
/** The input part of the annotation value. */
private final String input;
/** The output part of the annotation value. */
private final String output;
/** The optional query part of the annotation value. */
private final String query;
/** The matching Restlet method. */
private final Method restletMethod;
/**
* Constructor.
*
* @param javaClass
* The class or interface that hosts the annotated Java method.
* @param restletMethod
* The matching Restlet method.
* @param javaMethod
* The annotated Java method.
* @param annotationValue
* The annotation value.
*/
public MethodAnnotationInfo(Class<?> javaClass, Method restletMethod,
java.lang.reflect.Method javaMethod, String annotationValue) {
super(javaClass, javaMethod, annotationValue);
this.restletMethod = restletMethod;
// Parse the main components of the annotation value
if (!StringUtils.isNullOrEmpty(annotationValue)) {
int queryIndex = annotationValue.indexOf('?');
if (queryIndex != -1) {
this.query = annotationValue.substring(queryIndex + 1);
annotationValue = annotationValue.substring(0, queryIndex);
} else {
this.query = null;
}
int ioSeparatorIndex = annotationValue.indexOf(':');
if (ioSeparatorIndex != -1) {
this.input = annotationValue.substring(0, ioSeparatorIndex);
this.output = annotationValue.substring(ioSeparatorIndex + 1);
} else {
this.input = annotationValue;
this.output = annotationValue;
}
} else {
this.query = null;
this.input = null;
this.output = null;
}
}
/**
* Indicates if the current object is equal to the given object.
*
* @param other
* The other object.
* @return True if the current object includes the other.
*/
@Override
public boolean equals(Object other) {
boolean result = (other instanceof MethodAnnotationInfo);
if (result && (other != this)) {
MethodAnnotationInfo otherAnnotation = (MethodAnnotationInfo) other;
result = super.equals(otherAnnotation);
// Compare the Restlet method
if (result) {
result = ((getRestletMethod() == null)
&& (otherAnnotation.getRestletMethod() == null) || (getRestletMethod() != null)
&& getRestletMethod().equals(
otherAnnotation.getRestletMethod()));
}
}
return result;
}
/**
* Returns the input part of the annotation value.
*
* @return The input part of the annotation value.
*/
public String getInput() {
return input;
}
/**
* Returns the generic type for the given input parameter.
*
* @param index
* The input parameter index.
*
* @return The generic type.
*/
private Class<?> getJavaInputType(int index) {
return getJavaActualType(javaMethodImpl.getParameterTypes()[index],
javaMethodImpl.getGenericParameterTypes()[index]);
}
/**
* Returns the input types of the Java method.
*
* @return The input types of the Java method.
*/
public Class<?>[] getJavaInputTypes() {
int count = getJavaMethod().getParameterTypes().length;
Class<?>[] classes = new Class[count];
for (int i = 0; i < count; i++) {
classes[i] = getJavaInputType(i);
}
return classes;
}
/**
* Returns the output type of the Java method.
*
* @return The output type of the Java method.
*/
public Class<?> getJavaOutputType() {
return getJavaActualType(javaMethodImpl.getReturnType(),
javaMethodImpl.getGenericReturnType());
}
/**
* Returns the output part of the annotation value.
*
* @return The output part of the annotation value.
*/
public String getOutput() {
return output;
}
/**
* Returns the optional query part of the annotation value.
*
* @return The optional query part of the annotation value.
*/
public String getQuery() {
return query;
}
// [ifndef gwt] method
/**
* Returns a list of request variants based on the annotation value.
*
* @param metadataService
* The metadata service to use.
* @return A list of request variants.
* @throws IOException
*/
@SuppressWarnings("unchecked")
public List<Variant> getRequestVariants(MetadataService metadataService,
org.restlet.service.ConverterService converterService)
throws IOException {
List<Variant> result = null;
Class<?>[] classes = getJavaInputTypes();
if (classes != null && classes.length >= 1) {
result = getVariants(metadataService, getInput());
if (result == null) {
Class<?> inputClass = classes[0];
if (inputClass != null) {
result = (List<Variant>) converterService.getVariants(
inputClass, null);
}
}
}
return result;
}
// [ifndef gwt] method
/**
* Returns a list of response variants based on the annotation value.
*
* @param metadataService
* The metadata service to use.
* @param converterService
* The converter service to use.
* @return A list of response variants.
* @throws IOException
*/
@SuppressWarnings("unchecked")
public List<Variant> getResponseVariants(MetadataService metadataService,
org.restlet.service.ConverterService converterService)
throws IOException {
List<Variant> result = null;
if ((getJavaOutputType() != null)
&& (getJavaOutputType() != void.class)
&& (getJavaOutputType() != Void.class)) {
result = getVariants(metadataService, getOutput());
if (result == null) {
result = (List<Variant>) converterService.getVariants(
getJavaOutputType(), null);
}
}
return result;
}
/**
* Returns the matching Restlet method.
*
* @return The matching Restlet method.
*/
public Method getRestletMethod() {
return restletMethod;
}
// [ifndef gwt] method
/**
* Returns the list of representation variants associated to a given
* annotation value, corresponding to either an input or output entity.
*
* @param metadataService
* The metadata service to use.
* @param annotationValue
* The entity annotation value.
* @return A list of variants.
*/
private List<Variant> getVariants(MetadataService metadataService,
String annotationValue) {
List<Variant> result = null;
if (annotationValue != null) {
StringTokenizer stValue = new StringTokenizer(annotationValue,
"\\|");
while (stValue.hasMoreTokens()) {
String variantValue = stValue.nextToken().trim();
Variant variant = null;
List<MediaType> mediaTypes = null;
List<Language> languages = null;
CharacterSet characterSet = null;
StringTokenizer stExtension = new StringTokenizer(variantValue,
"\\+");
while (stExtension.hasMoreTokens()) {
String extension = stExtension.nextToken().trim();
if (extension == null) {
continue;
}
List<Metadata> metadataList = metadataService
.getAllMetadata(extension);
if (metadataList != null) {
for (Metadata metadata : metadataList) {
if (metadata instanceof MediaType) {
if (mediaTypes == null) {
mediaTypes = new ArrayList<MediaType>();
}
mediaTypes.add((MediaType) metadata);
} else if (metadata instanceof Language) {
if (languages == null) {
languages = new ArrayList<Language>();
}
languages.add((Language) metadata);
} else if (metadata instanceof CharacterSet) {
if (characterSet == null) {
characterSet = (CharacterSet) metadata;
} else {
Context.getCurrentLogger()
.warning(
"A representation variant can have only one character set. Please check your annotation value.");
}
}
}
}
}
// Now build the representation variants
if (mediaTypes != null) {
for (MediaType mediaType : mediaTypes) {
if ((result == null) || (!result.contains(mediaType))) {
if (result == null) {
result = new ArrayList<Variant>();
}
variant = new Variant(mediaType);
if (languages != null) {
variant.getLanguages().addAll(languages);
}
if (characterSet != null) {
variant.setCharacterSet(characterSet);
}
result.add(variant);
}
}
}
}
}
return result;
}
/**
* Indicates if the annotated method described is compatible with the given
* parameters.
*
* @param restletMethod
* The Restlet method to match.
* @param requestEntity
* Optional request entity.
* @param metadataService
* The metadata service to use.
* @param converterService
* The converter service to use.
* @return True if the annotated method is compatible.
* @throws IOException
*/
public boolean isCompatible(Method restletMethod, Form queryParams,
Representation requestEntity, MetadataService metadataService,
org.restlet.service.ConverterService converterService)
throws IOException {
boolean result = true;
// Verify query parameters
if (getQuery() != null) {
Form requiredParams = new Form(getQuery());
for (Iterator<Parameter> iter = requiredParams.iterator(); iter
.hasNext() && result;) {
result = queryParams.contains(iter.next());
}
}
// Verify HTTP method
if (result) {
result = getRestletMethod().equals(restletMethod);
}
// Verify request entity
if (result) {
result = isCompatibleRequestEntity(requestEntity, metadataService,
converterService);
}
return result;
}
/**
* Indicates if the given request entity is compatible with the annotated
* method described.
*
* @param requestEntity
* Optional request entity.
* @param metadataService
* The metadata service to use.
* @param converterService
* The converter service to use.
* @return True if the given request entity is compatible with the annotated
* method described.
* @throws IOException
*/
public boolean isCompatibleRequestEntity(Representation requestEntity,
MetadataService metadataService,
org.restlet.service.ConverterService converterService)
throws IOException {
boolean result = true;
if ((requestEntity != null) && requestEntity.isAvailable()) {
List<Variant> requestVariants = getRequestVariants(metadataService,
converterService);
if ((requestVariants != null) && !requestVariants.isEmpty()) {
// Check that the compatibility
result = false;
for (int i = 0; (!result) && (i < requestVariants.size()); i++) {
result = (requestVariants.get(i)
.isCompatible(requestEntity));
}
} else {
result = false;
}
}
return result;
}
@Override
public String toString() {
return "MethodAnnotationInfo [javaMethod: " + javaMethod
+ ", javaClass: " + getJavaClass() + ", restletMethod: "
+ restletMethod + ", input: " + getInput() + ", value: "
+ getAnnotationValue() + ", output: " + getOutput()
+ ", query: " + getQuery() + "]";
}
}