#833 - Migrate packages.
Changed the package structure to better reflect the different modules of the library. All client related code now lives in the client package, server related APIs in server with their respective WebMVC and WebFlux implementations in sub-packages. Added migration script to allow users to easily migrate to the new structure and added reference documentation section on the migration. Traversons built in defaulting to HAL HttpMessageConverters and LinkDiscoverer implementation now caused a cyclic relationship between the client and mediatype.hal packages. This has be fixed by looking up the defaults via a SpringFactories interface.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.server;
|
||||
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Identifiable;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.plugin.core.Plugin;
|
||||
|
||||
/**
|
||||
* Accessor to links pointing to controllers backing an entity type. The {@link IllegalArgumentException} potentially
|
||||
* thrown by the declared methods will only appear if the {@link #supports(Class)} method has returned {@literal false}
|
||||
* and the method has been invoked anyway, i.e. if {@link #supports(Class)} returns {@literal true} it's safe to invoke
|
||||
* the interface methods an the exception will never be thrown.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface EntityLinks extends Plugin<Class<?>> {
|
||||
|
||||
/**
|
||||
* Returns a {@link LinkBuilder} able to create links to the controller managing the given entity type. Expects a
|
||||
* controller being mapped to a fully expanded URI template (i.e. not path variables being used).
|
||||
*
|
||||
* @param type the entity type to point to, must not be {@literal null}.
|
||||
* @return the {@link LinkBuilder} pointing to the collection resource. Will never be {@literal null}.
|
||||
* @throws IllegalArgumentException in case the given type is unknown the entity links infrastructure.
|
||||
*/
|
||||
LinkBuilder linkFor(Class<?> type);
|
||||
|
||||
/**
|
||||
* Returns a {@link LinkBuilder} able to create links to the controller managing the given entity type, unfolding the
|
||||
* given parameters into the URI template the backing controller is mapped to.
|
||||
*
|
||||
* @param type the entity type to point to, must not be {@literal null}.
|
||||
* @return the {@link LinkBuilder} pointing to the collection resource.
|
||||
* @throws IllegalArgumentException in case the given type is unknown the entity links infrastructure.
|
||||
*/
|
||||
LinkBuilder linkFor(Class<?> type, Object... parameters);
|
||||
|
||||
/**
|
||||
* Returns a {@link LinkBuilder} able to create links to the controller managing the given entity type and id.
|
||||
* Implementations will know about the URI structure being used to expose single-resource URIs.
|
||||
*
|
||||
* @param type the entity type to point to, must not be {@literal null}.
|
||||
* @param id the id of the object of the handed type, {@link Identifiable}s will be unwrapped.
|
||||
* @return the {@link LinkBuilder} pointing to the single resource identified by the given type and id. Will never be
|
||||
* {@literal null}.
|
||||
* @throws IllegalArgumentException in case the given type is unknown the entity links infrastructure.
|
||||
*/
|
||||
LinkBuilder linkForSingleResource(Class<?> type, Object id);
|
||||
|
||||
/**
|
||||
* Returns a {@link LinkBuilder} able to create links to the controller managing the given entity.
|
||||
*
|
||||
* @see #linkForSingleResource(Class, Object)
|
||||
* @param entity the entity type to point to, must not be {@literal null}.
|
||||
* @return the {@link LinkBuilder} pointing the given entity. Will never be {@literal null}.
|
||||
* @throws IllegalArgumentException in case the type of the given entity is unknown the entity links infrastructure.
|
||||
*/
|
||||
LinkBuilder linkForSingleResource(Identifiable<?> entity);
|
||||
|
||||
/**
|
||||
* Creates a {@link Link} pointing to the collection resource of the given type. The relation type of the link will be
|
||||
* determined by the implementation class and should be defaulted to {@link IanaLinkRelations#SELF}.
|
||||
*
|
||||
* @param type the entity type to point to, must not be {@literal null}.
|
||||
* @return the {@link Link} pointing to the collection resource exposed for the given entity. Will never be
|
||||
* {@literal null}.
|
||||
* @throws IllegalArgumentException in case the given type is unknown the entity links infrastructure.
|
||||
*/
|
||||
Link linkToCollectionResource(Class<?> type);
|
||||
|
||||
/**
|
||||
* Creates a {@link Link} pointing to single resource backing the given entity type and id. The relation type of the
|
||||
* link will be determined by the implementation class and should be defaulted to {@link IanaLinkRelations#SELF}.
|
||||
*
|
||||
* @param type the entity type to point to, must not be {@literal null}.
|
||||
* @param id the identifier of the entity of the given type
|
||||
* @return the {@link Link} pointing to the resource exposed for the entity with the given type and id. Will never be
|
||||
* {@literal null}.
|
||||
* @throws IllegalArgumentException in case the given type is unknown the entity links infrastructure.
|
||||
*/
|
||||
Link linkToSingleResource(Class<?> type, Object id);
|
||||
|
||||
/**
|
||||
* Creates a {@link Link} pointing to single resource backing the given entity. The relation type of the link will be
|
||||
* determined by the implementation class and should be defaulted to {@link IanaLinkRelations#SELF}.
|
||||
*
|
||||
* @param entity the entity type to point to, must not be {@literal null}.
|
||||
* @return the {@link Link} pointing to the resource exposed for the given entity. Will never be {@literal null}.
|
||||
* @throws IllegalArgumentException in case the type of the given entity is unknown the entity links infrastructure.
|
||||
*/
|
||||
Link linkToSingleResource(Identifiable<?> entity);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2012 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.server;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.hateoas.server.core.ControllerEntityLinks;
|
||||
|
||||
/**
|
||||
* Annotation to demarcate controllers that expose URI templates of a structure according to
|
||||
* {@link ControllerEntityLinks}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface ExposesResourceFor {
|
||||
|
||||
/**
|
||||
* The domain type the controller exposes resources for.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> value();
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2012 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.server;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Identifiable;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
|
||||
/**
|
||||
* Builder to ease building {@link Link} instances.
|
||||
*
|
||||
* @author Ricardo Gladwell
|
||||
*/
|
||||
public interface LinkBuilder {
|
||||
|
||||
/**
|
||||
* Adds the given object's {@link String} representation as sub-resource to the current URI. Will unwrap
|
||||
* {@link Identifiable}s to their id value (see {@link Identifiable#getId()}).
|
||||
*
|
||||
* @param object
|
||||
* @return
|
||||
*/
|
||||
LinkBuilder slash(Object object);
|
||||
|
||||
/**
|
||||
* Adds the given {@link Identifiable}'s id as sub-resource. Will simply return the {@link LinkBuilder} as is if the
|
||||
* given entity is {@literal null}.
|
||||
*
|
||||
* @param identifiable
|
||||
* @return
|
||||
*/
|
||||
LinkBuilder slash(Identifiable<?> identifiable);
|
||||
|
||||
/**
|
||||
* Creates a URI of the link built by the current builder instance.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
URI toUri();
|
||||
|
||||
/**
|
||||
* Creates the {@link Link} built by the current builder instance with the given link relation.
|
||||
*
|
||||
* @param rel must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
default Link withRel(String rel) {
|
||||
return withRel(LinkRelation.of(rel));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link Link} built by the current builder instance with the given {@link LinkRelation}.
|
||||
*
|
||||
* @param rel must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
Link withRel(LinkRelation rel);
|
||||
|
||||
/**
|
||||
* Creates the {@link Link} built by the current builder instance with the default self link relation.
|
||||
*
|
||||
* @see IanaLinkRelations#SELF
|
||||
* @return
|
||||
*/
|
||||
Link withSelfRel();
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.server;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Factory for {@link LinkBuilder} instances.
|
||||
*
|
||||
* @author Ricardo Gladwell
|
||||
* @author Andrew Naydyonock
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface LinkBuilderFactory<T extends LinkBuilder> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link LinkBuilder} with a base of the mapping annotated to the given target class (controller,
|
||||
* service, etc.).
|
||||
*
|
||||
* @param target must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
T linkTo(Class<?> target);
|
||||
|
||||
/**
|
||||
* Creates a new {@link LinkBuilder} with a base of the mapping annotated to the given target class (controller,
|
||||
* service, etc.). The additional parameters are used to fill up potentially available path variables in the class
|
||||
* scope request mapping.
|
||||
*
|
||||
* @param target must not be {@literal null}.
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
T linkTo(Class<?> target, Object... parameters);
|
||||
|
||||
/**
|
||||
* Creates a new {@link LinkBuilder} with a base of the mapping annotated to the given target class (controller,
|
||||
* service, etc.). Parameter map is used to fill up potentially available path variables in the class scope request
|
||||
* mapping.
|
||||
*
|
||||
* @param target must not be {@literal null}.
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
T linkTo(Class<?> target, Map<String, ?> parameters);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.server;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.hateoas.server.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
|
||||
|
||||
/**
|
||||
* Extension of {@link LinkBuilderFactory} for implementations that also support creating {@link LinkBuilder}s by
|
||||
* pointing to a method.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface MethodLinkBuilderFactory<T extends LinkBuilder> extends LinkBuilderFactory<T> {
|
||||
|
||||
/**
|
||||
* Returns a {@link LinkBuilder} pointing to the URI mapped to the given {@link Method} and expanding this mapping
|
||||
* using the given parameters.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param parameters
|
||||
* @return
|
||||
*/
|
||||
T linkTo(Method method, Object... parameters);
|
||||
|
||||
/**
|
||||
* Returns a {@link LinkBuilder} pointing to the URI mapped to the given {@link Method} assuming it was invoked on an
|
||||
* object of the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param method must not be {@literal null}.
|
||||
* @param parameters
|
||||
* @return
|
||||
*/
|
||||
T linkTo(Class<?> type, Method method, Object... parameters);
|
||||
|
||||
/**
|
||||
* Returns a {@link LinkBuilder} pointing to the URI mapped to the method the result is handed into this method. Use
|
||||
* {@link DummyInvocationUtils#methodOn(Class, Object...)} to obtain a dummy instance of a controller to record a
|
||||
* dummy method invocation on. See {@link WebMvcLinkBuilder#linkTo(Object)} for an example.
|
||||
*
|
||||
* @see WebMvcLinkBuilder#linkTo(Object)
|
||||
* @param methodInvocationResult must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
T linkTo(Object methodInvocationResult);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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.server;
|
||||
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.plugin.core.Plugin;
|
||||
|
||||
/**
|
||||
* API to provide relation types for collections and items of the given type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface RelProvider extends Plugin<Class<?>> {
|
||||
|
||||
/**
|
||||
* Returns the relation type to be used to point to an item resource of the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
LinkRelation getItemResourceRelFor(Class<?> type);
|
||||
|
||||
/**
|
||||
* Returns the relation type to be used to point to a collection resource of the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
LinkRelation getCollectionResourceRelFor(Class<?> type);
|
||||
}
|
||||
55
src/main/java/org/springframework/hateoas/server/ResourceAssembler.java
Executable file
55
src/main/java/org/springframework/hateoas/server/ResourceAssembler.java
Executable file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2012-2018 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.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
/**
|
||||
* Interface for components that convert a domain type into a {@link ResourceSupport}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public interface ResourceAssembler<T, D extends ResourceSupport> {
|
||||
|
||||
/**
|
||||
* Converts the given entity into a {@code D}, which extends {@link ResourceSupport}.
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
D toResource(T entity);
|
||||
|
||||
/**
|
||||
* Converts an {@link Iterable} or {@code T}s into an {@link Iterable} of {@link ResourceSupport} and wraps them in a
|
||||
* {@link Resources} instance.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @return {@link Resources} containing {@code D}.
|
||||
*/
|
||||
default Resources<D> toResources(Iterable<? extends T> entities) {
|
||||
|
||||
List<D> resources = new ArrayList<>();
|
||||
for (T entity : entities) {
|
||||
resources.add(toResource(entity));
|
||||
}
|
||||
return new Resources<>(resources);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2012 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.server;
|
||||
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
/**
|
||||
* SPI interface to allow components to process the {@link ResourceSupport} instances returned from Spring MVC
|
||||
* controllers.
|
||||
*
|
||||
* @see Resource
|
||||
* @see Resources
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface ResourceProcessor<T extends ResourceSupport> {
|
||||
|
||||
/**
|
||||
* Processes the given resource, add links, alter the domain data etc.
|
||||
*
|
||||
* @param resource
|
||||
* @return the processed resource
|
||||
*/
|
||||
T process(T resource);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2018 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.server;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ResourceAssembler} based purely on the domain type, using {@code Resource<T>} as the enclosing
|
||||
* "resource" type.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface SimpleResourceAssembler<T> extends ResourceAssembler<T, Resource<T>> {
|
||||
|
||||
/**
|
||||
* Converts the given entity into a {@link Resource}.
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
default Resource<T> toResource(T entity) {
|
||||
|
||||
Resource<T> resource = new Resource<>(entity);
|
||||
addLinks(resource);
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define links to add to every individual {@link Resource}.
|
||||
*
|
||||
* @param resource
|
||||
*/
|
||||
void addLinks(Resource<T> resource);
|
||||
|
||||
/**
|
||||
* Converts all given entities into resources and wraps the collection as a resource as well.
|
||||
*
|
||||
* @see #toResource(Object)
|
||||
* @param entities must not be {@literal null}.
|
||||
* @return {@link Resources} containing {@link Resource} of {@code T}.
|
||||
*/
|
||||
@Override
|
||||
default Resources<Resource<T>> toResources(Iterable<? extends T> entities) {
|
||||
|
||||
Assert.notNull(entities, "entities must not be null!");
|
||||
List<Resource<T>> resourceList = new ArrayList<>();
|
||||
|
||||
for (T entity : entities) {
|
||||
resourceList.add(toResource(entity));
|
||||
}
|
||||
|
||||
Resources<Resource<T>> resources = new Resources<>(resourceList);
|
||||
addLinks(resources);
|
||||
return resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define links to add to the {@link Resources} collection.
|
||||
*
|
||||
* @param resources
|
||||
*/
|
||||
void addLinks(Resources<Resource<T>> resources);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2012 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.server.core;
|
||||
|
||||
import org.springframework.hateoas.Identifiable;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.server.EntityLinks;
|
||||
import org.springframework.hateoas.server.LinkBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation base class to delegate the higher level methods of {@link EntityLinks} by delegating to the more fine
|
||||
* grained ones to reduce the implementation effort for actual implementation classes.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public abstract class AbstractEntityLinks implements EntityLinks {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EntityLinks#getLinkToSingleResource(org.springframework.hateoas.Identifiable)
|
||||
*/
|
||||
@Override
|
||||
public Link linkToSingleResource(Identifiable<?> entity) {
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
return linkToSingleResource(entity.getClass(), entity.getId());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EntityLinks#linkForSingleResource(java.lang.Class, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public LinkBuilder linkForSingleResource(Class<?> type, Object id) {
|
||||
return linkFor(type).slash(id);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EntityLinks#linkForSingleResource(org.springframework.hateoas.Identifiable)
|
||||
*/
|
||||
@Override
|
||||
public LinkBuilder linkForSingleResource(Identifiable<?> entity) {
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
return linkForSingleResource(entity.getClass(), entity.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2012 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.server.core;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simply helper to reference a dedicated attribute of an {@link Annotation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AnnotationAttribute {
|
||||
|
||||
private final Class<? extends Annotation> annotationType;
|
||||
private final String attributeName;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationAttribute} to the {@code value} attribute of the given {@link Annotation} type.
|
||||
*
|
||||
* @param annotationType must not be {@literal null}.
|
||||
*/
|
||||
public AnnotationAttribute(Class<? extends Annotation> annotationType) {
|
||||
this(annotationType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationAttribute} for the given {@link Annotation} type and annotation attribute name.
|
||||
*
|
||||
* @param annotationType must not be {@literal null}.
|
||||
* @param attributeName can be {@literal null}, defaults to {@code value}.
|
||||
*/
|
||||
public AnnotationAttribute(Class<? extends Annotation> annotationType, String attributeName) {
|
||||
|
||||
Assert.notNull(annotationType, "AnnotationType must not be null!");
|
||||
|
||||
this.annotationType = annotationType;
|
||||
this.attributeName = attributeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the annotation type.
|
||||
*
|
||||
* @return the annotationType
|
||||
*/
|
||||
public Class<? extends Annotation> getAnnotationType() {
|
||||
return annotationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the {@link Annotation} attribute's value from the given {@link MethodParameter}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public String getValueFrom(MethodParameter parameter) {
|
||||
|
||||
Assert.notNull(parameter, "MethodParameter must not be null!");
|
||||
Annotation annotation = parameter.getParameterAnnotation(annotationType);
|
||||
return annotation == null ? null : getValueFrom(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the {@link Annotation} attribute's value from the given {@link AnnotatedElement}.
|
||||
*
|
||||
* @param annotatedElement must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public String getValueFrom(AnnotatedElement annotatedElement) {
|
||||
|
||||
Assert.notNull(annotatedElement, "Annotated element must not be null!");
|
||||
Annotation annotation = annotatedElement.getAnnotation(annotationType);
|
||||
return annotation == null ? null : getValueFrom(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Annotation} attribute's value from the given {@link Annotation}.
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public String getValueFrom(Annotation annotation) {
|
||||
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
return (String) (attributeName == null ? AnnotationUtils.getValue(annotation) : AnnotationUtils.getValue(
|
||||
annotation, attributeName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.server.core;
|
||||
|
||||
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
|
||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* {@link MappingDiscoverer} implementation that inspects mappings from a particular annotation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class AnnotationMappingDiscoverer implements MappingDiscoverer {
|
||||
|
||||
private static final Pattern MULTIPLE_SLASHES = Pattern.compile("/{2,}");
|
||||
|
||||
private final Class<? extends Annotation> annotationType;
|
||||
private final String mappingAttributeName;
|
||||
|
||||
/**
|
||||
* Creates an {@link AnnotationMappingDiscoverer} for the given annotation type. Will lookup the {@code value}
|
||||
* attribute by default.
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
*/
|
||||
public AnnotationMappingDiscoverer(Class<? extends Annotation> annotation) {
|
||||
this(annotation, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AnnotationMappingDiscoverer} for the given annotation type and attribute name.
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @param mappingAttributeName if {@literal null}, it defaults to {@code value}.
|
||||
*/
|
||||
public AnnotationMappingDiscoverer(Class<? extends Annotation> annotation, String mappingAttributeName) {
|
||||
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
|
||||
this.annotationType = annotation;
|
||||
this.mappingAttributeName = mappingAttributeName;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getMapping(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
String[] mapping = getMappingFrom(findMergedAnnotation(type, annotationType));
|
||||
|
||||
return mapping.length == 0 ? null : mapping[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
public String getMapping(Method method) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
return getMapping(method.getDeclaringClass(), method);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
public String getMapping(Class<?> type, Method method) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
|
||||
String[] mapping = getMappingFrom(findMergedAnnotation(method, annotationType));
|
||||
String typeMapping = getMapping(type);
|
||||
|
||||
if (mapping == null || mapping.length == 0) {
|
||||
return typeMapping;
|
||||
}
|
||||
|
||||
return typeMapping == null || "/".equals(typeMapping) ? mapping[0] : join(typeMapping, mapping[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract {@link org.springframework.web.bind.annotation.RequestMapping}'s list of {@link RequestMethod}s into an
|
||||
* array of {@link String}s.
|
||||
*
|
||||
* @param type
|
||||
* @param method
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Collection<HttpMethod> getRequestMethod(Class<?> type, Method method) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
|
||||
Annotation mergedAnnotation = findMergedAnnotation(method, annotationType);
|
||||
Object value = getValue(mergedAnnotation, "method");
|
||||
|
||||
RequestMethod[] requestMethods = (RequestMethod[]) value;
|
||||
|
||||
List<HttpMethod> requestMethodNames = new ArrayList<>();
|
||||
|
||||
for (RequestMethod requestMethod : requestMethods) {
|
||||
requestMethodNames.add(HttpMethod.valueOf(requestMethod.toString()));
|
||||
}
|
||||
|
||||
return requestMethodNames;
|
||||
}
|
||||
|
||||
private String[] getMappingFrom(Annotation annotation) {
|
||||
|
||||
if (annotation == null) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
Object value = mappingAttributeName == null ? getValue(annotation) : getValue(annotation, mappingAttributeName);
|
||||
|
||||
if (value instanceof String) {
|
||||
return new String[] { (String) value };
|
||||
} else if (value instanceof String[]) {
|
||||
return (String[]) value;
|
||||
} else if (value == null) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
throw new IllegalStateException(String.format(
|
||||
"Unsupported type for the mapping attribute! Support String and String[] but got %s!", value.getClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins the given mappings making sure exactly one slash.
|
||||
*
|
||||
* @param typeMapping must not be {@literal null} or empty.
|
||||
* @param mapping must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
private static String join(String typeMapping, String mapping) {
|
||||
return MULTIPLE_SLASHES.matcher(typeMapping.concat("/").concat(mapping)).replaceAll("/");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2013-2017 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.server.core;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.server.RelProvider;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Alexander Baetz
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class AnnotationRelProvider implements RelProvider, Ordered {
|
||||
|
||||
private final Map<Class<?>, Relation> annotationCache = new HashMap<>();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public LinkRelation getCollectionResourceRelFor(Class<?> type) {
|
||||
|
||||
Relation annotation = lookupAnnotation(type);
|
||||
|
||||
if (annotation == null || Relation.NO_RELATION.equals(annotation.collectionRelation())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return LinkRelation.of(annotation.collectionRelation());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public LinkRelation getItemResourceRelFor(Class<?> type) {
|
||||
|
||||
Relation annotation = lookupAnnotation(type);
|
||||
|
||||
if (annotation == null || Relation.NO_RELATION.equals(annotation.value())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return LinkRelation.of(annotation.value());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.Ordered#getOrder()
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> delimiter) {
|
||||
return lookupAnnotation(delimiter) != null;
|
||||
}
|
||||
|
||||
private Relation lookupAnnotation(Class<?> type) {
|
||||
return annotationCache.computeIfAbsent(type, key -> AnnotationUtils.getAnnotation(key, Relation.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.core;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Caching adapter of {@link MappingDiscoverer}.
|
||||
*
|
||||
* @author Michal Stochmialek
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
public class CachingMappingDiscoverer implements MappingDiscoverer {
|
||||
|
||||
private static final Map<String, String> MAPPINGS = new ConcurrentReferenceHashMap<>();
|
||||
private static final Map<String, Collection<HttpMethod>> METHODS = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
private final MappingDiscoverer delegate;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getMapping(Class<?> type) {
|
||||
|
||||
String key = key(type, null);
|
||||
|
||||
return MAPPINGS.computeIfAbsent(key, __ -> delegate.getMapping(type));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
public String getMapping(Method method) {
|
||||
|
||||
String key = key(method.getDeclaringClass(), method);
|
||||
|
||||
return MAPPINGS.computeIfAbsent(key, __ -> delegate.getMapping(method));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.MappingDiscoverer#getMapping(java.lang.Class, java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
public String getMapping(Class<?> type, Method method) {
|
||||
|
||||
String key = key(type, method);
|
||||
|
||||
return MAPPINGS.computeIfAbsent(key, __ -> delegate.getMapping(type, method));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.MappingDiscoverer#getRequestMethod(java.lang.Class, java.lang.reflect.Method)
|
||||
*/
|
||||
@Override
|
||||
public Collection<HttpMethod> getRequestMethod(Class<?> type, Method method) {
|
||||
return METHODS.computeIfAbsent(key(type, method), __ -> delegate.getRequestMethod(type, method));
|
||||
}
|
||||
|
||||
private static String key(Class<?> type, Method method) {
|
||||
|
||||
StringBuilder builder = new StringBuilder(type.getName());
|
||||
|
||||
if (method == null) {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
builder.append(method.getName());
|
||||
builder.append(StringUtils.arrayToCommaDelimitedString(method.getParameterTypes()));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.server.core;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.server.EntityLinks;
|
||||
import org.springframework.hateoas.server.ExposesResourceFor;
|
||||
import org.springframework.hateoas.server.LinkBuilder;
|
||||
import org.springframework.hateoas.server.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<>();
|
||||
|
||||
controllerTypes.forEach(this::registerControllerClass);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2012 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.server.core;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.hateoas.server.ExposesResourceFor;
|
||||
import org.springframework.hateoas.server.LinkBuilder;
|
||||
import org.springframework.hateoas.server.LinkBuilderFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link FactoryBean} implementation to create {@link ControllerEntityLinks} instances looking up controller classes
|
||||
* from an {@link ApplicationContext}. The controller types are identified by the annotation type configured.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ControllerEntityLinksFactoryBean extends AbstractFactoryBean<ControllerEntityLinks> implements
|
||||
ApplicationContextAware {
|
||||
|
||||
private Class<? extends Annotation> annotation;
|
||||
private LinkBuilderFactory<? extends LinkBuilder> linkBuilderFactory;
|
||||
private ApplicationContext context;
|
||||
|
||||
/**
|
||||
* Configures the annotation type to inspect the {@link ApplicationContext} for beans that carry the given annotation.
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
*/
|
||||
public void setAnnotation(Class<? extends Annotation> annotation) {
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
this.annotation = annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link LinkBuilderFactory} to be used to create {@link LinkBuilder} instances.
|
||||
*
|
||||
* @param linkBuilderFactory the linkBuilderFactory to set
|
||||
*/
|
||||
public void setLinkBuilderFactory(LinkBuilderFactory<? extends LinkBuilder> linkBuilderFactory) {
|
||||
this.linkBuilderFactory = linkBuilderFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.config.AbstractFactoryBean#getObjectType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return ControllerEntityLinks.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance()
|
||||
*/
|
||||
@Override
|
||||
protected ControllerEntityLinks createInstance() {
|
||||
|
||||
Collection<Class<?>> controllerTypes = new HashSet<>();
|
||||
|
||||
for (Class<?> controllerType : getBeanTypesWithAnnotation(annotation)) {
|
||||
if (AnnotationUtils.findAnnotation(controllerType, ExposesResourceFor.class) != null) {
|
||||
controllerTypes.add(controllerType);
|
||||
}
|
||||
}
|
||||
|
||||
return new ControllerEntityLinks(controllerTypes, linkBuilderFactory);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.config.AbstractFactoryBean#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
|
||||
Assert.state(annotation != null, "Annotation type must be configured!");
|
||||
Assert.state(linkBuilderFactory != null, "LinkBuilderFactory must be configured!");
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
|
||||
private Iterable<Class<?>> getBeanTypesWithAnnotation(Class<? extends Annotation> type) {
|
||||
|
||||
Set<Class<?>> annotatedTypes = new HashSet<>();
|
||||
|
||||
for (String beanName : context.getBeanDefinitionNames()) {
|
||||
|
||||
Annotation annotation = context.findAnnotationOnBean(beanName, type);
|
||||
if (annotation != null) {
|
||||
annotatedTypes.add(context.getType(beanName));
|
||||
}
|
||||
}
|
||||
|
||||
return annotatedTypes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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.server.core;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.server.RelProvider;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link RelProvider} to simply use the uncapitalized version of the given type's name as
|
||||
* single resource rel as well as an appended {@code List} for the collection resource rel.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class DefaultRelProvider implements RelProvider, Ordered {
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> delimiter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public LinkRelation getCollectionResourceRelFor(Class<?> type) {
|
||||
return LinkRelation.of(StringUtils.uncapitalize(type.getSimpleName()) + "List");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public LinkRelation getItemResourceRelFor(Class<?> type) {
|
||||
return LinkRelation.of(StringUtils.uncapitalize(type.getSimpleName()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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.server.core;
|
||||
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.server.EntityLinks;
|
||||
import org.springframework.hateoas.server.LinkBuilder;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link EntityLinks} implementation that delegates to the {@link EntityLinks} instances registered in the
|
||||
* {@link PluginRegistry} given on instance creation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DelegatingEntityLinks extends AbstractEntityLinks {
|
||||
|
||||
private final PluginRegistry<EntityLinks, Class<?>> delegates;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DelegatingEntityLinks} using the given {@link PluginRegistry}.
|
||||
*
|
||||
* @param plugins must not be {@literal null}.
|
||||
*/
|
||||
public DelegatingEntityLinks(PluginRegistry<EntityLinks, Class<?>> plugins) {
|
||||
|
||||
Assert.notNull(plugins, "PluginRegistry must not be null!");
|
||||
this.delegates = plugins;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EntityLinks#linkFor(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public LinkBuilder linkFor(Class<?> type) {
|
||||
return getPluginFor(type).linkFor(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EntityLinks#linkFor(java.lang.Class, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public LinkBuilder linkFor(Class<?> type, Object... parameters) {
|
||||
return getPluginFor(type).linkFor(type, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EntityLinks#getLinkToCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Link linkToCollectionResource(Class<?> type) {
|
||||
return getPluginFor(type).linkToCollectionResource(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EntityLinks#getLinkToSingleResource(java.lang.Class, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Link linkToSingleResource(Class<?> type, Object id) {
|
||||
return getPluginFor(type).linkToSingleResource(type, id);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> delimiter) {
|
||||
return delegates.hasPluginFor(delimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin for the given type or throws an {@link IllegalArgumentException} if no delegate
|
||||
* {@link EntityLinks} can be found.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private EntityLinks getPluginFor(Class<?> type) {
|
||||
|
||||
return delegates.getPluginFor(type) //
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
String.format("Cannot determine link for %s! No EntityLinks instance found supporting the domain type!",
|
||||
type.getName())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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.server.core;
|
||||
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.server.RelProvider;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DelegatingRelProvider implements RelProvider {
|
||||
|
||||
private final PluginRegistry<RelProvider, Class<?>> providers;
|
||||
|
||||
public DelegatingRelProvider(PluginRegistry<RelProvider, Class<?>> providers) {
|
||||
|
||||
Assert.notNull(providers, "RelProviders must not be null!");
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public LinkRelation getItemResourceRelFor(Class<?> type) {
|
||||
return providers.getRequiredPluginFor(type).getItemResourceRelFor(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public LinkRelation getCollectionResourceRelFor(java.lang.Class<?> type) {
|
||||
return providers.getRequiredPluginFor(type).getCollectionResourceRelFor(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(java.lang.Class<?> delimiter) {
|
||||
return providers.hasPluginFor(delimiter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.server.core;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.target.EmptyTargetSource;
|
||||
import org.springframework.cglib.proxy.Callback;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
import org.springframework.cglib.proxy.Factory;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
import org.springframework.objenesis.ObjenesisStd;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Utility methods to capture dummy method invocations.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DummyInvocationUtils {
|
||||
|
||||
private static final ObjenesisStd OBJENESIS = new ObjenesisStd();
|
||||
private static final Map<Class<?>, Class<?>> CLASS_CACHE = new ConcurrentReferenceHashMap<>(16,
|
||||
ReferenceType.WEAK);
|
||||
|
||||
/**
|
||||
* Method interceptor that records the last method invocation and creates a proxy for the return value that exposes
|
||||
* the method invocation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class InvocationRecordingMethodInterceptor
|
||||
implements MethodInterceptor, LastInvocationAware, org.springframework.cglib.proxy.MethodInterceptor {
|
||||
|
||||
private static final Method GET_INVOCATIONS;
|
||||
private static final Method GET_OBJECT_PARAMETERS;
|
||||
|
||||
private final Class<?> targetType;
|
||||
private final Object[] objectParameters;
|
||||
private MethodInvocation invocation;
|
||||
|
||||
static {
|
||||
GET_INVOCATIONS = ReflectionUtils.findMethod(LastInvocationAware.class, "getLastInvocation");
|
||||
GET_OBJECT_PARAMETERS = ReflectionUtils.findMethod(LastInvocationAware.class, "getObjectParameters");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link InvocationRecordingMethodInterceptor} carrying the given parameters forward that might be
|
||||
* needed to populate the class level mapping.
|
||||
*
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @param parameters must not be {@literal null}.
|
||||
*/
|
||||
InvocationRecordingMethodInterceptor(Class<?> targetType, Object... parameters) {
|
||||
|
||||
Assert.notNull(targetType, "Target type must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
this.targetType = targetType;
|
||||
this.objectParameters = parameters.clone();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], org.springframework.cglib.proxy.MethodProxy)
|
||||
*/
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) {
|
||||
|
||||
if (GET_INVOCATIONS.equals(method)) {
|
||||
return getLastInvocation();
|
||||
} else if (GET_OBJECT_PARAMETERS.equals(method)) {
|
||||
return getObjectParameters();
|
||||
} else if (Object.class.equals(method.getDeclaringClass())) {
|
||||
return ReflectionUtils.invokeMethod(method, obj, args);
|
||||
}
|
||||
|
||||
this.invocation = new SimpleMethodInvocation(targetType, method, args);
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
return returnType.cast(getProxyWithInterceptor(returnType, this, obj.getClass().getClassLoader()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
|
||||
*/
|
||||
@Override
|
||||
public Object invoke(org.aopalliance.intercept.MethodInvocation invocation) {
|
||||
return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.DummyInvocationUtils.LastInvocationAware#getLastInvocation()
|
||||
*/
|
||||
@Override
|
||||
public MethodInvocation getLastInvocation() {
|
||||
return invocation;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.DummyInvocationUtils.LastInvocationAware#getObjectParameters()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Object> getObjectParameters() {
|
||||
return Arrays.asList(objectParameters).iterator();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a proxy of the given type, backed by an {@link EmptyTargetSource} to simply drop method invocations but
|
||||
* equips it with an {@link InvocationRecordingMethodInterceptor}. The interceptor records the last invocation and
|
||||
* returns a proxy of the return type that also implements {@link LastInvocationAware} so that the last method
|
||||
* invocation can be inspected. Parameters passed to the subsequent method invocation are generally neglected except
|
||||
* the ones that might be mapped into the URI translation eventually, e.g. {@link org.springframework.web.bind.annotation.PathVariable} in the case of Spring
|
||||
* MVC. Note, that the return types of the methods have to be capable to be proxied.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param parameters parameters to extend template variables in the type level mapping.
|
||||
* @return
|
||||
*/
|
||||
public static <T> T methodOn(Class<T> type, Object... parameters) {
|
||||
|
||||
Assert.notNull(type, "Given type must not be null!");
|
||||
|
||||
InvocationRecordingMethodInterceptor interceptor = new InvocationRecordingMethodInterceptor(type, parameters);
|
||||
return getProxyWithInterceptor(type, interceptor, type.getClassLoader());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T getProxyWithInterceptor(Class<?> type, InvocationRecordingMethodInterceptor interceptor,
|
||||
ClassLoader classLoader) {
|
||||
|
||||
if (type.isInterface()) {
|
||||
|
||||
ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE);
|
||||
factory.addInterface(type);
|
||||
factory.addInterface(LastInvocationAware.class);
|
||||
factory.addAdvice(interceptor);
|
||||
|
||||
return (T) factory.getProxy();
|
||||
}
|
||||
|
||||
Factory factory = (Factory) OBJENESIS.newInstance(getOrCreateEnhancedClass(type, classLoader));
|
||||
factory.setCallbacks(new Callback[] { interceptor });
|
||||
return (T) factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the already created proxy class for the given source type or creates a new one.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param classLoader must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static Class<?> getOrCreateEnhancedClass(Class<?> type, ClassLoader classLoader) {
|
||||
|
||||
Assert.notNull(type, "Source type must not be null!");
|
||||
Assert.notNull(classLoader, "ClassLoader must not be null!");
|
||||
|
||||
return CLASS_CACHE.computeIfAbsent(type, key -> {
|
||||
|
||||
Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(key);
|
||||
enhancer.setInterfaces(new Class<?>[] { LastInvocationAware.class });
|
||||
enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class);
|
||||
enhancer.setClassLoader(classLoader);
|
||||
|
||||
return enhancer.createClass();
|
||||
});
|
||||
}
|
||||
|
||||
@Value
|
||||
private static class SimpleMethodInvocation implements MethodInvocation {
|
||||
|
||||
@NonNull Class<?> targetType;
|
||||
@NonNull Method method;
|
||||
@NonNull Object[] arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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.server.core;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.Resource;
|
||||
|
||||
/**
|
||||
* A wrapper to handle values to be embedded into a {@link Resource}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface EmbeddedWrapper {
|
||||
|
||||
/**
|
||||
* Returns the rel to be used when embedding. If this returns {@literal null}, the rel will be calculated based on the
|
||||
* type returned by {@link #getRelTargetType()}. A wrapper returning {@literal null} for both {@link #getRel()} and
|
||||
* {@link #getRelTargetType()} is considered invalid.
|
||||
*
|
||||
* @return
|
||||
* @see #getRelTargetType()
|
||||
*/
|
||||
Optional<LinkRelation> getRel();
|
||||
|
||||
/**
|
||||
* Returns whether the wrapper has the given rel.
|
||||
*
|
||||
* @param rel can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean hasRel(LinkRelation rel);
|
||||
|
||||
/**
|
||||
* Returns whether the wrapper is a collection value.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean isCollectionValue();
|
||||
|
||||
/**
|
||||
* Returns the actual value to embed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Object getValue();
|
||||
|
||||
/**
|
||||
* Returns the type to be used to calculate a type based rel. Can return {@literal null} in case an explicit rel is
|
||||
* returned in {@link #getRel()}. A wrapper returning {@literal null} for both {@link #getRel()} and
|
||||
* {@link #getRelTargetType()} is considered invalid.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> getRelTargetType();
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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.server.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Interface to mark objects that are aware of the rel they'd like to be exposed under.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class EmbeddedWrappers {
|
||||
|
||||
private final boolean preferCollections;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EmbeddedWrappers}.
|
||||
*
|
||||
* @param preferCollections whether wrappers for single elements should rather treat the value as collection.
|
||||
*/
|
||||
public EmbeddedWrappers(boolean preferCollections) {
|
||||
this.preferCollections = preferCollections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link EmbeddedWrapper} that
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
public EmbeddedWrapper wrap(Object source) {
|
||||
return wrap(source, AbstractEmbeddedWrapper.NO_REL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link EmbeddedWrapper} for an empty {@link Collection} with the given element type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public EmbeddedWrapper emptyCollectionOf(Class<?> type) {
|
||||
return new EmptyCollectionEmbeddedWrapper(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link EmbeddedWrapper} with the given rel.
|
||||
*
|
||||
* @param source can be {@literal null}, will return {@literal null} if so.
|
||||
* @param rel must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public EmbeddedWrapper wrap(Object source, LinkRelation rel) {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (source instanceof EmbeddedWrapper) {
|
||||
return (EmbeddedWrapper) source;
|
||||
}
|
||||
|
||||
if (source instanceof Collection) {
|
||||
return new EmbeddedCollection((Collection<Object>) source, rel);
|
||||
}
|
||||
|
||||
if (preferCollections) {
|
||||
return new EmbeddedCollection(Collections.singleton(source), rel);
|
||||
}
|
||||
|
||||
return new EmbeddedElement(source, rel);
|
||||
}
|
||||
|
||||
private static abstract class AbstractEmbeddedWrapper implements EmbeddedWrapper {
|
||||
|
||||
private static final LinkRelation NO_REL = LinkRelation.of("___norel___");
|
||||
|
||||
private final LinkRelation rel;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractEmbeddedWrapper} with the given rel.
|
||||
*
|
||||
* @param rel must not be {@literal null} or empty.
|
||||
*/
|
||||
public AbstractEmbeddedWrapper(LinkRelation rel) {
|
||||
|
||||
Assert.notNull(rel, "Rel must not be null or empty!");
|
||||
this.rel = rel;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.EmbeddedWrapper#getRel()
|
||||
*/
|
||||
@Override
|
||||
public Optional<LinkRelation> getRel() {
|
||||
|
||||
return Optional.ofNullable(rel) //
|
||||
.filter(it -> !it.equals(NO_REL));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.EmbeddedWrapper#hasRel(org.springframework.hateoas.LinkRelation)
|
||||
*/
|
||||
@Override
|
||||
public boolean hasRel(LinkRelation rel) {
|
||||
return this.rel.isSameAs(rel);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.EmbeddedWrapper#getRelTargetType()
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<?> getRelTargetType() {
|
||||
|
||||
Object peek = peek();
|
||||
|
||||
if (peek == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
peek = peek instanceof Resource ? ((Resource<Object>) peek).getContent() : peek;
|
||||
|
||||
return AopUtils.getTargetClass(peek);
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek into the wrapped element. The object returned is used to determine the actual value type of the wrapper.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract Object peek();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EmbeddedWrapper} for a single element.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class EmbeddedElement extends AbstractEmbeddedWrapper {
|
||||
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EmbeddedElement} for the given value and link relation.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @param relation must not be {@literal null}.
|
||||
*/
|
||||
public EmbeddedElement(Object value, LinkRelation relation) {
|
||||
|
||||
super(relation);
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.EmbeddedWrapper#getValue()
|
||||
*/
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.EmbeddedWrappers.AbstractElementWrapper#peek()
|
||||
*/
|
||||
@Override
|
||||
protected Object peek() {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.EmbeddedWrapper#isCollectionValue()
|
||||
*/
|
||||
@Override
|
||||
public boolean isCollectionValue() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EmbeddedWrapper} for a collection of elements.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class EmbeddedCollection extends AbstractEmbeddedWrapper {
|
||||
|
||||
private final Collection<Object> value;
|
||||
|
||||
/**
|
||||
* @param value must not be {@literal null} or empty.
|
||||
* @param rel must not be {@literal null} or empty.
|
||||
*/
|
||||
public EmbeddedCollection(Collection<Object> value, LinkRelation rel) {
|
||||
|
||||
super(rel);
|
||||
|
||||
Assert.notNull(value, "Collection must not be null!");
|
||||
|
||||
if (AbstractEmbeddedWrapper.NO_REL.equals(rel) && value.isEmpty()) {
|
||||
throw new IllegalArgumentException("Cannot wrap an empty collection with no rel given!");
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.hal.EmbeddedWrapper#getValue()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object> getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.EmbeddedWrappers.AbstractEmbeddedWrapper#peek()
|
||||
*/
|
||||
@Override
|
||||
protected Object peek() {
|
||||
return value.isEmpty() ? null : value.iterator().next();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.EmbeddedWrapper#isCollectionValue()
|
||||
*/
|
||||
@Override
|
||||
public boolean isCollectionValue() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link EmbeddedWrapper} to simulate a {@link Collection} of a given element type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 0.17
|
||||
*/
|
||||
private static class EmptyCollectionEmbeddedWrapper implements EmbeddedWrapper {
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EmptyCollectionEmbeddedWrapper}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public EmptyCollectionEmbeddedWrapper(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Element type must not be null!");
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.EmbeddedWrapper#getRel()
|
||||
*/
|
||||
@Override
|
||||
public Optional<LinkRelation> getRel() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.EmbeddedWrapper#getValue()
|
||||
*/
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.EmbeddedWrapper#getRelTargetType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getRelTargetType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.EmbeddedWrapper#isCollectionValue()
|
||||
*/
|
||||
@Override
|
||||
public boolean isCollectionValue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.EmbeddedWrapper#hasRel(org.springframework.hateoas.LinkRelation)
|
||||
*/
|
||||
@Override
|
||||
public boolean hasRel(LinkRelation rel) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2016 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.server.core;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
/**
|
||||
* Utilities for URI encoding.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
* @since 0.22
|
||||
* @soundtrack Don Philippe - Between Now And Now (Between Now And Now)
|
||||
*/
|
||||
@UtilityClass
|
||||
class EncodingUtils {
|
||||
|
||||
private static final String ENCODING = "UTF-8";
|
||||
|
||||
/**
|
||||
* Encodes the given path value.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static String encodePath(Object source) {
|
||||
|
||||
Assert.notNull(source, "Path value must not be null!");
|
||||
|
||||
try {
|
||||
return UriUtils.encodePath(source.toString(), ENCODING);
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given request parameter value.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static String encodeParameter(Object source) {
|
||||
|
||||
Assert.notNull(source, "Request parameter value must not be null!");
|
||||
|
||||
try {
|
||||
return UriUtils.encodeQueryParam(source.toString(), ENCODING);
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given fragment value.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static String encodeFragment(Object source) {
|
||||
|
||||
Assert.notNull(source, "Fragment value must not be null!");
|
||||
|
||||
try {
|
||||
return UriUtils.encodeFragment(source.toString(), ENCODING);
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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.server.core;
|
||||
|
||||
import org.atteo.evo.inflector.English;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.server.RelProvider;
|
||||
|
||||
/**
|
||||
* {@link RelProvider} implementation using the Evo Inflector implementation of an algorithmic approach to English
|
||||
* plurals.
|
||||
*
|
||||
* @see http://www.csse.monash.edu.au/~damian/papers/HTML/Plurals.html
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class EvoInflectorRelProvider extends DefaultRelProvider {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.DefaultRelProvider#getCollectionResourceRelFor(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public LinkRelation getCollectionResourceRelFor(Class<?> type) {
|
||||
return LinkRelation.of(English.plural(getItemResourceRelFor(type).value()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 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.server.core;
|
||||
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Links;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.server.ResourceProcessor;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Special {@link ResponseEntity} that exposes {@link Link} instances in the contained {@link ResourceSupport} as link
|
||||
* headers instead of in the body. Note, that this class is not intended to be used directly from user code but by
|
||||
* support code that will transparently invoke the header exposure. If you use this class from a controller directly,
|
||||
* the {@link Link}s will not be present in the {@link ResourceSupport} instance anymore when {@link ResourceProcessor}s
|
||||
* kick in.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class HeaderLinksResponseEntity<T extends ResourceSupport> extends ResponseEntity<T> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link HeaderLinksResponseEntity} from the given {@link ResponseEntity}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
*/
|
||||
private HeaderLinksResponseEntity(ResponseEntity<T> entity) {
|
||||
|
||||
super(entity.getBody(), getHeadersWithLinks(entity), entity.getStatusCode());
|
||||
entity.getBody().removeLinks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link HeaderLinksResponseEntity} from the given {@link HttpEntity} by defaulting the status code to
|
||||
* {@link HttpStatus#OK}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
*/
|
||||
private HeaderLinksResponseEntity(HttpEntity<T> entity) {
|
||||
this(ResponseEntity.ok().headers(entity.getHeaders()).body(entity.getBody()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the given {@link HttpEntity} into a {@link HeaderLinksResponseEntity}. Will default the status code to
|
||||
* {@link HttpStatus#OK} if the given value is not a {@link ResponseEntity}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static <S extends ResourceSupport> HeaderLinksResponseEntity<S> wrap(HttpEntity<S> entity) {
|
||||
|
||||
Assert.notNull(entity, "Given HttpEntity must not be null!");
|
||||
|
||||
if (entity instanceof ResponseEntity) {
|
||||
return new HeaderLinksResponseEntity<>((ResponseEntity<S>) entity);
|
||||
} else {
|
||||
return new HeaderLinksResponseEntity<>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the given {@link ResourceSupport} into a {@link HeaderLinksResponseEntity}. Will default the status code to
|
||||
* {@link HttpStatus#OK}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static <S extends ResourceSupport> HeaderLinksResponseEntity<S> wrap(S entity) {
|
||||
|
||||
Assert.notNull(entity, "ResourceSupport must not be null!");
|
||||
|
||||
return new HeaderLinksResponseEntity<>(ResponseEntity.ok(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Link}s contained in the {@link ResourceSupport} of the given {@link ResponseEntity} as
|
||||
* {@link HttpHeaders}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static <T extends ResourceSupport> HttpHeaders getHeadersWithLinks(ResponseEntity<T> entity) {
|
||||
|
||||
Links links = entity.getBody().getLinks();
|
||||
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.putAll(entity.getHeaders());
|
||||
httpHeaders.add("Link", links.toString());
|
||||
|
||||
return httpHeaders;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.core;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* @author Oliver Drotbohm
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public interface LastInvocationAware {
|
||||
|
||||
Iterator<Object> getObjectParameters();
|
||||
|
||||
MethodInvocation getLastInvocation();
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.server.core;
|
||||
|
||||
import static org.springframework.hateoas.server.core.EncodingUtils.*;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Identifiable;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.server.LinkBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Base class to implement {@link LinkBuilder}s based on a Spring MVC {@link UriComponentsBuilder}.
|
||||
*
|
||||
* @author Ricardo Gladwell
|
||||
* @author Oliver Gierke
|
||||
* @author Kamill Sokol
|
||||
* @author Kevin Conaway
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkBuilder {
|
||||
|
||||
private final UriComponentsBuilder builder;
|
||||
private final @Getter List<Affordance> affordances;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LinkBuilderSupport} using the given {@link UriComponents}.
|
||||
*
|
||||
* @param builder must not be {@literal null}.
|
||||
*/
|
||||
protected LinkBuilderSupport(UriComponentsBuilder builder) {
|
||||
this(builder, Collections.emptyList());
|
||||
}
|
||||
|
||||
protected LinkBuilderSupport(UriComponentsBuilder builder, List<Affordance> affordances) {
|
||||
|
||||
Assert.notNull(builder, "UriComponents must not be null!");
|
||||
Assert.notNull(affordances, "Affordances must not be null!");
|
||||
|
||||
this.builder = builder.cloneBuilder();
|
||||
this.affordances = affordances;
|
||||
}
|
||||
|
||||
protected LinkBuilderSupport(UriComponents components, List<Affordance> affordances) {
|
||||
|
||||
String uriString = components.toUriString();
|
||||
UriComponentsBuilder builder = uriString.isEmpty() //
|
||||
? UriComponentsBuilder.fromUri(components.toUri()) //
|
||||
: UriComponentsBuilder.fromUriString(uriString);
|
||||
|
||||
this.builder = builder;
|
||||
this.affordances = affordances;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilder#slash(java.lang.Object)
|
||||
*/
|
||||
public T slash(Object object) {
|
||||
|
||||
object = object instanceof Optional ? ((Optional<?>) object).orElse(null) : object;
|
||||
|
||||
if (object == null) {
|
||||
return getThis();
|
||||
}
|
||||
|
||||
if (object instanceof Identifiable) {
|
||||
return slash((Identifiable<?>) object);
|
||||
}
|
||||
|
||||
String path = object.toString();
|
||||
|
||||
if (path.endsWith("#")) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(path)) {
|
||||
return getThis();
|
||||
}
|
||||
|
||||
path = path.startsWith("/") ? path : "/".concat(path);
|
||||
|
||||
return slash(UriComponentsBuilder.fromUriString(path).build(), false);
|
||||
}
|
||||
|
||||
protected T slash(UriComponents components, boolean encoded) {
|
||||
|
||||
return withFreshBuilder(builder -> {
|
||||
|
||||
for (String pathSegment : components.getPathSegments()) {
|
||||
builder.pathSegment(encoded ? pathSegment : encodePath(pathSegment));
|
||||
}
|
||||
|
||||
String fragment = components.getFragment();
|
||||
|
||||
if (StringUtils.hasText(fragment)) {
|
||||
builder.fragment(encoded ? fragment : encodeFragment(fragment));
|
||||
}
|
||||
|
||||
return createNewInstance(builder.query(components.getQuery()), affordances);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilder#slash(org.springframework.hateoas.Identifiable)
|
||||
*/
|
||||
public T slash(Identifiable<?> identifyable) {
|
||||
|
||||
if (identifyable == null) {
|
||||
return getThis();
|
||||
}
|
||||
|
||||
return slash(identifyable.getId());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilder#toUri()
|
||||
*/
|
||||
public URI toUri() {
|
||||
return builder.build().toUri().normalize();
|
||||
}
|
||||
|
||||
public T addAffordances(Collection<Affordance> affordances) {
|
||||
|
||||
List<Affordance> newAffordances = new ArrayList<>();
|
||||
newAffordances.addAll(this.affordances);
|
||||
newAffordances.addAll(affordances);
|
||||
|
||||
return createNewInstance(builder, newAffordances);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilder#withRel(org.springframework.hateoas.LinkRelation)
|
||||
*/
|
||||
public Link withRel(LinkRelation rel) {
|
||||
|
||||
return new Link(toString(), rel) //
|
||||
.withAffordances(affordances);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilder#withSelfRel()
|
||||
*/
|
||||
public Link withSelfRel() {
|
||||
return withRel(IanaLinkRelations.SELF);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return builder.build().toUriString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link Function} using a freshly cloned {@link UriComponentsBuilder}.
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected <S> S withFreshBuilder(Function<UriComponentsBuilder, S> function) {
|
||||
|
||||
Assert.notNull(function, "Function must not be null!");
|
||||
|
||||
return function.apply(builder.cloneBuilder());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current concrete instance.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract T getThis();
|
||||
|
||||
/**
|
||||
* Creates a new instance of the sub-class.
|
||||
*
|
||||
* @param builder will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected abstract T createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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.server.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Strategy interface to discover a URI mapping and related {@link org.springframework.hateoas.Affordance}s for either a
|
||||
* given type or method.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public interface MappingDiscoverer {
|
||||
|
||||
/**
|
||||
* Returns the mapping associated with the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return the type-level mapping or {@literal null} in case none is present.
|
||||
*/
|
||||
String getMapping(Class<?> type);
|
||||
|
||||
/**
|
||||
* Returns the mapping associated with the given {@link Method}. This will include the type-level mapping.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
|
||||
*/
|
||||
String getMapping(Method method);
|
||||
|
||||
/**
|
||||
* Returns the mapping for the given {@link Method} invoked on the given type. This can be used to calculate the
|
||||
* mapping for a super type method being invoked on a sub-type with a type mapping.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param method must not be {@literal null}.
|
||||
* @return the method mapping including the type-level one or {@literal null} if neither of them present.
|
||||
*/
|
||||
String getMapping(Class<?> type, Method method);
|
||||
|
||||
/**
|
||||
* Returns the HTTP verbs for the given {@link Method} invoked on the given type. This can be used to build hypermedia
|
||||
* templates.
|
||||
*
|
||||
* @param type
|
||||
* @param method
|
||||
* @return
|
||||
*/
|
||||
Collection<HttpMethod> getRequestMethod(Class<?> type, Method method);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author Oliver Drotbohm
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public interface MethodInvocation {
|
||||
|
||||
Object[] getArguments();
|
||||
|
||||
Method getMethod();
|
||||
|
||||
Class<?> getTargetType();
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.server.core;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.SynthesizingMethodParameter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
* Value object to represent {@link MethodParameters} to allow to easily find the ones with a given annotation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class MethodParameters {
|
||||
|
||||
private static ParameterNameDiscoverer DISCOVERER = new DefaultParameterNameDiscoverer();
|
||||
|
||||
private final List<MethodParameter> parameters;
|
||||
private final Map<Class<?>, List<MethodParameter>> parametersWithAnnotationCache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link MethodParameters} from the given {@link Method}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
*/
|
||||
public MethodParameters(Method method) {
|
||||
this(method, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MethodParameters} for the given {@link Method} and {@link AnnotationAttribute}. If the latter
|
||||
* is given, method parameter names will be looked up from the annotation attribute if present.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param namingAnnotation can be {@literal null}.
|
||||
*/
|
||||
public MethodParameters(Method method, AnnotationAttribute namingAnnotation) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
|
||||
this.parameters = IntStream.range(0, method.getParameterTypes().length) //
|
||||
.mapToObj(it -> new AnnotationNamingMethodParameter(method, it, namingAnnotation)) //
|
||||
.peek(it -> it.initParameterNameDiscovery(DISCOVERER)) //
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link MethodParameter}s.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<MethodParameter> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MethodParameter} with the given name or {@literal null} if none found.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public Optional<MethodParameter> getParameter(String name) {
|
||||
|
||||
Assert.hasText(name, "Parameter name must not be null!");
|
||||
|
||||
return getParameters().stream() //
|
||||
.filter(it -> name.equals(it.getParameterName())) //
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all parameters of the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
* @since 0.9
|
||||
*/
|
||||
public List<MethodParameter> getParametersOfType(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return getParameters().stream() //
|
||||
.filter(it -> it.getParameterType().equals(type)) //
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link MethodParameter}s annotated with the given annotation type.
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public List<MethodParameter> getParametersWith(Class<? extends Annotation> annotation) {
|
||||
|
||||
return parametersWithAnnotationCache.computeIfAbsent(annotation, key -> {
|
||||
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
|
||||
return getParameters().stream()//
|
||||
.filter(it -> it.hasParameterAnnotation(annotation))//
|
||||
.collect(Collectors.toList());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom {@link MethodParameter} extension that will favor the name configured in the {@link AnnotationAttribute} if
|
||||
* set over discovering it.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class AnnotationNamingMethodParameter extends SynthesizingMethodParameter {
|
||||
|
||||
private final AnnotationAttribute attribute;
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationNamingMethodParameter} for the given {@link Method}'s parameter with the given
|
||||
* index.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param parameterIndex
|
||||
* @param attribute can be {@literal null}
|
||||
*/
|
||||
public AnnotationNamingMethodParameter(Method method, int parameterIndex, AnnotationAttribute attribute) {
|
||||
|
||||
super(method, parameterIndex);
|
||||
this.attribute = attribute;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.MethodParameter#getParameterName()
|
||||
*/
|
||||
@Override
|
||||
public String getParameterName() {
|
||||
|
||||
if (name != null) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (attribute != null) {
|
||||
String foundName = attribute.getValueFrom(this);
|
||||
if (foundName != null) {
|
||||
name = foundName;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
name = super.getParameterName();
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 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.server.core;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
|
||||
/**
|
||||
* Annotation to configure the relation to be used when embedding objects in HAL representations of {@link Resource}s
|
||||
* and {@link Resources}.
|
||||
*
|
||||
* @author Alexander Baetz
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Relation {
|
||||
|
||||
String NO_RELATION = "";
|
||||
|
||||
/**
|
||||
* Defines the relation to be used when referring to a single resource.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String value() default NO_RELATION;
|
||||
|
||||
/**
|
||||
* Defines the relation to be used when referring to a collection of resources.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String collectionRelation() default NO_RELATION;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2017-2019 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.server.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.AffordanceModelFactory;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.QueryParameter;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
|
||||
/**
|
||||
* Extract information needed to assemble an {@link Affordance} from a Spring MVC web method.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
public class SpringAffordanceBuilder {
|
||||
|
||||
/**
|
||||
* Use the attributes of the current method call along with a collection of {@link AffordanceModelFactory}'s to create
|
||||
* a set of {@link Affordance}s.
|
||||
*
|
||||
* @param invocation
|
||||
* @param discoverer
|
||||
* @param components
|
||||
* @return
|
||||
*/
|
||||
public static List<Affordance> create(MethodInvocation invocation, MappingDiscoverer discoverer,
|
||||
UriComponents components) {
|
||||
|
||||
List<Affordance> affordances = new ArrayList<>();
|
||||
|
||||
for (HttpMethod requestMethod : discoverer.getRequestMethod(invocation.getTargetType(), invocation.getMethod())) {
|
||||
|
||||
String methodName = invocation.getMethod().getName();
|
||||
|
||||
String href = components.toUriString().equals("") ? "/" : components.toUriString();
|
||||
Link affordanceLink = new Link(href).withRel(LinkRelation.of(methodName));
|
||||
|
||||
MethodParameters invocationMethodParameters = new MethodParameters(invocation.getMethod());
|
||||
|
||||
ResolvableType inputType = invocationMethodParameters.getParametersWith(RequestBody.class).stream() //
|
||||
.findFirst() //
|
||||
.map(ResolvableType::forMethodParameter) //
|
||||
.orElse(ResolvableType.NONE);
|
||||
|
||||
List<QueryParameter> queryMethodParameters = invocationMethodParameters.getParametersWith(RequestParam.class)
|
||||
.stream() //
|
||||
.map(methodParameter -> methodParameter.getParameterAnnotation(RequestParam.class)) //
|
||||
.map(requestParam -> new QueryParameter(requestParam.name(), requestParam.value(), requestParam.required())) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
ResolvableType outputType = ResolvableType.forMethodReturnType(invocation.getMethod());
|
||||
|
||||
affordances
|
||||
.add(new Affordance(methodName, affordanceLink, requestMethod, inputType, queryMethodParameters, outputType));
|
||||
|
||||
}
|
||||
|
||||
return affordances;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* A {@link LinkBuilderSupport} extension that can keep a list of {@link TemplateVariables} around.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public abstract class TemplateVariableAwareLinkBuilderSupport<T extends TemplateVariableAwareLinkBuilderSupport<T>>
|
||||
extends LinkBuilderSupport<T> {
|
||||
|
||||
private final TemplateVariables variables;
|
||||
|
||||
protected TemplateVariableAwareLinkBuilderSupport(UriComponentsBuilder builder, TemplateVariables variables,
|
||||
List<Affordance> affordances) {
|
||||
|
||||
super(builder, affordances);
|
||||
|
||||
this.variables = variables;
|
||||
}
|
||||
|
||||
protected TemplateVariableAwareLinkBuilderSupport(UriComponents components, TemplateVariables variables,
|
||||
List<Affordance> affordances) {
|
||||
|
||||
super(components, affordances);
|
||||
|
||||
this.variables = variables;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.LinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
protected final T createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances) {
|
||||
return createNewInstance(builder, affordances, variables);
|
||||
}
|
||||
|
||||
protected abstract T createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
TemplateVariables variables);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.LinkBuilderSupport#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
String result = super.toString();
|
||||
|
||||
if (variables == TemplateVariables.NONE) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!result.contains("#")) {
|
||||
return result.concat(variables.toString());
|
||||
}
|
||||
|
||||
String[] parts = result.split("#");
|
||||
return parts[0].concat(variables.toString()).concat("#").concat(parts[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Builds and caches {@link UriTemplate} instances.
|
||||
*
|
||||
* @author Michal Stochmialek
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
public class UriTemplateFactory {
|
||||
|
||||
private static final Map<String, UriTemplate> CACHE = new ConcurrentReferenceHashMap<String, UriTemplate>();
|
||||
|
||||
/**
|
||||
* Returns the the {@link UriTemplate} for the given mapping.
|
||||
*
|
||||
* @param mapping must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public static UriTemplate templateFor(String mapping) {
|
||||
|
||||
Assert.hasText(mapping, "Mapping must not be null or empty!");
|
||||
|
||||
return CACHE.computeIfAbsent(mapping, UriTemplate::new);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.core;
|
||||
|
||||
import static org.springframework.hateoas.TemplateVariable.VariableType.*;
|
||||
import static org.springframework.hateoas.TemplateVariables.*;
|
||||
import static org.springframework.hateoas.server.core.EncodingUtils.*;
|
||||
import static org.springframework.web.util.UriComponents.UriTemplateVariables.*;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.server.LinkBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ValueConstants;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Utility for taking a method invocation and extracting a {@link LinkBuilder}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class WebHandler {
|
||||
|
||||
private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer
|
||||
.of(new AnnotationMappingDiscoverer(RequestMapping.class));
|
||||
private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR //
|
||||
= new AnnotatedParametersParameterAccessor(new AnnotationAttribute(PathVariable.class));
|
||||
private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR //
|
||||
= new RequestParamParameterAccessor();
|
||||
|
||||
public interface LinkBuilderCreator<T extends LinkBuilder> {
|
||||
T createBuilder(UriComponents components, TemplateVariables variables, List<Affordance> affordances);
|
||||
}
|
||||
|
||||
public static <T extends LinkBuilder> T linkTo(Object invocationValue,
|
||||
Function<String, UriComponentsBuilder> mappingToUriComponentsBuilder, LinkBuilderCreator<T> creator) {
|
||||
return linkTo(invocationValue, mappingToUriComponentsBuilder, creator, null);
|
||||
}
|
||||
|
||||
public static <T extends LinkBuilder> T linkTo(Object invocationValue,
|
||||
Function<String, UriComponentsBuilder> mappingToUriComponentsBuilder, LinkBuilderCreator<T> creator,
|
||||
BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler) {
|
||||
|
||||
Assert.isInstanceOf(LastInvocationAware.class, invocationValue);
|
||||
|
||||
LastInvocationAware invocations = (LastInvocationAware) invocationValue;
|
||||
MethodInvocation invocation = invocations.getLastInvocation();
|
||||
|
||||
String mapping = DISCOVERER.getMapping(invocation.getTargetType(), invocation.getMethod());
|
||||
|
||||
UriComponentsBuilder builder = mappingToUriComponentsBuilder.apply(mapping);
|
||||
UriTemplate template = UriTemplateFactory.templateFor(mapping == null ? "/" : mapping);
|
||||
Map<String, Object> values = new HashMap<>();
|
||||
|
||||
Iterator<String> names = template.getVariableNames().iterator();
|
||||
Iterator<Object> classMappingParameters = invocations.getObjectParameters();
|
||||
|
||||
while (classMappingParameters.hasNext()) {
|
||||
values.put(names.next(), encodePath(classMappingParameters.next()));
|
||||
}
|
||||
|
||||
for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR
|
||||
.getBoundParameters(invocation)) {
|
||||
values.put(parameter.getVariableName(), encodePath(parameter.asString()));
|
||||
}
|
||||
|
||||
List<String> optionalEmptyParameters = new ArrayList<>();
|
||||
|
||||
for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR
|
||||
.getBoundParameters(invocation)) {
|
||||
|
||||
bindRequestParameters(builder, parameter);
|
||||
|
||||
if (SKIP_VALUE.equals(parameter.getValue())) {
|
||||
|
||||
values.put(parameter.getVariableName(), SKIP_VALUE);
|
||||
|
||||
if (!parameter.isRequired()) {
|
||||
optionalEmptyParameters.add(parameter.getVariableName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String variable : template.getVariableNames()) {
|
||||
if (!values.containsKey(variable)) {
|
||||
values.put(variable, SKIP_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
UriComponents components = additionalUriHandler == null //
|
||||
? builder.buildAndExpand(values)
|
||||
: additionalUriHandler.apply(builder, invocation).buildAndExpand(values);
|
||||
|
||||
TemplateVariables variables = NONE;
|
||||
|
||||
for (String parameter : optionalEmptyParameters) {
|
||||
|
||||
boolean previousRequestParameter = components.getQueryParams().isEmpty() && variables.equals(NONE);
|
||||
TemplateVariable variable = new TemplateVariable(parameter,
|
||||
previousRequestParameter ? REQUEST_PARAM : REQUEST_PARAM_CONTINUED);
|
||||
variables = variables.concat(variable);
|
||||
}
|
||||
|
||||
List<Affordance> affordances = SpringAffordanceBuilder.create(invocation, DISCOVERER, components);
|
||||
|
||||
return creator.createBuilder(components, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the given {@link UriComponentsBuilder} with request parameters found in the given
|
||||
* {@link AnnotatedParametersParameterAccessor.BoundMethodParameter}.
|
||||
*
|
||||
* @param builder must not be {@literal null}.
|
||||
* @param parameter must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void bindRequestParameters(UriComponentsBuilder builder,
|
||||
AnnotatedParametersParameterAccessor.BoundMethodParameter parameter) {
|
||||
|
||||
Object value = parameter.getValue();
|
||||
String key = parameter.getVariableName();
|
||||
|
||||
if (value instanceof MultiValueMap) {
|
||||
|
||||
MultiValueMap<String, String> requestParams = (MultiValueMap<String, String>) value;
|
||||
|
||||
for (Map.Entry<String, List<String>> multiValueEntry : requestParams.entrySet()) {
|
||||
for (String singleEntryValue : multiValueEntry.getValue()) {
|
||||
builder.queryParam(multiValueEntry.getKey(), encodeParameter(singleEntryValue));
|
||||
}
|
||||
}
|
||||
|
||||
} else if (value instanceof Map) {
|
||||
|
||||
Map<String, String> requestParams = (Map<String, String>) value;
|
||||
|
||||
for (Map.Entry<String, String> requestParamEntry : requestParams.entrySet()) {
|
||||
builder.queryParam(requestParamEntry.getKey(), encodeParameter(requestParamEntry.getValue()));
|
||||
}
|
||||
|
||||
} else if (value instanceof Collection) {
|
||||
|
||||
for (Object element : (Collection<?>) value) {
|
||||
builder.queryParam(key, encodeParameter(element));
|
||||
}
|
||||
|
||||
} else if (SKIP_VALUE.equals(value)) {
|
||||
|
||||
if (parameter.isRequired()) {
|
||||
builder.queryParam(key, String.format("{%s}", parameter.getVariableName()));
|
||||
}
|
||||
|
||||
} else {
|
||||
builder.queryParam(key, encodeParameter(parameter.asString()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom extension of {@link AnnotatedParametersParameterAccessor} for {@link RequestParam} to allow {@literal null}
|
||||
* values handed in for optional request parameters.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class RequestParamParameterAccessor extends AnnotatedParametersParameterAccessor {
|
||||
|
||||
public RequestParamParameterAccessor() {
|
||||
super(new AnnotationAttribute(RequestParam.class));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor#createParameter(org.springframework.core.MethodParameter, java.lang.Object, org.springframework.hateoas.core.AnnotationAttribute)
|
||||
*/
|
||||
@Override
|
||||
protected BoundMethodParameter createParameter(final MethodParameter parameter, Object value,
|
||||
AnnotationAttribute attribute) {
|
||||
|
||||
return new BoundMethodParameter(parameter, value, attribute) {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor.BoundMethodParameter#isRequired()
|
||||
*/
|
||||
@Override
|
||||
public boolean isRequired() {
|
||||
|
||||
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
|
||||
|
||||
if (parameter.isOptional()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return annotation.required() //
|
||||
&& annotation.defaultValue().equals(ValueConstants.DEFAULT_NONE);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor#verifyParameterValue(org.springframework.core.MethodParameter, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected Object verifyParameterValue(MethodParameter parameter, Object value) {
|
||||
|
||||
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
|
||||
|
||||
value = ObjectUtils.unwrapOptional(value);
|
||||
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (!annotation.required() || parameter.isOptional()) {
|
||||
return SKIP_VALUE;
|
||||
}
|
||||
|
||||
return annotation.defaultValue().equals(ValueConstants.DEFAULT_NONE) ? SKIP_VALUE : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to allow accessing {@link MethodInvocation} parameters with the configured
|
||||
* {@link AnnotationAttribute}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private static class AnnotatedParametersParameterAccessor {
|
||||
|
||||
private static final Map<Method, MethodParameters> METHOD_PARAMETERS_CACHE = new ConcurrentReferenceHashMap<>(16,
|
||||
ConcurrentReferenceHashMap.ReferenceType.WEAK);
|
||||
|
||||
private final @NonNull AnnotationAttribute attribute;
|
||||
|
||||
/**
|
||||
* Returns {@link BoundMethodParameter}s contained in the given {@link MethodInvocation}.
|
||||
*
|
||||
* @param invocation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public List<BoundMethodParameter> getBoundParameters(MethodInvocation invocation) {
|
||||
|
||||
Assert.notNull(invocation, "MethodInvocation must not be null!");
|
||||
|
||||
MethodParameters parameters = getOrCreateMethodParametersFor(invocation.getMethod());
|
||||
Object[] arguments = invocation.getArguments();
|
||||
List<BoundMethodParameter> result = new ArrayList<>();
|
||||
|
||||
for (MethodParameter parameter : parameters.getParametersWith(attribute.getAnnotationType())) {
|
||||
|
||||
Object value = arguments[parameter.getParameterIndex()];
|
||||
Object verifiedValue = verifyParameterValue(parameter, value);
|
||||
|
||||
if (verifiedValue != null) {
|
||||
result.add(createParameter(parameter, verifiedValue, attribute));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link BoundMethodParameter} for the given {@link MethodParameter}, parameter value and
|
||||
* {@link AnnotationAttribute}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @param value can be {@literal null}.
|
||||
* @param attribute must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected BoundMethodParameter createParameter(MethodParameter parameter, Object value,
|
||||
AnnotationAttribute attribute) {
|
||||
return new BoundMethodParameter(parameter, value, attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to verify the parameter values given for a dummy invocation. Default implementation rejects
|
||||
* {@literal null} values as they indicate an invalid dummy call.
|
||||
*
|
||||
* @param parameter will never be {@literal null}.
|
||||
* @param value could be {@literal null}.
|
||||
* @return the verified value.
|
||||
*/
|
||||
protected Object verifyParameterValue(MethodParameter parameter, Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MethodParameters} for the given {@link Method}.
|
||||
*
|
||||
* @param method
|
||||
* @return
|
||||
*/
|
||||
private static MethodParameters getOrCreateMethodParametersFor(Method method) {
|
||||
return METHOD_PARAMETERS_CACHE.computeIfAbsent(method, MethodParameters::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a {@link MethodParameter} alongside the value it has been bound to.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class BoundMethodParameter {
|
||||
|
||||
private static final ConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService();
|
||||
private static final TypeDescriptor STRING_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
|
||||
private final MethodParameter parameter;
|
||||
private final Object value;
|
||||
private final AnnotationAttribute attribute;
|
||||
private final TypeDescriptor parameterTypeDescriptor;
|
||||
|
||||
/**
|
||||
* Creates a new {@link BoundMethodParameter}
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @param value can be {@literal null}.
|
||||
* @param attribute can be {@literal null}.
|
||||
*/
|
||||
public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute) {
|
||||
|
||||
Assert.notNull(parameter, "MethodParameter must not be null!");
|
||||
|
||||
this.parameter = parameter;
|
||||
this.value = value;
|
||||
this.attribute = attribute;
|
||||
this.parameterTypeDescriptor = TypeDescriptor.nested(parameter, parameter.isOptional() ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the {@link UriTemplate} variable to be bound. The name will be derived from the configured
|
||||
* {@link AnnotationAttribute} or the {@link MethodParameter} name as fallback.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getVariableName() {
|
||||
|
||||
if (attribute == null) {
|
||||
return parameter.getParameterName();
|
||||
}
|
||||
|
||||
Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType());
|
||||
String annotationAttributeValue = attribute.getValueFrom(annotation);
|
||||
|
||||
return StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue : parameter.getParameterName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw value bound to the {@link MethodParameter}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bound value converted into a {@link String} based on default conversion service setup.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String asString() {
|
||||
|
||||
return value == null //
|
||||
? null //
|
||||
: (String) CONVERSION_SERVICE.convert(value, parameterTypeDescriptor, STRING_DESCRIPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given parameter is a required one. Defaults to {@literal true}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Implementations of core API interfaces.
|
||||
*/
|
||||
package org.springframework.hateoas.server.core;
|
||||
|
||||
276
src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilder.java
Executable file
276
src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilder.java
Executable file
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.server.mvc;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.server.core.AnnotationMappingDiscoverer;
|
||||
import org.springframework.hateoas.server.core.CachingMappingDiscoverer;
|
||||
import org.springframework.hateoas.server.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.server.core.MappingDiscoverer;
|
||||
import org.springframework.hateoas.server.core.TemplateVariableAwareLinkBuilderSupport;
|
||||
import org.springframework.hateoas.server.core.UriTemplateFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.util.DefaultUriTemplateHandler;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Builder to ease building {@link Link} instances pointing to Spring MVC controllers.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Kamill Sokol
|
||||
* @author Greg Turnquist
|
||||
* @author Kevin Conaway
|
||||
* @author Andrew Naydyonock
|
||||
* @author Oliver Trosien
|
||||
* @author Greg Turnquist
|
||||
* @deprecated use {@link WebMvcLinkBuilder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class ControllerLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<ControllerLinkBuilder> {
|
||||
|
||||
private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer
|
||||
.of(new AnnotationMappingDiscoverer(RequestMapping.class));
|
||||
private static final ControllerLinkBuilderFactory FACTORY = new ControllerLinkBuilderFactory();
|
||||
private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler();
|
||||
|
||||
/**
|
||||
* Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponentsBuilder}.
|
||||
*
|
||||
* @param builder must not be {@literal null}.
|
||||
*/
|
||||
ControllerLinkBuilder(UriComponentsBuilder builder) {
|
||||
this(builder, TemplateVariables.NONE, Collections.emptyList());
|
||||
}
|
||||
|
||||
ControllerLinkBuilder(UriComponentsBuilder builder, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(builder, variables, affordances);
|
||||
}
|
||||
|
||||
ControllerLinkBuilder(UriComponents uriComponents, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(uriComponents, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ControllerLinkBuilder linkTo(Class<?> controller) {
|
||||
return linkTo(controller, new Object[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class. The
|
||||
* additional parameters are used to fill up potentially available path variables in the class scop request mapping.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be {@literal null}.
|
||||
* @param parameters additional parameters to bind to the URI template declared in the annotation, must not be
|
||||
* {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ControllerLinkBuilder linkTo(Class<?> controller, Object... parameters) {
|
||||
|
||||
Assert.notNull(controller, "Controller must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller);
|
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ControllerLinkBuilder} with a base of the mapping annotated to the given controller class.
|
||||
* Parameter map is used to fill up potentially available path variables in the class scope request mapping.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be {@literal null}.
|
||||
* @param parameters additional parameters to bind to the URI template declared in the annotation, must not be
|
||||
* {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static ControllerLinkBuilder linkTo(Class<?> controller, Map<String, ?> parameters) {
|
||||
|
||||
Assert.notNull(controller, "Controller must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller);
|
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method, Object...)
|
||||
*/
|
||||
public static ControllerLinkBuilder linkTo(Method method, Object... parameters) {
|
||||
return linkTo(method.getDeclaringClass(), method, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Class<?>, Method, Object...)
|
||||
*/
|
||||
public static ControllerLinkBuilder linkTo(Class<?> controller, Method method, Object... parameters) {
|
||||
|
||||
Assert.notNull(controller, "Controller type must not be null!");
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
|
||||
UriTemplate template = UriTemplateFactory.templateFor(DISCOVERER.getMapping(controller, method));
|
||||
URI uri = template.expand(parameters);
|
||||
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ControllerLinkBuilder} pointing to a controller method. Hand in a dummy method invocation result
|
||||
* you can create via {@link #methodOn(Class, Object...)} or {@link DummyInvocationUtils#methodOn(Class, Object...)}.
|
||||
*
|
||||
* <pre>
|
||||
* @RequestMapping("/customers")
|
||||
* class CustomerController {
|
||||
*
|
||||
* @RequestMapping("/{id}/addresses")
|
||||
* HttpEntity<Addresses> showAddresses(@PathVariable Long id) { … }
|
||||
* }
|
||||
*
|
||||
* Link link = linkTo(methodOn(CustomerController.class).showAddresses(2L)).withRel("addresses");
|
||||
* </pre>
|
||||
*
|
||||
* The resulting {@link Link} instance will point to {@code /customers/2/addresses} and have a rel of
|
||||
* {@code addresses}. For more details on the method invocation constraints, see
|
||||
* {@link DummyInvocationUtils#methodOn(Class, Object...)}.
|
||||
*
|
||||
* @param invocationValue
|
||||
* @return
|
||||
*/
|
||||
public static ControllerLinkBuilder linkTo(Object invocationValue) {
|
||||
return FACTORY.linkTo(invocationValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a {@link Link} from the {@link ControllerLinkBuilder} and look up the related {@link Affordance}. Should
|
||||
* only be one.
|
||||
*
|
||||
* <pre>
|
||||
* Link findOneLink = linkTo(methodOn(EmployeeController.class).findOne(id)).withSelfRel()
|
||||
* .andAffordance(afford(methodOn(EmployeeController.class).updateEmployee(null, id)));
|
||||
* </pre>
|
||||
*
|
||||
* This takes a link and adds an {@link Affordance} based on another Spring MVC handler method.
|
||||
*
|
||||
* @param invocationValue
|
||||
* @return
|
||||
*/
|
||||
public static Affordance afford(Object invocationValue) {
|
||||
|
||||
ControllerLinkBuilder linkBuilder = linkTo(invocationValue);
|
||||
|
||||
Assert.isTrue(linkBuilder.getAffordances().size() == 1, "A base can only have one affordance, itself");
|
||||
|
||||
return linkBuilder.getAffordances().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link DummyInvocationUtils#methodOn(Class, Object...)} to be available in case you work with static
|
||||
* imports of {@link ControllerLinkBuilder}.
|
||||
*
|
||||
* @param controller must not be {@literal null}.
|
||||
* @param parameters parameters to extend template variables in the type level mapping.
|
||||
* @return
|
||||
*/
|
||||
public static <T> T methodOn(Class<T> controller, Object... parameters) {
|
||||
return DummyInvocationUtils.methodOn(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.UriComponentsLinkBuilder#getThis()
|
||||
*/
|
||||
@Override
|
||||
protected ControllerLinkBuilder getThis() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List, org.springframework.hateoas.TemplateVariables)
|
||||
*/
|
||||
@Override
|
||||
protected ControllerLinkBuilder createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
TemplateVariables variables) {
|
||||
return new ControllerLinkBuilder(builder, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} to continue to build the already built URI in a more fine grained way.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public UriComponentsBuilder toUriComponentsBuilder() {
|
||||
return UriComponentsBuilder.fromUri(toUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} obtained from the current servlet mapping. If no
|
||||
* {@link RequestContextHolder} exists (you're outside a Spring Web call), fall back to relative URIs.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static UriComponentsBuilder getBuilder() {
|
||||
return UriComponentsBuilderFactory.getBuilder();
|
||||
}
|
||||
|
||||
private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler {
|
||||
|
||||
public CustomUriTemplateHandler() {
|
||||
setStrictEncoding(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public UriComponents expandAndEncode(UriComponentsBuilder builder, Map<String, ?> uriVariables) {
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriVariables) {
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.server.mvc;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
|
||||
import org.springframework.hateoas.server.core.LinkBuilderSupport;
|
||||
import org.springframework.hateoas.server.core.MethodParameters;
|
||||
import org.springframework.hateoas.server.core.WebHandler;
|
||||
|
||||
/**
|
||||
* Factory for {@link LinkBuilderSupport} instances based on the request mapping annotated on the given controller.
|
||||
*
|
||||
* @author Ricardo Gladwell
|
||||
* @author Oliver Gierke
|
||||
* @author Dietrich Schulten
|
||||
* @author Kamill Sokol
|
||||
* @author Ross Turner
|
||||
* @author Oemer Yildiz
|
||||
* @author Kevin Conaway
|
||||
* @author Andrew Naydyonock
|
||||
* @author Greg Turnquist
|
||||
* @deprecated use {@link WebMvcLinkBuilderFactory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<ControllerLinkBuilder> {
|
||||
|
||||
private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method
|
||||
* invocations.
|
||||
*
|
||||
* @see #linkTo(Object)
|
||||
* @param uriComponentsContributors the uriComponentsContributors to set
|
||||
*/
|
||||
public void setUriComponentsContributors(List<? extends UriComponentsContributor> uriComponentsContributors) {
|
||||
this.uriComponentsContributors = Collections.unmodifiableList(uriComponentsContributors);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public ControllerLinkBuilder linkTo(Class<?> controller) {
|
||||
return ControllerLinkBuilder.linkTo(controller);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public ControllerLinkBuilder linkTo(Class<?> controller, Object... parameters) {
|
||||
return ControllerLinkBuilder.linkTo(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public ControllerLinkBuilder linkTo(Class<?> controller, Map<String, ?> parameters) {
|
||||
return ControllerLinkBuilder.linkTo(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.Class, java.lang.reflect.Method, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public ControllerLinkBuilder linkTo(Class<?> controller, Method method, Object... parameters) {
|
||||
return ControllerLinkBuilder.linkTo(controller, method, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public ControllerLinkBuilder linkTo(Object invocationValue) {
|
||||
|
||||
return WebHandler.linkTo(invocationValue, mapping -> ControllerLinkBuilder.getBuilder().path(mapping),
|
||||
ControllerLinkBuilder::new, (builder, invocation) -> {
|
||||
|
||||
MethodParameters parameters = new MethodParameters(invocation.getMethod());
|
||||
Iterator<Object> parameterValues = Arrays.asList(invocation.getArguments()).iterator();
|
||||
|
||||
for (MethodParameter parameter : parameters.getParameters()) {
|
||||
Object parameterValue = parameterValues.next();
|
||||
|
||||
for (UriComponentsContributor contributor : this.uriComponentsContributors) {
|
||||
|
||||
if (contributor.supportsParameter(parameter)) {
|
||||
contributor.enhance(builder, parameter, parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public ControllerLinkBuilder linkTo(Method method, Object... parameters) {
|
||||
return ControllerLinkBuilder.linkTo(method, parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2013-2017 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.server.mvc;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.server.ExposesResourceFor;
|
||||
import org.springframework.hateoas.server.RelProvider;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ControllerRelProvider implements RelProvider {
|
||||
|
||||
private final Class<?> controllerType;
|
||||
private final Class<?> entityType;
|
||||
private final PluginRegistry<RelProvider, Class<?>> providers;
|
||||
|
||||
public ControllerRelProvider(Class<?> controller, PluginRegistry<RelProvider, Class<?>> providers) {
|
||||
|
||||
Assert.notNull(controller, "Controller must not be null!");
|
||||
|
||||
ExposesResourceFor annotation = AnnotationUtils.findAnnotation(controller, ExposesResourceFor.class);
|
||||
Assert.notNull(annotation, "Controller must be annotated with ExposesResourceFor!");
|
||||
|
||||
this.controllerType = controller;
|
||||
this.entityType = annotation.value();
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForCollectionResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public LinkRelation getCollectionResourceRelFor(Class<?> resource) {
|
||||
return providers.getRequiredPluginFor(entityType).getCollectionResourceRelFor(resource);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getRelForSingleResource(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public LinkRelation getItemResourceRelFor(Class<?> resource) {
|
||||
return providers.getRequiredPluginFor(entityType).getItemResourceRelFor(resource);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> delimiter) {
|
||||
return controllerType.equals(delimiter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2012-2017 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.server.mvc;
|
||||
|
||||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.hateoas.Identifiable;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.server.ResourceAssembler;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class to implement {@link ResourceAssembler}s. Will automate {@link ResourceSupport} instance creation and make
|
||||
* sure a self-link is always added.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public abstract class IdentifiableResourceAssemblerSupport<T extends Identifiable<?>, D extends ResourceSupport>
|
||||
extends ResourceAssemblerSupport<T, D> {
|
||||
|
||||
private final Class<?> controllerClass;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourceAssemblerSupport} using the given controller class and resource type.
|
||||
*
|
||||
* @param controllerClass must not be {@literal null}.
|
||||
* @param resourceType must not be {@literal null}.
|
||||
*/
|
||||
public IdentifiableResourceAssemblerSupport(Class<?> controllerClass, Class<D> resourceType) {
|
||||
|
||||
super(controllerClass, resourceType);
|
||||
this.controllerClass = controllerClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new resource and adds a self link to it consisting using the {@link Identifiable}'s id.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected D createResource(T entity) {
|
||||
return createResource(entity, new Object[0]);
|
||||
}
|
||||
|
||||
protected D createResource(T entity, Object... parameters) {
|
||||
return createResourceWithId(entity.getId(), entity, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected D createResourceWithId(Object id, T entity, Object... parameters) {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
|
||||
D instance = instantiateResource(entity);
|
||||
instance.add(linkTo(controllerClass, unwrapIdentifyables(parameters)).slash(id).withSelfRel());
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the ids of the given values in case they're {@link Identifiable}s. Returns all other objects as they are.
|
||||
*
|
||||
* @param values must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private Object[] unwrapIdentifyables(Object[] values) {
|
||||
|
||||
return Arrays.stream(values) //
|
||||
.map(element -> element instanceof Identifiable ? ((Identifiable<?>) element).getId().get() : element) //
|
||||
.toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2017-2019 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.server.mvc;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
|
||||
/**
|
||||
* Simple Jackson serializers and deserializers.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class JacksonSerializers {
|
||||
|
||||
/**
|
||||
* Custom {@link JsonDeserializer} for Spring's {@link MediaType} using the {@link MediaType#parseMediaType(String)}
|
||||
* method.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public static class MediaTypeDeserializer extends StdDeserializer<MediaType> {
|
||||
|
||||
private static final long serialVersionUID = 391537719262033410L;
|
||||
|
||||
public MediaTypeDeserializer() {
|
||||
super(MediaType.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
|
||||
*/
|
||||
@Override
|
||||
public MediaType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
return MediaType.parseMediaType(p.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2012-2018 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.server.mvc;
|
||||
|
||||
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.server.ResourceAssembler;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class to implement {@link ResourceAssembler}s. Will automate {@link ResourceSupport} instance creation and make
|
||||
* sure a self-link is always added.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public abstract class ResourceAssemblerSupport<T, D extends ResourceSupport> implements ResourceAssembler<T, D> {
|
||||
|
||||
private final Class<?> controllerClass;
|
||||
private final Class<D> resourceType;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourceAssemblerSupport} using the given controller class and resource type.
|
||||
*
|
||||
* @param controllerClass must not be {@literal null}.
|
||||
* @param resourceType must not be {@literal null}.
|
||||
*/
|
||||
public ResourceAssemblerSupport(Class<?> controllerClass, Class<D> resourceType) {
|
||||
|
||||
Assert.notNull(controllerClass, "ControllerClass must not be null!");
|
||||
Assert.notNull(resourceType, "ResourceType must not be null!");
|
||||
|
||||
this.controllerClass = controllerClass;
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.ResourceAssembler#toResources(java.lang.Iterable)
|
||||
*/
|
||||
@Override
|
||||
public Resources<D> toResources(Iterable<? extends T> entities) {
|
||||
return this.map(entities).toResources();
|
||||
}
|
||||
|
||||
public Builder<T, D> map(Iterable<? extends T> entities) {
|
||||
return new Builder<>(entities, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new resource with a self link to the given id.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param id must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected D createResourceWithId(Object id, T entity) {
|
||||
return createResourceWithId(id, entity, new Object[0]);
|
||||
}
|
||||
|
||||
protected D createResourceWithId(Object id, T entity, Object... parameters) {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null!");
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
|
||||
D instance = instantiateResource(entity);
|
||||
instance.add(linkTo(this.controllerClass, parameters).slash(id).withSelfRel());
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates the resource object. Default implementation will assume a no-arg constructor and use reflection but
|
||||
* can be overridden to manually set up the object instance initially (e.g. to improve performance if this becomes an
|
||||
* issue).
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
protected D instantiateResource(T entity) {
|
||||
return BeanUtils.instantiateClass(this.resourceType);
|
||||
}
|
||||
|
||||
static class Builder<T, D extends ResourceSupport> {
|
||||
|
||||
private final Iterable<? extends T> entities;
|
||||
private final ResourceAssemblerSupport<T, D> resourceAssembler;
|
||||
|
||||
Builder(Iterable<? extends T> entities, ResourceAssemblerSupport<T, D> resourceAssembler) {
|
||||
|
||||
this.entities = Objects.requireNonNull(entities, "entities must not null!");
|
||||
this.resourceAssembler = resourceAssembler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a list of {@code T}s into a list of {@link ResourceSupport}s.
|
||||
*
|
||||
* @see #toListOfResources() if you need this transformed list rendered as hypermedia
|
||||
* @return
|
||||
*/
|
||||
public List<D> toListOfResources() {
|
||||
|
||||
List<D> result = new ArrayList<>();
|
||||
|
||||
for (T entity : this.entities) {
|
||||
result.add(this.resourceAssembler.toResource(entity));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all given entities into resources and wraps the result in a {@link Resources} instance.
|
||||
*
|
||||
* @see #toListOfResources() and {@link ResourceAssembler#toResource(Object)}
|
||||
* @return
|
||||
*/
|
||||
public Resources<D> toResources() {
|
||||
return new Resources<>(toListOfResources());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.server.mvc;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.server.ResourceProcessor;
|
||||
import org.springframework.hateoas.server.core.HeaderLinksResponseEntity;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodReturnValueHandler} to post-process the objects returned from controller methods using the
|
||||
* configured {@link ResourceProcessor}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 0.20
|
||||
* @soundtrack Doppelkopf - Balance (Von Abseits)
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ResourceProcessorHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||
|
||||
static final ResolvableType RESOURCE_TYPE = ResolvableType.forRawClass(Resource.class);
|
||||
static final ResolvableType RESOURCES_TYPE = ResolvableType.forRawClass(Resources.class);
|
||||
private static final ResolvableType HTTP_ENTITY_TYPE = ResolvableType.forRawClass(HttpEntity.class);
|
||||
|
||||
static final Field CONTENT_FIELD = ReflectionUtils.findField(Resources.class, "content");
|
||||
|
||||
static {
|
||||
ReflectionUtils.makeAccessible(CONTENT_FIELD);
|
||||
}
|
||||
|
||||
private final @NonNull HandlerMethodReturnValueHandler delegate;
|
||||
private final @NonNull ResourceProcessorInvoker invoker;
|
||||
|
||||
private boolean rootLinksAsHeaders = false;
|
||||
|
||||
/**
|
||||
* @param rootLinksAsHeaders the rootLinksAsHeaders to set
|
||||
*/
|
||||
public void setRootLinksAsHeaders(boolean rootLinksAsHeaders) {
|
||||
this.rootLinksAsHeaders = rootLinksAsHeaders;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodReturnValueHandler#supportsReturnType(org.springframework.core.MethodParameter)
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return delegate.supportsReturnType(returnType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodReturnValueHandler#handleReturnValue(java.lang.Object, org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest)
|
||||
*/
|
||||
@Override
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest) throws Exception {
|
||||
|
||||
Object value = returnValue;
|
||||
|
||||
if (returnValue instanceof HttpEntity) {
|
||||
value = ((HttpEntity<?>) returnValue).getBody();
|
||||
}
|
||||
|
||||
// No post-processable type found - proceed with delegate
|
||||
if (!(value instanceof ResourceSupport)) {
|
||||
delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have a Resource or Resources - find suitable processors
|
||||
ResolvableType targetType = ResolvableType.forMethodReturnType(returnType.getMethod());
|
||||
|
||||
// Unbox HttpEntity
|
||||
if (HTTP_ENTITY_TYPE.isAssignableFrom(targetType)) {
|
||||
targetType = targetType.getGeneric(0);
|
||||
}
|
||||
|
||||
ResolvableType returnValueType = ResolvableType.forClass(value.getClass());
|
||||
|
||||
// Returned value is actually of a more specific type, use this type information
|
||||
if (!getRawType(targetType).equals(getRawType(returnValueType))) {
|
||||
targetType = returnValueType;
|
||||
}
|
||||
|
||||
ResourceSupport result = invoker.invokeProcessorsFor((ResourceSupport) value, targetType);
|
||||
delegate.handleReturnValue(rewrapResult(result, returnValue), returnType, mavContainer, webRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-wraps the result of the post-processing work into an {@link HttpEntity} or {@link ResponseEntity} if the
|
||||
* original value was one of those two types. Copies headers and status code from the original value but uses the new
|
||||
* body.
|
||||
*
|
||||
* @param newBody the post-processed value.
|
||||
* @param originalValue the original input value.
|
||||
* @return
|
||||
*/
|
||||
Object rewrapResult(ResourceSupport newBody, Object originalValue) {
|
||||
|
||||
if (!(originalValue instanceof HttpEntity)) {
|
||||
return rootLinksAsHeaders ? HeaderLinksResponseEntity.wrap(newBody) : newBody;
|
||||
}
|
||||
|
||||
HttpEntity<ResourceSupport> entity = null;
|
||||
|
||||
if (originalValue instanceof ResponseEntity) {
|
||||
ResponseEntity<?> source = (ResponseEntity<?>) originalValue;
|
||||
entity = new ResponseEntity<>(newBody, source.getHeaders(), source.getStatusCode());
|
||||
} else {
|
||||
HttpEntity<?> source = (HttpEntity<?>) originalValue;
|
||||
entity = new HttpEntity<>(newBody, source.getHeaders());
|
||||
}
|
||||
|
||||
return rootLinksAsHeaders ? HeaderLinksResponseEntity.wrap(entity) : entity;
|
||||
}
|
||||
|
||||
private static Class<?> getRawType(ResolvableType type) {
|
||||
|
||||
Class<?> rawType = type.getRawClass();
|
||||
return rawType == null ? Object.class : rawType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* Copyright 2016 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.server.mvc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.server.ResourceProcessor;
|
||||
import org.springframework.hateoas.server.core.EmbeddedWrapper;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Component to easily invoke all {@link ResourceProcessor} instances registered for values of type
|
||||
* {@link ResourceSupport}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 0.20
|
||||
* @soundtrack Doppelkopf - Die fabelhaften Vier (Von Abseits)
|
||||
*/
|
||||
public class ResourceProcessorInvoker {
|
||||
|
||||
private final List<ProcessorWrapper> processors;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourceProcessorInvoker} to consider the given {@link ResourceProcessor} to post-process the
|
||||
* controller methods return value to before invoking the delegate.
|
||||
*
|
||||
* @param processors the {@link ResourceProcessor}s to be considered, must not be {@literal null}.
|
||||
*/
|
||||
public ResourceProcessorInvoker(Collection<ResourceProcessor<?>> processors) {
|
||||
|
||||
Assert.notNull(processors, "ResourceProcessors must not be null!");
|
||||
|
||||
this.processors = new ArrayList<>();
|
||||
|
||||
for (ResourceProcessor<?> processor : processors) {
|
||||
|
||||
ResolvableType processorType = ResolvableType.forClass(ResourceProcessor.class, processor.getClass());
|
||||
Class<?> rawType = processorType.getGeneric(0).resolve();
|
||||
|
||||
if (Resource.class.isAssignableFrom(rawType)) {
|
||||
this.processors.add(new ResourceProcessorWrapper(processor));
|
||||
} else if (Resources.class.isAssignableFrom(rawType)) {
|
||||
this.processors.add(new ResourcesProcessorWrapper(processor));
|
||||
} else {
|
||||
this.processors.add(new DefaultProcessorWrapper(processor));
|
||||
}
|
||||
}
|
||||
|
||||
this.processors.sort(AnnotationAwareOrderComparator.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes all {@link ResourceProcessor} instances registered for the type of the given value.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public <T extends ResourceSupport> T invokeProcessorsFor(T value) {
|
||||
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
|
||||
return invokeProcessorsFor(value, ResolvableType.forClass(value.getClass()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes all {@link ResourceProcessor} instances registered for the type of the given value and reference type.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @param referenceType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends ResourceSupport> T invokeProcessorsFor(T value, ResolvableType referenceType) {
|
||||
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
Assert.notNull(referenceType, "Reference type must not be null!");
|
||||
|
||||
// For Resources implementations, process elements first
|
||||
if (ResourceProcessorHandlerMethodReturnValueHandler.RESOURCES_TYPE.isAssignableFrom(referenceType)) {
|
||||
|
||||
Resources<?> resources = (Resources<?>) value;
|
||||
ResolvableType elementTargetType = ResolvableType.forClass(Resources.class, referenceType.getRawClass())
|
||||
.getGeneric(0);
|
||||
List<Object> result = new ArrayList<>(resources.getContent().size());
|
||||
|
||||
for (Object element : resources) {
|
||||
|
||||
ResolvableType elementType = ResolvableType.forClass(element.getClass());
|
||||
|
||||
if (!getRawType(elementTargetType).equals(elementType.getRawClass())) {
|
||||
elementTargetType = elementType;
|
||||
}
|
||||
|
||||
result.add(invokeProcessorsFor(element, elementTargetType));
|
||||
}
|
||||
|
||||
ReflectionUtils.setField(ResourceProcessorHandlerMethodReturnValueHandler.CONTENT_FIELD, resources, result);
|
||||
}
|
||||
|
||||
return (T) invokeProcessorsFor((Object) value, referenceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes all registered {@link ResourceProcessor}s registered for the given {@link ResolvableType}.
|
||||
*
|
||||
* @param value the object to process
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
private Object invokeProcessorsFor(Object value, ResolvableType type) {
|
||||
|
||||
Object currentValue = value;
|
||||
|
||||
// Process actual value
|
||||
for (ResourceProcessorInvoker.ProcessorWrapper wrapper : this.processors) {
|
||||
if (wrapper.supports(type, currentValue)) {
|
||||
currentValue = wrapper.invokeProcessor(currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
private static boolean isRawTypeAssignable(ResolvableType left, Class<?> right) {
|
||||
return getRawType(left).isAssignableFrom(right);
|
||||
}
|
||||
|
||||
private static Class<?> getRawType(ResolvableType type) {
|
||||
|
||||
Class<?> rawType = type.getRawClass();
|
||||
return rawType == null ? Object.class : rawType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to unify interaction with {@link ResourceProcessor}s. The {@link Ordered} rank should be determined by
|
||||
* the underlying processor.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private interface ProcessorWrapper extends Ordered {
|
||||
|
||||
/**
|
||||
* Returns whether the underlying processor supports the given {@link ResolvableType}. It might also additionally
|
||||
* inspect the object that would eventually be handed to the processor.
|
||||
*
|
||||
* @param type the type of object to be post processed, will never be {@literal null}.
|
||||
* @param value the object that would be passed into the processor eventually, can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean supports(ResolvableType type, Object value);
|
||||
|
||||
/**
|
||||
* Performs the actual invocation of the processor. Implementations can be sure
|
||||
* {@link #supports(ResolvableType, Object)} has been called before and returned {@literal true}.
|
||||
*
|
||||
* @param object
|
||||
*/
|
||||
Object invokeProcessor(Object object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ProcessorWrapper} to generically deal with {@link ResourceSupport} types.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class DefaultProcessorWrapper implements ResourceProcessorInvoker.ProcessorWrapper {
|
||||
|
||||
private final ResourceProcessor<?> processor;
|
||||
private final ResolvableType targetType;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultProcessorWrapper} with the given {@link ResourceProcessor}.
|
||||
*
|
||||
* @param processor must not be {@literal null}.
|
||||
*/
|
||||
DefaultProcessorWrapper(ResourceProcessor<?> processor) {
|
||||
|
||||
Assert.notNull(processor, "Processor must not be null!");
|
||||
|
||||
this.processor = processor;
|
||||
this.targetType = ResolvableType.forClass(ResourceProcessor.class, processor.getClass()).getGeneric(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.ResourceProcessorHandlerMethodReturnValueHandler.ProcessorWrapper#supports(org.springframework.core.ResolvableType, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(ResolvableType type, Object value) {
|
||||
return isRawTypeAssignable(targetType, getRawType(type));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.ResourceProcessorHandlerMethodReturnValueHandler.PostProcessorWrapper#invokeProcessor(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object invokeProcessor(Object object) {
|
||||
return ((ResourceProcessor<ResourceSupport>) processor).process((ResourceSupport) object);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.Ordered#getOrder()
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return CustomOrderAwareComparator.INSTANCE.getOrder(processor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target type the underlying {@link ResourceProcessor} wants to get invoked for.
|
||||
*
|
||||
* @return the targetType
|
||||
*/
|
||||
public ResolvableType getTargetType() {
|
||||
return targetType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ProcessorWrapper} to deal with {@link ResourceProcessor}s for {@link Resource}s. Will fall back to peeking
|
||||
* into the {@link Resource}'s content for type resolution.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class ResourceProcessorWrapper extends ResourceProcessorInvoker.DefaultProcessorWrapper {
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourceProcessorWrapper} for the given {@link ResourceProcessor}.
|
||||
*
|
||||
* @param processor must not be {@literal null}.
|
||||
*/
|
||||
public ResourceProcessorWrapper(ResourceProcessor<?> processor) {
|
||||
super(processor);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.ResourceProcessorHandlerMethodReturnValueHandler.DefaultProcessorWrapper#supports(org.springframework.core.ResolvableType, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(ResolvableType type, Object value) {
|
||||
|
||||
if (!ResourceProcessorHandlerMethodReturnValueHandler.RESOURCE_TYPE.isAssignableFrom(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.supports(type, value) && isValueTypeMatch((Resource<?>) value, getTargetType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link Resource} matches the given target {@link ResolvableType}. We inspect the
|
||||
* {@link Resource}'s value to determine the match.
|
||||
*
|
||||
* @param resource
|
||||
* @param target must not be {@literal null}.
|
||||
* @return whether the given {@link Resource} can be assigned to the given target {@link ResolvableType}
|
||||
*/
|
||||
private static boolean isValueTypeMatch(Resource<?> resource, ResolvableType target) {
|
||||
|
||||
if (resource == null || !isRawTypeAssignable(target, resource.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object content = resource.getContent();
|
||||
|
||||
if (content == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResolvableType type = findGenericType(target, Resource.class);
|
||||
return type != null && type.getGeneric(0).isAssignableFrom(ResolvableType.forClass(content.getClass()));
|
||||
}
|
||||
|
||||
private static ResolvableType findGenericType(ResolvableType source, Class<?> type) {
|
||||
|
||||
Class<?> rawType = getRawType(source);
|
||||
|
||||
if (Object.class.equals(rawType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rawType.equals(type)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
return findGenericType(source.getSuperType(), type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ProcessorWrapper} for {@link ResourceProcessor}s targeting {@link Resources}. Will peek into the content of
|
||||
* the {@link Resources} for type matching decisions if needed.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public static class ResourcesProcessorWrapper extends ResourceProcessorInvoker.DefaultProcessorWrapper {
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourcesProcessorWrapper} for the given {@link ResourceProcessor}.
|
||||
*
|
||||
* @param processor must not be {@literal null}.
|
||||
*/
|
||||
public ResourcesProcessorWrapper(ResourceProcessor<?> processor) {
|
||||
super(processor);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.ResourceProcessorHandlerMethodReturnValueHandler.DefaultProcessorWrapper#supports(org.springframework.core.ResolvableType, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(ResolvableType type, Object value) {
|
||||
|
||||
if (!ResourceProcessorHandlerMethodReturnValueHandler.RESOURCES_TYPE.isAssignableFrom(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.supports(type, value) && isValueTypeMatch((Resources<?>) value, getTargetType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link Resources} instance matches the given {@link ResolvableType}. We predict this by
|
||||
* inspecting the first element of the content of the {@link Resources}.
|
||||
*
|
||||
* @param resources the {@link Resources} to inspect.
|
||||
* @param target that target {@link ResolvableType}.
|
||||
* @return
|
||||
*/
|
||||
static boolean isValueTypeMatch(Resources<?> resources, ResolvableType target) {
|
||||
|
||||
if (resources == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Collection<?> content = resources.getContent();
|
||||
|
||||
if (content.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResolvableType superType = null;
|
||||
|
||||
for (Class<?> resourcesType : Arrays.<Class<?>>asList(resources.getClass(), Resources.class)) {
|
||||
|
||||
superType = getSuperType(target, resourcesType);
|
||||
|
||||
if (superType != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (superType == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object element = content.iterator().next();
|
||||
ResolvableType resourceType = superType.getGeneric(0);
|
||||
|
||||
if (element instanceof Resource) {
|
||||
return ResourceProcessorWrapper.isValueTypeMatch((Resource<?>) element, resourceType);
|
||||
} else if (element instanceof EmbeddedWrapper) {
|
||||
return isRawTypeAssignable(resourceType, ((EmbeddedWrapper) element).getRelTargetType());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ResolvableType} for the given raw super class.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @param superType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static ResolvableType getSuperType(ResolvableType source, Class<?> superType) {
|
||||
|
||||
if (source.getRawClass().equals(superType)) {
|
||||
return source;
|
||||
}
|
||||
|
||||
ResolvableType candidate = source.getSuperType();
|
||||
|
||||
if (superType.isAssignableFrom(candidate.getRawClass())) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
for (ResolvableType interfaces : source.getInterfaces()) {
|
||||
if (superType.isAssignableFrom(interfaces.getRawClass())) {
|
||||
return interfaces;
|
||||
}
|
||||
}
|
||||
|
||||
return ResolvableType.forClass(superType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper extension of {@link AnnotationAwareOrderComparator} to make {@link #getOrder(Object)} public to allow it
|
||||
* being used in a standalone fashion.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class CustomOrderAwareComparator extends AnnotationAwareOrderComparator {
|
||||
|
||||
public static ResourceProcessorInvoker.CustomOrderAwareComparator INSTANCE = new CustomOrderAwareComparator();
|
||||
|
||||
@Override
|
||||
protected int getOrder(Object obj) {
|
||||
return super.getOrder(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2012-2016 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.server.mvc;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.hateoas.server.ResourceProcessor;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
|
||||
/**
|
||||
* Special {@link RequestMappingHandlerAdapter} that tweaks the {@link HandlerMethodReturnValueHandlerComposite} to be
|
||||
* proxied by a {@link ResourceProcessorHandlerMethodReturnValueHandler} which will invoke the {@link ResourceProcessor}
|
||||
* s found in the application context and eventually delegate to the originally configured
|
||||
* {@link HandlerMethodReturnValueHandler}.
|
||||
* <p/>
|
||||
* This is a separate component as it might make sense to deploy it in a standalone SpringMVC application to enable post
|
||||
* processing. It would actually make most sense in Spring HATEOAS project.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Phil Webb
|
||||
* @since 0.20
|
||||
* @soundtrack Dopplekopf - Regen für immer (Von Abseits)
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ResourceProcessorInvokingHandlerAdapter extends RequestMappingHandlerAdapter {
|
||||
|
||||
private static final Method RETURN_VALUE_HANDLER_METHOD = ReflectionUtils
|
||||
.findMethod(ResourceProcessorInvokingHandlerAdapter.class, "getReturnValueHandlers");
|
||||
|
||||
private @NonNull final ResourceProcessorInvoker invoker;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
|
||||
super.afterPropertiesSet();
|
||||
|
||||
// Retrieve actual handlers to use as delegate
|
||||
HandlerMethodReturnValueHandlerComposite oldHandlers = getReturnValueHandlersComposite();
|
||||
|
||||
// Set up ResourceProcessingHandlerMethodResolver to delegate to originally configured ones
|
||||
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>();
|
||||
newHandlers.add(new ResourceProcessorHandlerMethodReturnValueHandler(oldHandlers, invoker));
|
||||
|
||||
// Configure the new handler to be used
|
||||
this.setReturnValueHandlers(newHandlers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link HandlerMethodReturnValueHandlerComposite} for return handlers, dealing with API changes introduced in
|
||||
* Spring 4.0.
|
||||
*
|
||||
* @return a HandlerMethodReturnValueHandlerComposite
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private HandlerMethodReturnValueHandlerComposite getReturnValueHandlersComposite() {
|
||||
|
||||
Object handlers = ReflectionUtils.invokeMethod(RETURN_VALUE_HANDLER_METHOD, this);
|
||||
|
||||
if (handlers instanceof HandlerMethodReturnValueHandlerComposite) {
|
||||
return (HandlerMethodReturnValueHandlerComposite) handlers;
|
||||
}
|
||||
|
||||
return new HandlerMethodReturnValueHandlerComposite() //
|
||||
.addHandlers((List<? extends HandlerMethodReturnValueHandler>) handlers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2014-2015 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.server.mvc;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Extension of {@link MappingJackson2HttpMessageConverter} to constrain the ability to read and write HTTP message
|
||||
* based on the target type. Useful in case the {@link ObjectMapper} about to be configured has customizations that
|
||||
* shall only be applied to object trees of a certain base type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class TypeConstrainedMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
|
||||
|
||||
private final Class<?> type;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypeConstrainedMappingJackson2HttpMessageConverter} for the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public TypeConstrainedMappingJackson2HttpMessageConverter(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor to supply all parameters at once.
|
||||
*
|
||||
* @param type
|
||||
* @param supportedMediaTypes
|
||||
* @param objectMapper
|
||||
*/
|
||||
public TypeConstrainedMappingJackson2HttpMessageConverter(Class<?> type, List<MediaType> supportedMediaTypes, ObjectMapper objectMapper) {
|
||||
|
||||
this(type);
|
||||
setSupportedMediaTypes(supportedMediaTypes);
|
||||
setObjectMapper(objectMapper);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#canRead(java.lang.Class, org.springframework.http.MediaType)
|
||||
*/
|
||||
@Override
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) {
|
||||
return type.isAssignableFrom(clazz) && super.canRead(clazz, mediaType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#canRead(java.lang.reflect.Type, java.lang.Class, org.springframework.http.MediaType)
|
||||
*/
|
||||
@Override
|
||||
public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) {
|
||||
return this.type.isAssignableFrom(getJavaType(type, contextClass).getRawClass())
|
||||
&& super.canRead(type, contextClass, mediaType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#canWrite(java.lang.Class, org.springframework.http.MediaType)
|
||||
*/
|
||||
@Override
|
||||
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
|
||||
return type.isAssignableFrom(clazz) && super.canWrite(clazz, mediaType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2015-2019 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.server.mvc;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Helper to easily create {@link ParameterizedTypeReference} instances to Spring HATEOAS resource types. They're
|
||||
* basically a shortcut over using a verbose {@code new ParameterizedTypeReference<Resources<DomainType>>()}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 0.17
|
||||
*/
|
||||
public class TypeReferences {
|
||||
|
||||
/**
|
||||
* A {@link ParameterizedTypeReference} to return a {@link org.springframework.hateoas.Resource} of some type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 0.17
|
||||
*/
|
||||
public static class ResourceType<T>
|
||||
extends SyntheticParameterizedTypeReference<org.springframework.hateoas.Resource<T>> {}
|
||||
|
||||
/**
|
||||
* A {@link ParameterizedTypeReference} to return a {@link org.springframework.hateoas.Resources} of some type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 0.17
|
||||
*/
|
||||
public static class ResourcesType<T>
|
||||
extends SyntheticParameterizedTypeReference<org.springframework.hateoas.Resources<T>> {}
|
||||
|
||||
/**
|
||||
* A {@link ParameterizedTypeReference} to return a {@link org.springframework.hateoas.PagedResources} of some type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 0.17
|
||||
*/
|
||||
public static class PagedResourcesType<T>
|
||||
extends SyntheticParameterizedTypeReference<org.springframework.hateoas.PagedResources<T>> {}
|
||||
|
||||
/**
|
||||
* Special {@link ParameterizedTypeReference} to customize the generic type detection and eventually return a
|
||||
* synthetic {@link ParameterizedType} to represent the resource type along side its generic parameter.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 0.17
|
||||
*/
|
||||
private static abstract class SyntheticParameterizedTypeReference<T> extends ParameterizedTypeReference<T> {
|
||||
|
||||
private final Type type;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
SyntheticParameterizedTypeReference() {
|
||||
|
||||
Class<? extends SyntheticParameterizedTypeReference> foo = getClass();
|
||||
Type genericSuperclass = foo.getGenericSuperclass();
|
||||
ParameterizedType bar = (ParameterizedType) genericSuperclass;
|
||||
Type domainType = bar.getActualTypeArguments()[0];
|
||||
|
||||
Class<?> parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(getClass());
|
||||
Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass();
|
||||
Assert.isInstanceOf(ParameterizedType.class, type);
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
Assert.isTrue(parameterizedType.getActualTypeArguments().length == 1,
|
||||
String.format("Type must have exactly one generic type argument but has %s.",
|
||||
parameterizedType.getActualTypeArguments().length));
|
||||
|
||||
Class<?> resourceType = GenericTypeResolver.resolveType(parameterizedType.getActualTypeArguments()[0],
|
||||
new HashMap<>());
|
||||
|
||||
this.type = new SyntheticParameterizedType(resourceType, domainType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.ParameterizedTypeReference#getType()
|
||||
*/
|
||||
@Override
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.ParameterizedTypeReference#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this == obj || obj instanceof SyntheticParameterizedTypeReference
|
||||
&& this.type.equals(((SyntheticParameterizedTypeReference<?>) obj).type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.ParameterizedTypeReference#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.type.hashCode();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.ParameterizedTypeReference#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SyntheticParameterizedTypeReference<" + this.type + ">";
|
||||
}
|
||||
|
||||
private static Class<?> findParameterizedTypeReferenceSubclass(Class<?> child) {
|
||||
|
||||
Class<?> parent = child.getSuperclass();
|
||||
if (Object.class.equals(parent)) {
|
||||
throw new IllegalStateException("Expected SyntheticParameterizedTypeReference superclass");
|
||||
} else if (SyntheticParameterizedTypeReference.class.equals(parent)) {
|
||||
return child;
|
||||
} else {
|
||||
return findParameterizedTypeReferenceSubclass(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A sythetic {@link ParameterizedType}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 0.17
|
||||
*/
|
||||
private static final class SyntheticParameterizedType implements ParameterizedType, Serializable {
|
||||
|
||||
private static final long serialVersionUID = -521679299810654826L;
|
||||
|
||||
private final Type rawType;
|
||||
private final Type[] typeArguments;
|
||||
|
||||
SyntheticParameterizedType(Type rawType, Type... typeArguments) {
|
||||
|
||||
this.rawType = rawType;
|
||||
this.typeArguments = typeArguments;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.reflect.ParameterizedType#getActualTypeArguments()
|
||||
*/
|
||||
@Override
|
||||
public Type[] getActualTypeArguments() {
|
||||
return this.typeArguments;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.reflect.ParameterizedType#getRawType()
|
||||
*/
|
||||
@Override
|
||||
public Type getRawType() {
|
||||
return this.rawType;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.reflect.ParameterizedType#getOwnerType()
|
||||
*/
|
||||
@Override
|
||||
public Type getOwnerType() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.mvc;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Factory class for {@link UriComponentsBuilder} instances caching the lookups to avoid unnecessary subsequent lookups.
|
||||
*
|
||||
* @author Michal Stochmialek
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class UriComponentsBuilderFactory {
|
||||
|
||||
static final String REQUEST_ATTRIBUTES_MISSING = "Could not find current request via RequestContextHolder. Is this being called from a Spring MVC handler?";
|
||||
private static final String CACHE_KEY = ControllerLinkBuilder.class.getName() + "#BUILDER_CACHE";
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} obtained from the current servlet mapping with scheme tweaked in case the
|
||||
* request contains an {@code X-Forwarded-Ssl} header, which is not (yet) supported by the underlying
|
||||
* {@link UriComponentsBuilder}. If no {@link RequestContextHolder} exists (you're outside a Spring Web call), fall
|
||||
* back to relative URIs.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static UriComponentsBuilder getBuilder() {
|
||||
|
||||
if (RequestContextHolder.getRequestAttributes() == null) {
|
||||
return UriComponentsBuilder.fromPath("/");
|
||||
}
|
||||
|
||||
URI baseUri = getCachedBaseUri();
|
||||
|
||||
return baseUri != null //
|
||||
? UriComponentsBuilder.fromUri(baseUri) //
|
||||
: cacheBaseUri(ServletUriComponentsBuilder.fromServletMapping(getCurrentRequest()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy of {@link ServletUriComponentsBuilder#getCurrentRequest()} until SPR-10110 gets fixed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static HttpServletRequest getCurrentRequest() {
|
||||
|
||||
RequestAttributes requestAttributes = getRequestAttributes();
|
||||
HttpServletRequest servletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
|
||||
Assert.state(servletRequest != null, "Could not find current HttpServletRequest");
|
||||
|
||||
return servletRequest;
|
||||
}
|
||||
|
||||
private static RequestAttributes getRequestAttributes() {
|
||||
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
|
||||
Assert.state(requestAttributes != null, REQUEST_ATTRIBUTES_MISSING);
|
||||
Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes);
|
||||
|
||||
return requestAttributes;
|
||||
}
|
||||
|
||||
private static UriComponentsBuilder cacheBaseUri(UriComponentsBuilder builder) {
|
||||
|
||||
URI uri = builder.build().toUri();
|
||||
|
||||
getRequestAttributes().setAttribute(CACHE_KEY, uri, RequestAttributes.SCOPE_REQUEST);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static URI getCachedBaseUri() {
|
||||
return (URI) getRequestAttributes().getAttribute(CACHE_KEY, RequestAttributes.SCOPE_REQUEST);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 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.server.mvc;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* SPI callback to enhance a {@link UriComponentsBuilder} when referring to a method through a dummy method invocation.
|
||||
* Will usually be implemented in implementations of {@link HandlerMethodArgumentResolver} as they represent exactly the
|
||||
* same functionality inverted.
|
||||
*
|
||||
* @see MethodLinkBuilderFactory#linkTo(Object)
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface UriComponentsContributor {
|
||||
|
||||
/**
|
||||
* Returns whether the {@link UriComponentsBuilder} supports the given {@link MethodParameter}.
|
||||
*
|
||||
* @param parameter will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean supportsParameter(MethodParameter parameter);
|
||||
|
||||
/**
|
||||
* Enhance the given {@link UriComponentsBuilder} with the given value.
|
||||
*
|
||||
* @param builder will never be {@literal null}.
|
||||
* @param parameter will never be {@literal null}.
|
||||
* @param value can be {@literal null}.
|
||||
*/
|
||||
void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value);
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.server.mvc;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.server.core.AnnotationMappingDiscoverer;
|
||||
import org.springframework.hateoas.server.core.CachingMappingDiscoverer;
|
||||
import org.springframework.hateoas.server.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.server.core.MappingDiscoverer;
|
||||
import org.springframework.hateoas.server.core.TemplateVariableAwareLinkBuilderSupport;
|
||||
import org.springframework.hateoas.server.core.UriTemplateFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.util.DefaultUriTemplateHandler;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Builder to ease building {@link Link} instances pointing to Spring MVC controllers.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Kamill Sokol
|
||||
* @author Greg Turnquist
|
||||
* @author Kevin Conaway
|
||||
* @author Andrew Naydyonock
|
||||
* @author Oliver Trosien
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<WebMvcLinkBuilder> {
|
||||
|
||||
private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer
|
||||
.of(new AnnotationMappingDiscoverer(RequestMapping.class));
|
||||
private static final WebMvcLinkBuilderFactory FACTORY = new WebMvcLinkBuilderFactory();
|
||||
private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler();
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebMvcLinkBuilder} using the given {@link UriComponentsBuilder}.
|
||||
*
|
||||
* @param builder must not be {@literal null}.
|
||||
*/
|
||||
WebMvcLinkBuilder(UriComponentsBuilder builder) {
|
||||
this(builder, TemplateVariables.NONE, Collections.emptyList());
|
||||
}
|
||||
|
||||
WebMvcLinkBuilder(UriComponentsBuilder builder, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(builder, variables, affordances);
|
||||
}
|
||||
|
||||
WebMvcLinkBuilder(UriComponents uriComponents, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(uriComponents, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebMvcLinkBuilder} with a base of the mapping annotated to the given controller class.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Class<?> controller) {
|
||||
return linkTo(controller, new Object[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebMvcLinkBuilder} with a base of the mapping annotated to the given controller class. The
|
||||
* additional parameters are used to fill up potentially available path variables in the class scop request mapping.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be {@literal null}.
|
||||
* @param parameters additional parameters to bind to the URI template declared in the annotation, must not be
|
||||
* {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Class<?> controller, Object... parameters) {
|
||||
|
||||
Assert.notNull(controller, "Controller must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller);
|
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getBuilder()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebMvcLinkBuilder} with a base of the mapping annotated to the given controller class.
|
||||
* Parameter map is used to fill up potentially available path variables in the class scope request mapping.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be {@literal null}.
|
||||
* @param parameters additional parameters to bind to the URI template declared in the annotation, must not be
|
||||
* {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Class<?> controller, Map<String, ?> parameters) {
|
||||
|
||||
Assert.notNull(controller, "Controller must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller);
|
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getBuilder()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method, Object...)
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
|
||||
return linkTo(method.getDeclaringClass(), method, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Class<?>, Method, Object...)
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Class<?> controller, Method method, Object... parameters) {
|
||||
|
||||
Assert.notNull(controller, "Controller type must not be null!");
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller, method);
|
||||
UriTemplate template = UriTemplateFactory.templateFor(mapping);
|
||||
URI uri = template.expand(parameters);
|
||||
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getBuilder()).slash(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link WebMvcLinkBuilder} pointing to a controller method. Hand in a dummy method invocation result you
|
||||
* can create via {@link #methodOn(Class, Object...)} or {@link DummyInvocationUtils#methodOn(Class, Object...)}.
|
||||
*
|
||||
* <pre>
|
||||
* @RequestMapping("/customers")
|
||||
* class CustomerController {
|
||||
*
|
||||
* @RequestMapping("/{id}/addresses")
|
||||
* HttpEntity<Addresses> showAddresses(@PathVariable Long id) { … }
|
||||
* }
|
||||
*
|
||||
* Link link = linkTo(methodOn(CustomerController.class).showAddresses(2L)).withRel("addresses");
|
||||
* </pre>
|
||||
*
|
||||
* The resulting {@link Link} instance will point to {@code /customers/2/addresses} and have a rel of
|
||||
* {@code addresses}. For more details on the method invocation constraints, see
|
||||
* {@link DummyInvocationUtils#methodOn(Class, Object...)}.
|
||||
*
|
||||
* @param invocationValue
|
||||
* @return
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Object invocationValue) {
|
||||
return FACTORY.linkTo(invocationValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a {@link Link} from the {@link WebMvcLinkBuilder} and look up the related {@link Affordance}. Should only
|
||||
* be one.
|
||||
*
|
||||
* <pre>
|
||||
* Link findOneLink = linkTo(methodOn(EmployeeController.class).findOne(id)).withSelfRel()
|
||||
* .andAffordance(afford(methodOn(EmployeeController.class).updateEmployee(null, id)));
|
||||
* </pre>
|
||||
*
|
||||
* This takes a link and adds an {@link Affordance} based on another Spring MVC handler method.
|
||||
*
|
||||
* @param invocationValue
|
||||
* @return
|
||||
*/
|
||||
public static Affordance afford(Object invocationValue) {
|
||||
|
||||
WebMvcLinkBuilder linkBuilder = linkTo(invocationValue);
|
||||
|
||||
Assert.isTrue(linkBuilder.getAffordances().size() == 1, "A base can only have one affordance, itself");
|
||||
|
||||
return linkBuilder.getAffordances().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link DummyInvocationUtils#methodOn(Class, Object...)} to be available in case you work with static
|
||||
* imports of {@link WebMvcLinkBuilder}.
|
||||
*
|
||||
* @param controller must not be {@literal null}.
|
||||
* @param parameters parameters to extend template variables in the type level mapping.
|
||||
* @return
|
||||
*/
|
||||
public static <T> T methodOn(Class<T> controller, Object... parameters) {
|
||||
return DummyInvocationUtils.methodOn(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.UriComponentsLinkBuilder#getThis()
|
||||
*/
|
||||
@Override
|
||||
protected WebMvcLinkBuilder getThis() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List, org.springframework.hateoas.TemplateVariables)
|
||||
*/
|
||||
@Override
|
||||
protected WebMvcLinkBuilder createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
TemplateVariables variables) {
|
||||
return new WebMvcLinkBuilder(builder, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} to continue to build the already built URI in a more fine grained way.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public UriComponentsBuilder toUriComponentsBuilder() {
|
||||
return UriComponentsBuilder.fromUri(toUri());
|
||||
}
|
||||
|
||||
private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler {
|
||||
|
||||
public CustomUriTemplateHandler() {
|
||||
setStrictEncoding(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public UriComponents expandAndEncode(UriComponentsBuilder builder, Map<String, ?> uriVariables) {
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriVariables) {
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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.server.mvc;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.server.MethodLinkBuilderFactory;
|
||||
import org.springframework.hateoas.server.core.LinkBuilderSupport;
|
||||
import org.springframework.hateoas.server.core.MethodParameters;
|
||||
import org.springframework.hateoas.server.core.WebHandler;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Factory for {@link LinkBuilderSupport} instances based on the request mapping annotated on the given controller.
|
||||
*
|
||||
* @author Ricardo Gladwell
|
||||
* @author Oliver Gierke
|
||||
* @author Dietrich Schulten
|
||||
* @author Kamill Sokol
|
||||
* @author Ross Turner
|
||||
* @author Oemer Yildiz
|
||||
* @author Kevin Conaway
|
||||
* @author Andrew Naydyonock
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class WebMvcLinkBuilderFactory implements MethodLinkBuilderFactory<WebMvcLinkBuilder> {
|
||||
|
||||
private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method
|
||||
* invocations.
|
||||
*
|
||||
* @see #linkTo(Object)
|
||||
* @param uriComponentsContributors the uriComponentsContributors to set
|
||||
*/
|
||||
public void setUriComponentsContributors(List<? extends UriComponentsContributor> uriComponentsContributors) {
|
||||
this.uriComponentsContributors = Collections.unmodifiableList(uriComponentsContributors);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Class<?> controller) {
|
||||
return WebMvcLinkBuilder.linkTo(controller);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Class<?> controller, Object... parameters) {
|
||||
return WebMvcLinkBuilder.linkTo(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Class<?> controller, Map<String, ?> parameters) {
|
||||
return WebMvcLinkBuilder.linkTo(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.Class, java.lang.reflect.Method, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Class<?> controller, Method method, Object... parameters) {
|
||||
return WebMvcLinkBuilder.linkTo(controller, method, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Object invocationValue) {
|
||||
|
||||
Function<String, UriComponentsBuilder> builderFactory = mapping -> UriComponentsBuilderFactory.getBuilder()
|
||||
.path(mapping);
|
||||
|
||||
return WebHandler.linkTo(invocationValue, builderFactory, WebMvcLinkBuilder::new, (builder, invocation) -> {
|
||||
|
||||
MethodParameters parameters = new MethodParameters(invocation.getMethod());
|
||||
Iterator<Object> parameterValues = Arrays.asList(invocation.getArguments()).iterator();
|
||||
|
||||
for (MethodParameter parameter : parameters.getParameters()) {
|
||||
|
||||
Object parameterValue = parameterValues.next();
|
||||
|
||||
uriComponentsContributors.stream() //
|
||||
.filter(it -> it.supportsParameter(parameter)) //
|
||||
.forEach(it -> it.enhance(builder, parameter, parameterValue));
|
||||
}
|
||||
|
||||
return builder;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
|
||||
return WebMvcLinkBuilder.linkTo(method, parameters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Spring MVC helper classes to build {@link org.springframework.hateoas.Link}s and assemble
|
||||
* {@link org.springframework.hateoas.ResourceSupport} types.
|
||||
*/
|
||||
package org.springframework.hateoas.server.mvc;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.reactive;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
|
||||
/**
|
||||
* {@link WebFilter} that ensures a copy of the {@link ServerWebExchange} is added to the Reactor {@link Context}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 1.0
|
||||
*/
|
||||
public class HypermediaWebFilter implements WebFilter {
|
||||
|
||||
public static final String SERVER_WEB_EXCHANGE = "serverWebExchange";
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.server.WebFilter#filter(org.springframework.web.server.ServerWebExchange, org.springframework.web.server.WebFilterChain)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
return chain.filter(exchange) //
|
||||
.subscriberContext(Context.of(SERVER_WEB_EXCHANGE, exchange));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.reactive;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.server.ResourceAssembler;
|
||||
import org.springframework.hateoas.server.SimpleResourceAssembler;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Reactive variant of {@link ResourceAssembler} combined with {@link SimpleResourceAssembler}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface ReactiveResourceAssembler<T, D extends ResourceSupport> {
|
||||
|
||||
/**
|
||||
* Converts the given entity into a {@code D}, which extends {@link ResourceSupport}.
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
Mono<D> toResource(T entity, ServerWebExchange exchange);
|
||||
|
||||
/**
|
||||
* Converts an {@link Iterable} or {@code T}s into an {@link Iterable} of {@link ResourceSupport} and wraps
|
||||
* them in a {@link Resources} instance.
|
||||
*
|
||||
* @param entities must not be {@literal null}.
|
||||
* @return {@link Resources} containing {@code D}.
|
||||
*/
|
||||
default Mono<Resources<D>> toResources(Flux<? extends T> entities, ServerWebExchange exchange) {
|
||||
|
||||
return entities
|
||||
.flatMap(entity -> toResource(entity, exchange))
|
||||
.collectList()
|
||||
.map(listOfResources -> new Resources<>(listOfResources));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.reactive;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.server.ResourceAssembler;
|
||||
import org.springframework.hateoas.server.SimpleResourceAssembler;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Reactive variant of {@link ResourceAssembler} combined with {@link SimpleResourceAssembler}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface SimpleReactiveResourceAssembler<T> extends ReactiveResourceAssembler<T, Resource<T>> {
|
||||
|
||||
/**
|
||||
* Converts the given entity into a {@link Resource} wrapped in a {@link Mono}.
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
default Mono<Resource<T>> toResource(T entity, ServerWebExchange exchange) {
|
||||
|
||||
Resource<T> resource = new Resource<>(entity);
|
||||
addLinks(resource, exchange);
|
||||
return Mono.just(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define links to add to every individual {@link Resource}.
|
||||
*
|
||||
* @param resource
|
||||
*/
|
||||
void addLinks(Resource<T> resource, ServerWebExchange exchange);
|
||||
|
||||
/**
|
||||
* Converts all given entities into resources and wraps the collection as a resource as well.
|
||||
*
|
||||
* @see #toResource(Object, ServerWebExchange)
|
||||
* @param entities must not be {@literal null}.
|
||||
* @return {@link Resources} containing {@link Resource} of {@code T}.
|
||||
*/
|
||||
default Mono<Resources<Resource<T>>> toResources(Flux<? extends T> entities, ServerWebExchange exchange) {
|
||||
|
||||
return entities //
|
||||
.flatMap(entity -> toResource(entity, exchange)) //
|
||||
.collectList() //
|
||||
.map(listOfResources -> {
|
||||
Resources<Resource<T>> resources = new Resources<>(listOfResources);
|
||||
addLinks(resources, exchange);
|
||||
return resources;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Define links to add to the {@link Resources} collection.
|
||||
*
|
||||
* @param resources
|
||||
*/
|
||||
void addLinks(Resources<Resource<T>> resources, ServerWebExchange exchange);
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright 2019 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.server.reactive;
|
||||
|
||||
import static org.springframework.hateoas.server.reactive.HypermediaWebFilter.*;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.server.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.server.core.TemplateVariableAwareLinkBuilderSupport;
|
||||
import org.springframework.hateoas.server.core.WebHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Utility for building reactive {@link Link}s.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Drotbohm
|
||||
* @since 1.0
|
||||
*/
|
||||
public class WebFluxLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<WebFluxLinkBuilder> {
|
||||
|
||||
private WebFluxLinkBuilder(UriComponentsBuilder builder, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(builder, variables, affordances);
|
||||
}
|
||||
|
||||
private WebFluxLinkBuilder(UriComponents components, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(components, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link WebFluxLinkBuilder} by checking if the Reactor Context contains a {@link ServerWebExchange} and
|
||||
* using that combined with the Spring Web annotations to build a full URI. If there is no exchange, then fall back to
|
||||
* relative URIs. Usually used with {@link #methodOn(Class, Object...)} to refer to a method invocation.
|
||||
*
|
||||
* @param invocation must not be {@literal null}.
|
||||
* @see #methodOn(Class, Object...)
|
||||
*/
|
||||
public static WebFluxBuilder linkTo(Object invocation) {
|
||||
|
||||
Assert.notNull(invocation, "Invocation must not be null!");
|
||||
|
||||
return new WebFluxBuilder(linkToInternal(invocation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link WebFluxLinkBuilder} using an explicitly defined {@link ServerWebExchange}. This is possible if your
|
||||
* WebFlux method includes the exchange and you want to pass it straight in.
|
||||
*
|
||||
* @param invocation must not be {@literal null}.
|
||||
* @param exchange must not be {@literal null}.
|
||||
*/
|
||||
public static WebFluxBuilder linkTo(Object invocation, ServerWebExchange exchange) {
|
||||
return new WebFluxBuilder(linkToInternal(invocation, exchange));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link DummyInvocationUtils#methodOn(Class, Object...)} to be available in case you work with static
|
||||
* imports of {@link WebFluxLinkBuilder}.
|
||||
*
|
||||
* @param controller must not be {@literal null}.
|
||||
* @param parameters parameters to extend template variables in the type level mapping.
|
||||
* @return
|
||||
*/
|
||||
public static <T> T methodOn(Class<T> controller, Object... parameters) {
|
||||
return DummyInvocationUtils.methodOn(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List, org.springframework.hateoas.TemplateVariables)
|
||||
*/
|
||||
@Override
|
||||
protected WebFluxLinkBuilder createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
TemplateVariables variables) {
|
||||
return new WebFluxLinkBuilder(builder, variables, affordances);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.LinkBuilderSupport#getThis()
|
||||
*/
|
||||
@Override
|
||||
protected WebFluxLinkBuilder getThis() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class WebFluxBuilder {
|
||||
|
||||
private final Mono<WebFluxLinkBuilder> builder;
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebFluxLink} for the {@link Link} with the given {@link LinkRelation}
|
||||
*
|
||||
* @param relation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public WebFluxLink withRel(LinkRelation relation) {
|
||||
return new WebFluxLink(builder.map(it -> it.withRel(relation)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebFluxLink} for the {@link Link} with the given link relation.
|
||||
*
|
||||
* @param relation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public WebFluxLink withRel(String relation) {
|
||||
return new WebFluxLink(builder.map(it -> it.withRel(relation)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebFluxLink} for the {@link Link} with the {@link IanaLinkRelations#SELF}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public WebFluxLink withSelfRel() {
|
||||
return new WebFluxLink(builder.map(WebFluxLinkBuilder::withSelfRel));
|
||||
}
|
||||
|
||||
/**
|
||||
* General callback to produce a {@link Link} from the given {@link WebFluxLinkBuilder}.
|
||||
*
|
||||
* @param finisher must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public WebFluxLink toLink(Function<WebFluxLinkBuilder, Mono<Link>> finisher) {
|
||||
|
||||
Assert.notNull(finisher, "Finisher must not be null!");
|
||||
|
||||
return new WebFluxLink(builder.flatMap(finisher));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intermediate representation of a {@link Link} within a reactive pipeline to easily add {@link Affordance}s from
|
||||
* method invocations.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public static class WebFluxLink {
|
||||
|
||||
private final Mono<Link> link;
|
||||
|
||||
/**
|
||||
* Adds the affordance created by the given virtual method invocation.
|
||||
*
|
||||
* @param invocation must not be {@literal null}.
|
||||
* @return
|
||||
* @see WebFluxLinkBuilder#methodOn(Class, Object...)
|
||||
*/
|
||||
public WebFluxLink andAffordance(Object invocation) {
|
||||
|
||||
Assert.notNull(invocation, "Invocation must not be null!");
|
||||
|
||||
return new WebFluxLink(link.flatMap(it -> linkToInternal(invocation) //
|
||||
.flatMapIterable(WebFluxLinkBuilder::getAffordances) //
|
||||
.singleOrEmpty() //
|
||||
.map(it::andAffordance)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebFluxLink} with the current {@link Link} instance transformed using the given mapper.
|
||||
*
|
||||
* @param mapper must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public WebFluxLink map(Function<Link, Link> mapper) {
|
||||
|
||||
Assert.notNull(mapper, "Function must not be null!");
|
||||
|
||||
return new WebFluxLink(link.map(mapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link Mono} of {@link Link} for further handling within a reactive pipeline.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Mono<Link> toMono() {
|
||||
return link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Mono} of {@link Link} with the current one augmented by the given {@link Function}. Allows
|
||||
* immediate customization of the {@link Link} instance and immediately return to a general reactive API.
|
||||
*
|
||||
* @param finisher must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Mono<Link> toMono(Function<Link, Link> finisher) {
|
||||
|
||||
Assert.notNull(finisher, "Function must not be null!");
|
||||
|
||||
return link.map(finisher);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} obtained from the {@link ServerWebExchange}.
|
||||
*
|
||||
* @param exchange
|
||||
*/
|
||||
private static UriComponentsBuilder getBuilder(ServerWebExchange exchange) {
|
||||
|
||||
return exchange == null //
|
||||
? UriComponentsBuilder.fromPath("/") //
|
||||
: UriComponentsBuilder.fromHttpRequest(exchange.getRequest());
|
||||
}
|
||||
|
||||
private static Mono<WebFluxLinkBuilder> linkToInternal(Object invocation) {
|
||||
|
||||
return Mono.subscriberContext() //
|
||||
.flatMap(context -> linkToInternal(invocation, context.getOrDefault(SERVER_WEB_EXCHANGE, null)));
|
||||
}
|
||||
|
||||
private static Mono<WebFluxLinkBuilder> linkToInternal(Object invocation, ServerWebExchange exchange) {
|
||||
|
||||
return Mono.just(WebHandler.linkTo(invocation, //
|
||||
path -> getBuilder(exchange).replacePath(path == null ? "/" : path), //
|
||||
WebFluxLinkBuilder::new));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user