DS-3489: Move DSpaceResource links to LinkFactory

This commit is contained in:
Tom Desair
2017-10-26 15:51:16 +02:00
committed by Tom Desair
parent a6d8808c49
commit c96886b598
7 changed files with 159 additions and 145 deletions

View File

@@ -2,14 +2,19 @@
* The contents of this file are subject to the license and copyright * 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 * detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at * tree and available online at
* <p> *
* http://www.dspace.org/license/ * http://www.dspace.org/license/
*/ */
package org.dspace.app.rest; package org.dspace.app.rest;
import java.io.File;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.Filter;
import org.dspace.app.rest.filter.DSpaceRequestContextFilter; import org.dspace.app.rest.filter.DSpaceRequestContextFilter;
import org.dspace.app.rest.model.hateoas.DSpaceRelProvider; import org.dspace.app.rest.model.hateoas.DSpaceRelProvider;
import org.dspace.app.rest.parameter.resolver.SearchFilterResolver;
import org.dspace.app.rest.utils.ApplicationConfig; import org.dspace.app.rest.utils.ApplicationConfig;
import org.dspace.app.util.DSpaceContextListener; import org.dspace.app.util.DSpaceContextListener;
import org.dspace.kernel.DSpaceKernel; import org.dspace.kernel.DSpaceKernel;
@@ -28,11 +33,12 @@ import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.RelProvider;
import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@@ -59,16 +65,12 @@ import java.io.File;
*/ */
@SpringBootApplication @SpringBootApplication
public class Application extends SpringBootServletInitializer { public class Application extends SpringBootServletInitializer {
private static final Logger log = LoggerFactory.getLogger(Application.class); private static final Logger log = LoggerFactory.getLogger(Application.class);
@Autowired @Autowired
private ApplicationConfig configuration; private ApplicationConfig configuration;
@Value("${dspace.dir}")
private String dspaceHome;
private transient DSpaceKernelImpl kernelImpl;
/** /**
* Override the default SpringBootServletInitializer.configure() method, * Override the default SpringBootServletInitializer.configure() method,
* passing it this Application class. * passing it this Application class.
@@ -84,6 +86,9 @@ public class Application extends SpringBootServletInitializer {
*/ */
@Override @Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class)
.initializers(new DSpaceKernelInitializer());
}
// start the kernel when the webapp starts // start the kernel when the webapp starts
try { try {
@@ -112,14 +117,7 @@ public class Application extends SpringBootServletInitializer {
@Bean @Bean
public ServletContextInitializer contextInitializer() { public ServletContextInitializer contextInitializer() {
return new ServletContextInitializer() { return servletContext -> servletContext.setInitParameter("dspace.dir", configuration.getDspaceHome());
@Override
public void onStartup(ServletContext servletContext)
throws ServletException {
servletContext.setInitParameter("dspace.dir", configuration.getDspaceHome());
}
};
} }
/** /**
@@ -171,7 +169,7 @@ public class Application extends SpringBootServletInitializer {
} }
@Bean @Bean
public WebMvcConfigurer webMvcConfigurer() { public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() { return new WebMvcConfigurerAdapter() {
@Override @Override
public void addCorsMappings(CorsRegistry registry) { public void addCorsMappings(CorsRegistry registry) {
@@ -180,14 +178,61 @@ public class Application extends SpringBootServletInitializer {
registry.addMapping("/api/**").allowedOrigins(corsAllowedOrigins); registry.addMapping("/api/**").allowedOrigins(corsAllowedOrigins);
} }
} }
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new SearchFilterResolver());
}
}; };
} }
/** Utility class that will destroy the DSpace Kernel on Spring Boot shutdown */
private class DSpaceKernelDestroyer implements ApplicationListener<ContextClosedEvent> {
private DSpaceKernelImpl kernelImpl;
public DSpaceKernelDestroyer(DSpaceKernelImpl kernelImpl) {
this.kernelImpl = kernelImpl;
}
public void onApplicationEvent(final ContextClosedEvent event) {
if (this.kernelImpl != null) {
this.kernelImpl.destroy();
this.kernelImpl = null;
}
}
}
/** Utility class that will initialize the DSpace Kernel on Spring Boot startup */
private class DSpaceKernelInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private transient DSpaceKernelImpl kernelImpl;
public void initialize(final ConfigurableApplicationContext applicationContext) {
String dspaceHome = applicationContext.getEnvironment().getProperty("dspace.dir");
// start the kernel when the webapp starts
try {
this.kernelImpl = DSpaceKernelInit.getKernel(null);
if (!this.kernelImpl.isRunning()) {
this.kernelImpl.start(getProvidedHome(dspaceHome)); // init the kernel
}
//Set the DSpace Kernel Application context as a parent of the Spring Boot context so that
//we can auto-wire all DSpace Kernel services
applicationContext.setParent(kernelImpl.getServiceManager().getApplicationContext());
//Add a listener for Spring Boot application shutdown so that we can nicely cleanup the DSpace kernel.
applicationContext.addApplicationListener(new DSpaceKernelDestroyer(kernelImpl));
} catch (Exception e) {
// failed to start so destroy it and log and throw an exception
try {
this.kernelImpl.destroy();
} catch (Exception e1) {
// nothing
}
String message = "Failure during ServletContext initialisation: " + e.getMessage();
log.error(message + ":" + e.getMessage(), e);
throw new RuntimeException(message, e);
}
}
/** /**
* Find DSpace's "home" directory. * Find DSpace's "home" directory.
* Initially look for JNDI Resource called "java:/comp/env/dspace.dir". * Initially look for JNDI Resource called "java:/comp/env/dspace.dir".
@@ -210,23 +255,8 @@ public class Application extends SpringBootServletInitializer {
providedHome = dspaceHome; providedHome = dspaceHome;
} }
} }
return providedHome;
} }
return providedHome; return providedHome;
} }
private class DSpaceKernelDestroyer implements ApplicationListener<ContextClosedEvent> {
private DSpaceKernelImpl kernelImpl;
public DSpaceKernelDestroyer(DSpaceKernelImpl kernelImpl) {
this.kernelImpl = kernelImpl;
}
public void onApplicationEvent(final ContextClosedEvent event) {
if (this.kernelImpl != null) {
this.kernelImpl.destroy();
this.kernelImpl = null;
}
}
} }
} }

View File

@@ -26,6 +26,7 @@ import org.dspace.app.rest.exception.PaginationException;
import org.dspace.app.rest.exception.RepositoryNotFoundException; import org.dspace.app.rest.exception.RepositoryNotFoundException;
import org.dspace.app.rest.exception.RepositorySearchMethodNotFoundException; import org.dspace.app.rest.exception.RepositorySearchMethodNotFoundException;
import org.dspace.app.rest.exception.RepositorySearchNotFoundException; import org.dspace.app.rest.exception.RepositorySearchNotFoundException;
import org.dspace.app.rest.link.HalLinkService;
import org.dspace.app.rest.model.LinkRest; import org.dspace.app.rest.model.LinkRest;
import org.dspace.app.rest.model.RestModel; import org.dspace.app.rest.model.RestModel;
import org.dspace.app.rest.model.hateoas.DSpaceResource; import org.dspace.app.rest.model.hateoas.DSpaceResource;
@@ -74,6 +75,9 @@ public class RestResourceController implements InitializingBean {
@Autowired @Autowired
RestRepositoryUtils repositoryUtils; RestRepositoryUtils repositoryUtils;
@Autowired
HalLinkService linkService;
@Override @Override
public void afterPropertiesSet() { public void afterPropertiesSet() {
List<Link> links = new ArrayList<Link>(); List<Link> links = new ArrayList<Link>();
@@ -124,6 +128,7 @@ public class RestResourceController implements InitializingBean {
throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found"); throw new ResourceNotFoundException(apiCategory + "." + model + " with id: " + id + " not found");
} }
DSpaceResource result = repository.wrapResource(modelObject); DSpaceResource result = repository.wrapResource(modelObject);
linkService.addLinks(result);
return result; return result;
} }
@@ -213,6 +218,8 @@ public class RestResourceController implements InitializingBean {
} }
RestModel modelObject = repository.findOne(uuid); RestModel modelObject = repository.findOne(uuid);
DSpaceResource result = repository.wrapResource(modelObject, rel); DSpaceResource result = repository.wrapResource(modelObject, rel);
linkService.addLinks(result);
if (result.getLink(rel) == null) { if (result.getLink(rel) == null) {
// TODO create a custom exception // TODO create a custom exception
throw new ResourceNotFoundException(rel + "undefined for " + model); throw new ResourceNotFoundException(rel + "undefined for " + model);
@@ -251,6 +258,7 @@ public class RestResourceController implements InitializingBean {
Page<DSpaceResource<T>> resources; Page<DSpaceResource<T>> resources;
try { try {
resources = repository.findAll(page).map(repository::wrapResource); resources = repository.findAll(page).map(repository::wrapResource);
resources.forEach(linkService::addLinks);
} catch (PaginationException pe) { } catch (PaginationException pe) {
resources = new PageImpl<DSpaceResource<T>>(new ArrayList<DSpaceResource<T>>(), page, pe.getTotal()); resources = new PageImpl<DSpaceResource<T>>(new ArrayList<DSpaceResource<T>>(), page, pe.getTotal());
} }
@@ -320,6 +328,7 @@ public class RestResourceController implements InitializingBean {
ResourceSupport result = null; ResourceSupport result = null;
if (returnPage) { if (returnPage) {
Page<DSpaceResource<T>> resources = ((Page<T>) searchResult).map(repository::wrapResource); Page<DSpaceResource<T>> resources = ((Page<T>) searchResult).map(repository::wrapResource);
resources.forEach(linkService::addLinks);
result = assembler.toResource(resources, link); result = assembler.toResource(resources, link);
} else { } else {
result = repository.wrapResource((T) searchResult); result = repository.wrapResource((T) searchResult);

View File

@@ -7,38 +7,75 @@
*/ */
package org.dspace.app.rest.link; package org.dspace.app.rest.link;
import org.dspace.app.rest.DiscoveryRestController; import java.beans.IntrospectionException;
import org.dspace.app.rest.model.hateoas.DSpaceResource; import java.beans.Introspector;
import org.dspace.app.rest.model.hateoas.HALResource; import java.beans.PropertyDescriptor;
import org.dspace.app.rest.model.hateoas.SearchConfigurationResource; import java.lang.reflect.Method;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.stereotype.Component;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.RestResourceController;
import org.dspace.app.rest.model.LinkRest;
import org.dspace.app.rest.model.RestModel;
import org.dspace.app.rest.model.hateoas.DSpaceResource;
import org.dspace.app.rest.utils.Utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component;
/** /**
* Created by raf on 25/09/2017. * Created by raf on 25/09/2017.
*/ */
@Component @Component
public class DSpaceResourceHalLinkFactory extends HalLinkFactory<DSpaceResource, DiscoveryRestController> { public class DSpaceResourceHalLinkFactory extends HalLinkFactory<DSpaceResource, RestResourceController> {
@Autowired
private Utils utils;
protected void addLinks(DSpaceResource halResource, LinkedList<Link> list) { protected void addLinks(DSpaceResource halResource, LinkedList<Link> list) {
RestModel data = halResource.getData();
try {
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();
} }
protected Class<DiscoveryRestController> getControllerClass() { Link linkToSubResource = utils.linkToSubResource(data, name);
return null; // no method is specified to retrieve the linked object(s) so check if it is already here
if (StringUtils.isBlank(linkAnnotation.method())) {
halResource.add(linkToSubResource);
}
} else if (RestModel.class.isAssignableFrom(readMethod.getReturnType())) {
Link linkToSubResource = utils.linkToSubResource(data, name);
halResource.add(linkToSubResource);
}
}
}
} catch (IntrospectionException e) {
e.printStackTrace();
}
halResource.add(utils.linkToSingleResource(data, Link.REL_SELF));
}
protected Class<RestResourceController> getControllerClass() {
return RestResourceController.class;
} }
protected Class<DSpaceResource> getResourceClass() { protected Class<DSpaceResource> getResourceClass() {
return null; return DSpaceResource.class;
} }
protected String getSelfLink(DSpaceResource halResource) { protected String getSelfLink(DSpaceResource halResource) {
return null; return utils.linkToSingleResource(halResource.getData(), Link.REL_SELF).getHref();
} }
} }

View File

@@ -7,18 +7,17 @@
*/ */
package org.dspace.app.rest.link; package org.dspace.app.rest.link;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import java.util.LinkedList;
import java.util.List;
import org.dspace.app.rest.model.hateoas.HALResource; import org.dspace.app.rest.model.hateoas.HALResource;
import org.springframework.hateoas.Link; import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
/** /**
* Created by raf on 25/09/2017. * Created by raf on 25/09/2017.
*/ */
@@ -26,7 +25,7 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
public abstract class HalLinkFactory<RESOURCE, CONTROLLER> { public abstract class HalLinkFactory<RESOURCE, CONTROLLER> {
public boolean supports(Class clazz) { public boolean supports(Class clazz) {
if(Objects.equals(clazz, getResourceClass())){ if(getResourceClass().isAssignableFrom(clazz)){
return true; return true;
} }
return false; return false;

View File

@@ -1,44 +0,0 @@
/**
* 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.link;
import org.dspace.app.rest.DiscoveryRestController;
import org.dspace.app.rest.model.hateoas.DSpaceResource;
import org.dspace.app.rest.model.hateoas.HALResource;
import org.dspace.app.rest.model.hateoas.ItemResource;
import org.dspace.app.rest.model.hateoas.SearchConfigurationResource;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
import java.util.List;
/**
* Created by raf on 25/09/2017.
*/
@Component
public class ItemResourceHalLinkFactory extends HalLinkFactory<ItemResource, DiscoveryRestController> {
protected void addLinks(ItemResource halResource, LinkedList<Link> list) {
}
protected Class<DiscoveryRestController> getControllerClass() {
return null;
}
protected Class<ItemResource> getResourceClass() {
return null;
}
protected String getSelfLink(ItemResource halResource) {
return null;
}
}

View File

@@ -7,21 +7,13 @@
*/ */
package org.dspace.app.rest.link.search; package org.dspace.app.rest.link.search;
import java.util.LinkedList;
import org.dspace.app.rest.DiscoveryRestController; import org.dspace.app.rest.DiscoveryRestController;
import org.dspace.app.rest.link.HalLinkFactory; import org.dspace.app.rest.link.HalLinkFactory;
import org.dspace.app.rest.model.SearchConfigurationRest;
import org.dspace.app.rest.model.hateoas.HALResource;
import org.dspace.app.rest.model.hateoas.SearchConfigurationResource;
import org.dspace.app.rest.model.hateoas.SearchSupportResource; import org.dspace.app.rest.model.hateoas.SearchSupportResource;
import org.springframework.hateoas.Link; import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.LinkedList;
import java.util.List;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
/** /**
* Created by raf on 25/09/2017. * Created by raf on 25/09/2017.
@@ -32,7 +24,7 @@ public class SearchSupportHalLinkFactory extends HalLinkFactory<SearchSupportRes
protected void addLinks(SearchSupportResource halResource, LinkedList<Link> list) { protected void addLinks(SearchSupportResource halResource, LinkedList<Link> list) {
list.add(buildLink(Link.REL_SELF, getMethodOn() list.add(buildLink(Link.REL_SELF, getMethodOn()
.getSearchSupport(null, null))); .getSearchSupport(null, null)));
list.add(buildLink("configuration", getMethodOn().getSearchConfiguration(null, null))); list.add(buildLink("search", getMethodOn().getSearchConfiguration(null, null)));
list.add(buildLink("facets", getMethodOn().getFacetsConfiguration(null, null))); list.add(buildLink("facets", getMethodOn().getFacetsConfiguration(null, null)));
} }

View File

@@ -7,6 +7,16 @@
*/ */
package org.dspace.app.rest.model.hateoas; package org.dspace.app.rest.model.hateoas;
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.Arrays;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.annotation.JsonUnwrapped;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.dspace.app.rest.model.BaseObjectRest; import org.dspace.app.rest.model.BaseObjectRest;
@@ -20,16 +30,6 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageImpl;
import org.springframework.hateoas.Link; import org.springframework.hateoas.Link;
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.Arrays;
import java.util.List;
import java.util.Map;
/** /**
* A base class for DSpace Rest HAL Resource. The HAL Resource wraps the REST * 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 * Resource adding support for the links and embedded resources. Each property
@@ -99,7 +99,6 @@ public abstract class DSpaceResource<T extends RestModel> extends HALResource {
Link linkToSubResource = utils.linkToSubResource(data, name); Link linkToSubResource = utils.linkToSubResource(data, name);
// no method is specified to retrieve the linked object(s) so check if it is already here // no method is specified to retrieve the linked object(s) so check if it is already here
if (StringUtils.isBlank(linkAnnotation.method())) { if (StringUtils.isBlank(linkAnnotation.method())) {
Object linkedObject = readMethod.invoke(data); Object linkedObject = readMethod.invoke(data);
Object wrapObject = linkedObject; Object wrapObject = linkedObject;
if (linkedObject instanceof RestModel) { if (linkedObject instanceof RestModel) {
@@ -168,8 +167,6 @@ public abstract class DSpaceResource<T extends RestModel> extends HALResource {
} }
} }
else if (RestModel.class.isAssignableFrom(readMethod.getReturnType())) { else if (RestModel.class.isAssignableFrom(readMethod.getReturnType())) {
Link linkToSubResource = utils.linkToSubResource(data, name);
this.add(linkToSubResource);
RestModel linkedObject = (RestModel) readMethod.invoke(data); RestModel linkedObject = (RestModel) readMethod.invoke(data);
if (linkedObject != null) { if (linkedObject != null) {
embedded.put(name, embedded.put(name,
@@ -188,15 +185,9 @@ public abstract class DSpaceResource<T extends RestModel> extends HALResource {
| InvocationTargetException e) { | InvocationTargetException e) {
throw new RuntimeException(e.getMessage(), 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() { public Map<String, Object> getEmbedded() {
return embedded; return embedded;
} }