/* * Copyright 2012-2013 the original author or authors. * * Licensed 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.springframework.hateoas.core; import java.util.HashMap; import java.util.Map; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.hateoas.EntityLinks; import org.springframework.hateoas.ExposesResourceFor; import org.springframework.hateoas.Link; import org.springframework.hateoas.LinkBuilder; import org.springframework.hateoas.LinkBuilderFactory; import org.springframework.util.Assert; /** * {@link EntityLinks} implementation which assumes a certain URI mapping structure: * <ol> * <li>A class-level mapping annotation that can contain template variables. The URI needs to expose the collection * resource, which means the controller has to expose a handler method mapped to an empty path: e.g. * {@code @RequestMapping(method = RequestMethod.GET)} in case of a Spring MVC controller.</li> * <li>Individual resources are exposed via a nested mapping consisting of the id of the managed entity, e.g. {@code * @RequestMapping("/{id}")}.<li> * </ol> * <pre> * @Controller * @ExposesResourceFor(Order.class) * @RequestMapping("/orders") * class OrderController { * * @RequestMapping * ResponseEntity orders(…) { … } * * @RequestMapping("/{id}") * ResponseEntity order(@PathVariable("id") … ) { … } * } * </pre> * * @author Oliver Gierke */ public class ControllerEntityLinks extends AbstractEntityLinks { private final Map<Class<?>, Class<?>> entityToController; private final LinkBuilderFactory<? extends LinkBuilder> linkBuilderFactory; /** * Creates a new {@link ControllerEntityLinks} inspecting the configured classes for the given annotation. * * @param controllerTypes the controller classes to be inspected. * @param linkBuilderFactory the {@link LinkBuilder} to use to create links. */ public ControllerEntityLinks(Iterable<? extends Class<?>> controllerTypes, LinkBuilderFactory<? extends LinkBuilder> linkBuilderFactory) { Assert.notNull(controllerTypes, "ControllerTypes must not be null!"); Assert.notNull(linkBuilderFactory, "LinkBuilderFactory must not be null!"); this.linkBuilderFactory = linkBuilderFactory; this.entityToController = new HashMap<Class<?>, Class<?>>(); for (Class<?> controllerType : controllerTypes) { registerControllerClass(controllerType); } } private void registerControllerClass(Class<?> controllerType) { Assert.notNull(controllerType, "Controller type must nor be null!"); ExposesResourceFor annotation = AnnotationUtils.findAnnotation(controllerType, ExposesResourceFor.class); if (annotation != null) { entityToController.put(annotation.value(), controllerType); } else { throw new IllegalArgumentException(String.format("Controller %s must be annotated with @ExposesResourceFor!", controllerType.getName())); } } /* * (non-Javadoc) * @see org.springframework.hateoas.EntityLinks#linkTo(java.lang.Class) */ @Override public LinkBuilder linkFor(Class<?> entity) { return linkFor(entity, new Object[0]); } /* * (non-Javadoc) * @see org.springframework.hateoas.EntityLinks#linkTo(java.lang.Class, java.lang.Object) */ @Override public LinkBuilder linkFor(Class<?> entity, Object... parameters) { Assert.notNull(entity, "Entity must not be null!"); Class<?> controllerType = entityToController.get(entity); if (controllerType == null) { throw new IllegalArgumentException(String.format( "Type %s is not managed by a Spring MVC controller. Make sure you have annotated your controller with %s!", entity.getName(), ExposesResourceFor.class.getName())); } return linkBuilderFactory.linkTo(controllerType, parameters); } /* * (non-Javadoc) * @see org.springframework.hateoas.EntityLinks#getLinkToCollectionResource(java.lang.Class) */ @Override public Link linkToCollectionResource(Class<?> entity) { return linkFor(entity).withSelfRel(); } /* * (non-Javadoc) * @see org.springframework.hateoas.EntityLinks#getLinkToSingleResource(java.lang.Class, java.lang.Object) */ @Override public Link linkToSingleResource(Class<?> entity, Object id) { return linkFor(entity).slash(id).withSelfRel(); } /* * (non-Javadoc) * @see org.springframework.plugin.core.Plugin#supports(java.lang.Object) */ @Override public boolean supports(Class<?> delimiter) { return entityToController.containsKey(delimiter); } }