/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2010-2011 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.jersey.server.hypermedia.filter;
import com.sun.jersey.api.model.AbstractMethod;
import com.sun.jersey.api.model.AbstractResource;
import com.sun.jersey.api.model.AbstractResourceMethod;
import com.sun.jersey.core.header.LinkHeader;
import com.sun.jersey.core.hypermedia.Action;
import com.sun.jersey.core.hypermedia.ContextualActionSet;
import com.sun.jersey.core.hypermedia.HypermediaController;
import com.sun.jersey.core.hypermedia.HypermediaController.LinkType;
import com.sun.jersey.core.reflection.AnnotatedMethod;
import com.sun.jersey.core.reflection.MethodList;
import com.sun.jersey.core.reflection.ReflectionHelper;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponse;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;
import com.sun.jersey.spi.container.ResourceFilterFactory;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Hypermedia filter factory.
*
* @author Santiago.PericasGeertsen@sun.com
* @see com.sun.jersey.api.container.filter
*/
public class HypermediaFilterFactory implements ResourceFilterFactory {
private final UriInfo uriInfo;
public HypermediaFilterFactory(@Context UriInfo uriInfo) {
this.uriInfo = uriInfo;
}
private class HypermediaFilter
implements ResourceFilter, ContainerResponseFilter, ContainerRequestFilter {
private AbstractMethod abstractMethod;
private Method contextualActionSetMethod;
private Map<String, AbstractResourceMethod> actionMethods;
public HypermediaFilter(AbstractMethod abstractMethod) {
this.abstractMethod = abstractMethod;
actionMethods = new HashMap<String, AbstractResourceMethod>();
prepare();
}
private void prepare() {
AbstractResource ar = abstractMethod.getResource();
List<Method> methods = getContextualActionSetMethods(ar);
if (!methods.isEmpty()) {
contextualActionSetMethod = methods.get(0);
}
// Look for @Action methods as sub-resource methods
for (AbstractResourceMethod m : ar.getSubResourceMethods()) {
Action action = m.getAnnotation(Action.class);
if (action != null) {
actionMethods.put(action.value(), m);
}
}
}
private List<Method> getContextualActionSetMethods(AbstractResource resource) {
final MethodList methodList = new MethodList(resource.getResourceClass(), true);
final List<Method> contextualActionSetMethods = new ArrayList<Method>();
for (AnnotatedMethod m : methodList.
hasAnnotation(ContextualActionSet.class).
hasNumParams(0).
hasReturnType(Set.class)) {
ReflectionHelper.setAccessibleMethod(m.getMethod());
contextualActionSetMethods.add(m.getMethod());
}
return contextualActionSetMethods;
}
// ResourceFilter
public ContainerRequestFilter getRequestFilter() {
return this;
}
public ContainerResponseFilter getResponseFilter() {
return this;
}
// ContainerResponseFilter
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
Object resourceInstance = uriInfo.getMatchedResources().get(0);
if (request.isTracingEnabled()) {
request.trace("HypermediaFilter called for response; " +
"resourceInstance = " + resourceInstance);
}
// If contract is contextual
Set<String> actionSet = null;
if (contextualActionSetMethod != null) {
try {
actionSet = (Set<String>)
contextualActionSetMethod.invoke(resourceInstance);
} catch (Exception _) {
// falls through
}
}
// If no contextual set, use all actions defined
if (actionSet == null) {
actionSet = actionMethods.keySet();
}
// Add link headers and check soundness of actionSet
if (actionSet != null) {
for (String action : actionSet) {
AbstractResourceMethod rm = actionMethods.get(action);
if (rm == null) {
throw new RuntimeException("Contextual action set " +
"returned by resource " + resourceInstance +
"is not sound");
}
// Bit hacky, but we need to drop path of last action
String uri = uriInfo.getRequestUri().toString();
if (abstractMethod.isAnnotationPresent(Action.class)) {
Path path = abstractMethod.getAnnotation(Path.class);
int k = uri.lastIndexOf(path.value());
assert k > 0;
uri = uri.substring(0, k);
}
UriBuilder uriBuilder = UriBuilder.fromUri(uri);
LinkHeader lh = LinkHeader.uri(uriBuilder.path(rm.getMethod()).build()).
rel(action).op(rm.getHttpMethod()).build();
response.getHttpHeaders().add("Link", lh);
}
}
return response;
}
// ContainerRequestFilter
public ContainerRequest filter(ContainerRequest request) {
Object resourceInstance = uriInfo.getMatchedResources().get(0);
if (request.isTracingEnabled()) {
request.trace("HypermediaFilter called for request; " +
"resourceInstance = " + resourceInstance);
}
// If not action method, no need to do any checks
Action action = abstractMethod.getAnnotation(Action.class);
if (action == null) {
return request;
}
// If contract is contextual
Set<String> actionSet = null;
if (contextualActionSetMethod != null) {
try {
actionSet = (Set<String>)
contextualActionSetMethod.invoke(resourceInstance);
} catch (Exception _) {
// falls through
}
}
// Check if action is in contextual action set
if (actionSet != null && !actionSet.contains(action.value())) {
throw new RuntimeException("Action '" +
action.value() +
"' is not in contextual action set returned by " +
resourceInstance);
}
return request;
}
}
public List<ResourceFilter> create(AbstractMethod am) {
// Check if method belongs to controller class
AbstractResource ar = am.getResource();
HypermediaController ctrl = ar.getAnnotation(HypermediaController.class);
if (ctrl != null) {
if (ctrl.linkType() != LinkType.LINK_HEADERS) {
throw new RuntimeException("Unsupported hypermedia " +
"link type " + ctrl.linkType().name());
}
// Return type matches model class or action?
if (am.getMethod().getReturnType() == ctrl.model() ||
am.isAnnotationPresent(Action.class))
{
List<ResourceFilter> result = new ArrayList<ResourceFilter>(1);
result.add(new HypermediaFilter(am));
return result;
}
}
// no filters
return null;
}
}