blob: d12cb0f5d2280fdfe4b6fe43360f11f5c8d4fa61 [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.wink.common.model.wadl;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.MatrixParam;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.xml.namespace.QName;
import org.apache.wink.common.DynamicResource;
import org.apache.wink.common.internal.registry.Injectable;
import org.apache.wink.common.internal.registry.Injectable.ParamType;
import org.apache.wink.common.internal.registry.metadata.ClassMetadata;
import org.apache.wink.common.internal.registry.metadata.MethodMetadata;
import org.apache.wink.common.internal.registry.metadata.ResourceMetadataCollector;
import org.apache.wink.common.internal.uritemplate.JaxRsUriTemplateProcessor;
import org.apache.wink.common.internal.uritemplate.UriTemplateProcessor;
/**
* @Path("/sfdsf"); public class MyResource {
* @Context javax.ws.rs.core.Application app;
* @OPTIONS public org.apache.wink.common.model.wadl.Application getWADL() {
* return new WADLGenerator().generate(app.getClasses()); } }
*/
public class WADLGenerator {
private static final String XML_SCHEMA_NS = "http://www.w3.org/2001/XMLSchema";
public Application generate(String baseURI, Set<Class<?>> classes) {
/*
* the idea is that classes comes from the Application subclass
*/
Application app = new Application();
if (classes == null || classes.isEmpty()) {
return app;
}
Resources resources = new Resources();
resources.setBase(baseURI);
Set<ClassMetadata> metadataSet = buildClassMetdata(classes);
resources.getResource().addAll(buildResources(metadataSet));
app.getResources().add(resources);
return app;
}
/* package */Set<ClassMetadata> buildClassMetdata(Set<Class<?>> classes) {
Set<ClassMetadata> metadataSet = new HashSet<ClassMetadata>(classes.size());
for (Class<?> c : classes) {
if (!isResource(c)) {
/* not a resource, so skip it */
continue;
}
ClassMetadata metadata = ResourceMetadataCollector.collectMetadata(c);
metadataSet.add(metadata);
}
return metadataSet;
}
/* package */Set<Resource> buildResources(Set<ClassMetadata> metadataSet) {
Set<Resource> s = new HashSet<Resource>(metadataSet.size());
for (ClassMetadata metadata : metadataSet) {
Resource r = buildResource(metadata);
s.add(r);
}
return s;
}
/* package */Resource buildResource(ClassMetadata metadata) {
Resource r = new Resource();
if (metadata != null) {
Class<?> resClass = metadata.getResourceClass();
if (resClass != null) {
WADLDoc d = resClass.getAnnotation(WADLDoc.class);
if (d != null) {
r.getDoc().add(getDocument(d));
}
}
}
/* set the path */
String path = metadata.getPath();
if (path == null) {
Class<?> resClass = metadata.getResourceClass();
if (DynamicResource.class.isAssignableFrom(resClass)) {
try {
DynamicResource dynRes = (DynamicResource)resClass.newInstance();
path = dynRes.getPath();
} catch (Exception e) {
// Drop through and look for @Path annotation.
}
}
}
UriTemplateProcessor processor = JaxRsUriTemplateProcessor.newNormalizedInstance(path);
String pathStr = processor.getTemplate();
for (String var : processor.getVariableNames()) {
pathStr = pathStr.replaceAll("\\{(\\s)*" + var + "(\\s)*:.*\\}", "{" + var + "}");
}
r.setPath(pathStr);
List<Object> methodOrSubresource = r.getMethodOrResource();
List<Param> resourceParams = r.getParam();
List<MethodMetadata> methodMetadata = metadata.getResourceMethods();
if (methodMetadata != null && !methodMetadata.isEmpty()) {
for (MethodMetadata methodMeta : methodMetadata) {
Method m = buildMethod(metadata, methodMeta);
methodOrSubresource.add(m);
/* also scan for all the path and matrix parameters */
List<Injectable> params = methodMeta.getFormalParameters();
if (params != null && params.size() > 0) {
for (Injectable p : params) {
Param param = null;
boolean isUnique = true;
switch (p.getParamType()) {
case QUERY:
/* do nothing */
break;
case HEADER:
/* do nothing */
break;
case ENTITY:
/* do nothing */
break;
case COOKIE:
/* not supported in WADL */
break;
case FORM:
/* should show up in the representation instead */
break;
case PATH:
param = buildParam(p);
for (Param rParam : resourceParams) {
if (param.getName() != null && param.getName().equals(rParam
.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
resourceParams.add(param);
}
break;
case MATRIX:
param = buildParam(p);
for (Param rParam : resourceParams) {
if (param.getName() != null && param.getName().equals(rParam
.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
resourceParams.add(param);
}
break;
case CONTEXT:
/* do nothing */
break;
}
}
}
}
}
/*
* list the class level parameters
*/
List<Injectable> fields = metadata.getInjectableFields();
if (fields != null) {
for (Injectable p : metadata.getInjectableFields()) {
Param param = null;
boolean isUnique = true;
switch (p.getParamType()) {
case QUERY:
resourceParams.add(buildParam(p));
break;
case HEADER:
resourceParams.add(buildParam(p));
break;
case ENTITY:
/* do nothing */
break;
case COOKIE:
/* not supported in WADL */
break;
case FORM:
/* should show up in the representation instead */
break;
case PATH:
param = buildParam(p);
for (Param rParam : resourceParams) {
if (param.getName() != null && param.getName().equals(rParam.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
resourceParams.add(param);
}
break;
case MATRIX:
param = buildParam(p);
for (Param rParam : resourceParams) {
if (param.getName() != null && param.getName().equals(rParam.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
resourceParams.add(param);
}
break;
case CONTEXT:
/* do nothing */
break;
}
}
}
/*
* list subresource methods
*/
methodMetadata = metadata.getSubResourceMethods();
if (methodMetadata != null && !methodMetadata.isEmpty()) {
for (MethodMetadata methodMeta : methodMetadata) {
Resource subRes = new Resource();
Method m = buildMethod(metadata, methodMeta);
subRes.getMethodOrResource().add(m);
subRes.setPath(methodMeta.getPath());
methodOrSubresource.add(subRes);
List<Param> subResParams = subRes.getParam();
/* also scan for all the path and matrix parameters */
List<Injectable> params = methodMeta.getFormalParameters();
if (params != null && params.size() > 0) {
for (Injectable p : params) {
Param param = null;
boolean isUnique = true;
switch (p.getParamType()) {
case QUERY:
/* do nothing */
break;
case HEADER:
/* do nothing */
break;
case ENTITY:
/* do nothing */
break;
case COOKIE:
/* not supported in WADL */
break;
case FORM:
/* should show up in the representation instead */
break;
case PATH:
param = buildParam(p);
for (Param rParam : subResParams) {
if (param.getName() != null && param.getName().equals(rParam
.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
subRes.getParam().add(param);
}
break;
case MATRIX:
param = buildParam(p);
for (Param rParam : subResParams) {
if (param.getName() != null && param.getName().equals(rParam
.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
subRes.getParam().add(param);
}
break;
case CONTEXT:
/* do nothing */
break;
}
}
}
}
}
/*
* list subresource locators
*/
methodMetadata = metadata.getSubResourceLocators();
if (methodMetadata != null && !methodMetadata.isEmpty()) {
for (MethodMetadata methodMeta : methodMetadata) {
Resource subRes = new Resource();
subRes.setPath(methodMeta.getPath());
if (methodMeta != null) {
java.lang.reflect.Method reflMethod = methodMeta.getReflectionMethod();
if (reflMethod != null) {
WADLDoc d = reflMethod.getAnnotation(WADLDoc.class);
if (d != null) {
subRes.getDoc().add(getDocument(d));
}
}
}
methodOrSubresource.add(subRes);
/* also scan for all the path and matrix parameters */
List<Injectable> params = methodMeta.getFormalParameters();
List<Param> subResParams = subRes.getParam();
if (params != null && params.size() > 0) {
for (Injectable p : params) {
Param param = null;
boolean isUnique = true;
switch (p.getParamType()) {
case QUERY:
param = buildParam(p);
for (Param rParam : subResParams) {
if (param.getName() != null && param.getName().equals(rParam
.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
subRes.getParam().add(param);
}
break;
case HEADER:
param = buildParam(p);
for (Param rParam : subResParams) {
if (param.getName() != null && param.getName().equals(rParam
.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
subRes.getParam().add(param);
}
break;
case ENTITY:
/* do nothing */
break;
case COOKIE:
/* not supported in WADL */
break;
case FORM:
/* should show up in the representation instead */
break;
case PATH:
param = buildParam(p);
for (Param rParam : subResParams) {
if (param.getName() != null && param.getName().equals(rParam
.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
subRes.getParam().add(param);
}
break;
case MATRIX:
param = buildParam(p);
for (Param rParam : subResParams) {
if (param.getName() != null && param.getName().equals(rParam
.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
subRes.getParam().add(param);
}
break;
case CONTEXT:
/* do nothing */
break;
}
}
}
}
}
return r;
}
/* package */Method buildMethod(ClassMetadata classMetadata, MethodMetadata metadata) {
Method m = new Method();
m.setName(metadata.getHttpMethod());
java.lang.reflect.Method reflMethod = metadata.getReflectionMethod();
if (reflMethod != null) {
WADLDoc d = reflMethod.getAnnotation(WADLDoc.class);
if (d != null) {
m.getDoc().add(getDocument(d));
}
}
Request r = buildRequest(classMetadata, metadata);
if (r != null) {
m.setRequest(r);
}
List<Response> resp = buildResponse(metadata);
if (resp != null) {
m.getResponse().addAll(resp);
}
return m;
}
/* package */Request buildRequest(ClassMetadata classMetadata, MethodMetadata metadata) {
Request r = null;
List<Injectable> params = metadata.getFormalParameters();
if (params != null && params.size() > 0) {
if (r == null) {
r = new Request();
}
boolean hasValidParams = false;
List<Param> requestParams = r.getParam();
for (Injectable p : params) {
Param param = null;
boolean isUnique = true;
switch (p.getParamType()) {
case QUERY:
hasValidParams = true;
param = buildParam(p);
for (Param rParam : requestParams) {
if (param.getName() != null && param.getName().equals(rParam.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
requestParams.add(buildParam(p));
}
break;
case HEADER:
hasValidParams = true;
param = buildParam(p);
for (Param rParam : requestParams) {
if (param.getName() != null && param.getName().equals(rParam.getName())
&& rParam.getStyle().equals(param.getStyle())) {
isUnique = false;
}
}
if (isUnique) {
requestParams.add(buildParam(p));
}
break;
case ENTITY:
hasValidParams = true;
Annotation[] anns = p.getAnnotations();
if (anns != null && anns.length > 0) {
for (Annotation a : anns) {
if (WADLDoc.class.equals(a.annotationType())) {
WADLDoc d = (WADLDoc)a;
r.getDoc().add(getDocument(d));
}
}
}
/* need to build the representation */
Set<Representation> representations =
buildIncomingRepresentation(classMetadata, metadata, p);
if (representations != null)
r.getRepresentation().addAll(representations);
break;
case COOKIE:
/* not supported in WADL */
break;
case FORM:
/* should show up in the representation instead */
break;
case PATH:
/* should show up in the containing resource */
break;
case MATRIX:
/* should show up in the containing resource */
break;
case CONTEXT:
/* do nothing */
break;
}
}
if (!hasValidParams) {
r = null;
}
}
return r;
}
/* package */Param buildParam(Injectable paramMetadata) {
Param p = null;
ParamType pt = paramMetadata.getParamType();
Annotation[] annotations = paramMetadata.getAnnotations();
switch (pt) {
case HEADER:
p = new Param();
p.setStyle(ParamStyle.HEADER);
if (annotations != null) {
for (Annotation a : annotations) {
if (HeaderParam.class.equals(a.annotationType())) {
HeaderParam paramAnn = (HeaderParam)a;
p.setName(paramAnn.value());
}
}
}
break;
case MATRIX:
p = new Param();
p.setStyle(ParamStyle.MATRIX);
for (Annotation a : annotations) {
if (MatrixParam.class.equals(a.annotationType())) {
MatrixParam paramAnn = (MatrixParam)a;
p.setName(paramAnn.value());
}
}
break;
case PATH:
p = new Param();
p.setStyle(ParamStyle.TEMPLATE);
for (Annotation a : annotations) {
if (PathParam.class.equals(a.annotationType())) {
PathParam paramAnn = (PathParam)a;
p.setName(paramAnn.value());
}
}
break;
case QUERY:
p = new Param();
p.setStyle(ParamStyle.QUERY);
for (Annotation a : annotations) {
if (QueryParam.class.equals(a.annotationType())) {
QueryParam paramAnn = (QueryParam)a;
p.setName(paramAnn.value());
}
}
break;
case FORM:
p = new Param();
p.setStyle(ParamStyle.QUERY);
for (Annotation a : annotations) {
if (FormParam.class.equals(a.annotationType())) {
FormParam paramAnn = (FormParam)a;
p.setName(paramAnn.value());
}
}
break;
case CONTEXT:
break;
case COOKIE:
break;
case ENTITY:
break;
}
if (p == null) {
/*
* The paramtype was never set so return null. This might have been
* some other type of injectable that shouldn't be created as a
* Param. This is a preventive measure.
*/
return null;
}
Class<?> memberType = paramMetadata.getType();
if (memberType.equals(int.class) || memberType.equals(Integer.class)) {
p.setType(new QName(XML_SCHEMA_NS, "int"));
} else if (memberType.equals(float.class) || memberType.equals(Float.class)) {
p.setType(new QName(XML_SCHEMA_NS, "float"));
} else if (memberType.equals(long.class) || memberType.equals(Long.class)) {
p.setType(new QName(XML_SCHEMA_NS, "long"));
} else if (memberType.equals(boolean.class) || memberType.equals(Boolean.class)) {
p.setType(new QName(XML_SCHEMA_NS, "boolean"));
} else if (memberType.equals(short.class) || memberType.equals(Short.class)) {
p.setType(new QName(XML_SCHEMA_NS, "short"));
} else if (memberType.equals(double.class) || memberType.equals(Double.class)) {
p.setType(new QName(XML_SCHEMA_NS, "double"));
} else if (memberType.equals(byte.class) || memberType.equals(Byte.class)) {
p.setType(new QName(XML_SCHEMA_NS, "byte"));
}
for (Annotation a : annotations) {
if (DefaultValue.class.equals(a.annotationType())) {
DefaultValue paramAnn = (DefaultValue)a;
p.setDefault(paramAnn.value());
}
if (WADLDoc.class.equals(a.annotationType())) {
WADLDoc d = (WADLDoc)a;
p.getDoc().add(getDocument(d));
}
}
return p;
}
/* package */Set<Representation> buildIncomingRepresentation(ClassMetadata classMetadata,
MethodMetadata methodMetadata,
Injectable metadata) {
if (methodMetadata == null) {
return null;
}
if (metadata != null && metadata.getParamType() != ParamType.ENTITY) {
throw new IllegalArgumentException("Parameter data is not for an entity.");
}
/*
* first check all the consumes
*/
Set<MediaType> consumesMT = methodMetadata.getConsumes();
Set<Representation> reps = null;
if (consumesMT != null && !consumesMT.isEmpty()) {
reps = new HashSet<Representation>();
for (MediaType mt : consumesMT) {
Representation r = new Representation();
r.setMediaType(mt.toString());
/*
* if the representation is a special case, we need to build for
* it
*/
/*
* special cases include application/xml, text/xml; should set
* the element attribute
*/
List<Param> params = new ArrayList<Param>();
if (mt.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE) || mt
.isCompatible(MediaType.MULTIPART_FORM_DATA_TYPE)) {
List<Injectable> injectables = methodMetadata.getFormalParameters();
if (injectables != null) {
for (Injectable i : injectables) {
if (ParamType.FORM.equals(i.getParamType())) {
params.add(buildParam(i));
}
}
}
/*
* should we scan the class metadata too? FormParams aren't
* required to be supported in other places than the
* resource method parameters
*/
if (!params.isEmpty()) {
r.getParam().addAll(params);
}
}
reps.add(r);
}
} else {
if (metadata == null) {
return null;
}
/*
* there aren't any consumes so we can't look for that but maybe we
* can look at the Providers registry and find all the relevant
* media types there
*/
}
return reps;
}
/* package */List<Response> buildResponse(MethodMetadata metadata) {
if (metadata == null) {
return null;
}
Response r = null;
// Set<MediaType> produces = metadata.getProduces();
Set<Representation> representations = buildOutgoingRepresentation(metadata, null);
if (representations != null && !representations.isEmpty()) {
r = new Response();
r.getRepresentation().addAll(representations);
}
java.lang.reflect.Method m = metadata.getReflectionMethod();
if (Void.TYPE.equals(m.getReturnType())) {
if (r == null) {
r = new Response();
}
r.getStatus().add(Long.valueOf(204));
} else if (!javax.ws.rs.core.Response.class.isAssignableFrom(m.getReturnType())) {
if (r == null) {
r = new Response();
}
r.getStatus().add(Long.valueOf(200));
}
return Collections.singletonList(r);
}
/* package */Set<Representation> buildOutgoingRepresentation(MethodMetadata methodMetadata,
Class<?> returnType) {
if (methodMetadata == null) {
return null;
}
/*
* first check all the consumes
*/
Set<MediaType> producesMT = methodMetadata.getProduces();
Set<Representation> reps = null;
if (producesMT != null && !producesMT.isEmpty()) {
reps = new HashSet<Representation>();
for (MediaType mt : producesMT) {
Representation r = new Representation();
r.setMediaType(mt.toString());
/*
* if the representation is a special case, we need to build for
* it
*/
/*
* TODO: special cases include application/xml, text/xml; should
* set the element attribute
*/
reps.add(r);
}
} else {
/*
* there aren't any produces so we can't look for that but maybe we
* can look at the Providers registry and find all the relevant
* media types there
*/
}
return reps;
}
/* package */Doc getDocument(WADLDoc desc) {
Doc d = new Doc();
d.setTitle(desc.value());
if (!"".equals(desc.content())) {
d.getContent().add(desc.content());
}
return d;
}
/*
* Customized isResource method so it accepts interfaces.
*/
protected boolean isResource(Class<?> cls) {
if (ResourceMetadataCollector.isDynamicResource(cls)) {
return true;
}
if (cls.getAnnotation(Path.class) != null) {
return true;
}
Class<?> declaringClass = cls;
while (!declaringClass.equals(Object.class)) {
// try a superclass
Class<?> superclass = declaringClass.getSuperclass();
if (superclass.getAnnotation(Path.class) != null) {
return true;
}
// try interfaces
Class<?>[] interfaces = declaringClass.getInterfaces();
for (Class<?> interfaceClass : interfaces) {
if (interfaceClass.getAnnotation(Path.class) != null) {
return true;
}
}
declaringClass = declaringClass.getSuperclass();
}
return false;
}
}