Java tutorial
/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at * * http://www.dspace.org/license/ */ package org.dspace.app.rest.model.hateoas; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang3.StringUtils; import org.atteo.evo.inflector.English; import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.LinkRest; import org.dspace.app.rest.model.LinksRest; import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.repository.DSpaceRestRepository; import org.dspace.app.rest.repository.LinkRestRepository; import org.dspace.app.rest.utils.Utils; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler; import org.springframework.hateoas.Link; import org.springframework.hateoas.PagedResources; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.core.EmbeddedWrappers; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonUnwrapped; /** * A base class for DSpace Rest HAL Resource. The HAL Resource wraps the REST * Resource adding support for the links and embedded resources. Each property * of the wrapped REST resource is automatically translated in a link and the * available information included as embedded resource * * @author Andrea Bollini (andrea.bollini at 4science.it) * */ public abstract class DSpaceResource<T extends RestModel> extends ResourceSupport { @JsonUnwrapped private final T data; @JsonProperty(value = "_embedded") @JsonInclude(Include.NON_EMPTY) private Map<String, Object> embedded = new HashMap<String, Object>(); public DSpaceResource(T data, Utils utils, String... rels) { this.data = data; if (data != null) { try { LinksRest links = data.getClass().getDeclaredAnnotation(LinksRest.class); if (links != null && rels != null) { List<String> relsList = Arrays.asList(rels); for (LinkRest linkAnnotation : links.links()) { if (!relsList.contains(linkAnnotation.name())) { continue; } String name = linkAnnotation.name(); Link linkToSubResource = utils.linkToSubResource(data, name); String apiCategory = data.getCategory(); String model = data.getType(); LinkRestRepository linkRepository = utils.getLinkResourceRepository(apiCategory, model, linkAnnotation.name()); if (!linkRepository.isEmbeddableRelation(data, linkAnnotation.name())) { continue; } try { //RestModel linkClass = linkAnnotation.linkClass().newInstance(); Method[] methods = linkRepository.getClass().getMethods(); boolean found = false; for (Method m : methods) { if (StringUtils.equals(m.getName(), linkAnnotation.method())) { // TODO add support for single linked object other than for collections Page<? extends Serializable> pageResult = (Page<? extends RestModel>) m.invoke( linkRepository, null, ((BaseObjectRest) data).getId(), null, null); EmbeddedPage ep = new EmbeddedPage(linkToSubResource.getHref(), pageResult, null); embedded.put(name, ep); found = true; } } // TODO custom exception if (!found) { throw new RuntimeException("Method for relation " + linkAnnotation.name() + " not found: " + linkAnnotation.method()); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e.getMessage(), e); } } } for (PropertyDescriptor pd : Introspector.getBeanInfo(data.getClass()).getPropertyDescriptors()) { Method readMethod = pd.getReadMethod(); String name = pd.getName(); if (readMethod != null && !"class".equals(name)) { LinkRest linkAnnotation = readMethod.getAnnotation(LinkRest.class); if (linkAnnotation != null) { if (StringUtils.isNotBlank(linkAnnotation.name())) { name = linkAnnotation.name(); } Link linkToSubResource = utils.linkToSubResource(data, name); // no method is specified to retrieve the linked object(s) so check if it is already here if (StringUtils.isBlank(linkAnnotation.method())) { this.add(linkToSubResource); Object linkedObject = readMethod.invoke(data); Object wrapObject = linkedObject; if (linkedObject instanceof RestModel) { RestModel linkedRM = (RestModel) linkedObject; wrapObject = utils .getResourceRepository(linkedRM.getCategory(), linkedRM.getType()) .wrapResource(linkedRM); } else { if (linkedObject instanceof List) { List<RestModel> linkedRMList = (List<RestModel>) linkedObject; if (linkedRMList.size() > 0) { DSpaceRestRepository<RestModel, ?> resourceRepository = utils .getResourceRepository(linkedRMList.get(0).getCategory(), linkedRMList.get(0).getType()); // TODO should we force pagination also of embedded resource? // This will force a pagination with size 10 for embedded collections as well // int pageSize = 1; // PageImpl<RestModel> page = new PageImpl( // linkedRMList.subList(0, // linkedRMList.size() > pageSize ? pageSize : linkedRMList.size()), new PageRequest(0, pageSize), linkedRMList.size()); PageImpl<RestModel> page = new PageImpl(linkedRMList); wrapObject = new EmbeddedPage(linkToSubResource.getHref(), page.map(resourceRepository::wrapResource), linkedRMList); } else { wrapObject = null; } } } if (linkedObject != null) { embedded.put(name, wrapObject); } else { embedded.put(name, null); } Method writeMethod = pd.getWriteMethod(); writeMethod.invoke(data, new Object[] { null }); } else { // call the link repository try { //RestModel linkClass = linkAnnotation.linkClass().newInstance(); String apiCategory = data.getCategory(); String model = data.getType(); LinkRestRepository linkRepository = utils.getLinkResourceRepository(apiCategory, model, linkAnnotation.name()); Method[] methods = linkRepository.getClass().getMethods(); boolean found = false; for (Method m : methods) { if (StringUtils.equals(m.getName(), linkAnnotation.method())) { // TODO add support for single linked object other than for collections Page<? extends Serializable> pageResult = (Page<? extends RestModel>) m .invoke(linkRepository, null, ((BaseObjectRest) data).getId(), null, null); EmbeddedPage ep = new EmbeddedPage(linkToSubResource.getHref(), pageResult, null); embedded.put(name, ep); found = true; } } // TODO custom exception if (!found) { throw new RuntimeException("Method for relation " + linkAnnotation.name() + " not found: " + linkAnnotation.method()); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException(e.getMessage(), e); } } } else if (RestModel.class.isAssignableFrom(readMethod.getReturnType())) { Link linkToSubResource = utils.linkToSubResource(data, name); this.add(linkToSubResource); RestModel linkedObject = (RestModel) readMethod.invoke(data); if (linkedObject != null) { embedded.put(name, utils .getResourceRepository(linkedObject.getCategory(), linkedObject.getType()) .wrapResource(linkedObject)); } else { embedded.put(name, null); } Method writeMethod = pd.getWriteMethod(); writeMethod.invoke(data, new Object[] { null }); } } } } catch (IntrospectionException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e.getMessage(), e); } this.add(utils.linkToSingleResource(data, Link.REL_SELF)); } } @Override public void add(Link link) { System.out.println("Chiamato " + link.getRel()); super.add(link); } public Map<String, Object> getEmbedded() { return embedded; } public T getData() { return data; } }