/*
* Copyright 2012 Guido Steinacker
*
* 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 de.otto.jsonhome.generator;
import de.otto.jsonhome.annotation.Href;
import de.otto.jsonhome.annotation.HrefTemplate;
import de.otto.jsonhome.annotation.Rel;
import de.otto.jsonhome.model.Hints;
import de.otto.jsonhome.model.ResourceLink;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.List;
import static de.otto.jsonhome.generator.MethodHelper.getParameterInfos;
import static de.otto.jsonhome.generator.UriBuilder.normalized;
import static de.otto.jsonhome.model.DirectLink.directLink;
import static de.otto.jsonhome.model.TemplatedLink.templatedLink;
import static java.net.URI.create;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
/**
* A generator used to generate the {@link ResourceLink resource links} of a method.
*
* @author Guido Steinacker
* @since 17.10.12
*/
public abstract class ResourceLinkGenerator {
private final URI applicationBaseUri;
private final URI relationTypeBaseUri;
private final URI varTypeBaseUri;
private final HintsGenerator hintsGenerator;
private final HrefVarsGenerator hrefVarsGenerator;
/**
* Generator used to generate {@link ResourceLink} instances.
*
* @param applicationBaseUri base URI of the application. This is used to generate HREFs and HREF-TEMPLATES.
* @param relationTypeBaseUri base URI of all link-relation types.
* @param varTypeBaseUri base URI used to generate var-types, identifying the semantics of variables used in href-templates.
* @param hintsGenerator generator used to create {@link Hints}
* @param hrefVarsGenerator generator used to create {@link de.otto.jsonhome.model.HrefVar HrefVars}.
*/
protected ResourceLinkGenerator(final URI applicationBaseUri,
final URI relationTypeBaseUri,
final URI varTypeBaseUri,
final HintsGenerator hintsGenerator,
final HrefVarsGenerator hrefVarsGenerator) {
this.applicationBaseUri = normalized(applicationBaseUri).toUri();
this.relationTypeBaseUri = normalized(relationTypeBaseUri).toUri();
this.varTypeBaseUri = varTypeBaseUri != null ? normalized(varTypeBaseUri).toUri() : null;
this.hintsGenerator = hintsGenerator;
this.hrefVarsGenerator = hrefVarsGenerator;
}
/**
* Analyzes a method of a controller and returns the list of ResourceLinks of this method.
*
* @param method the method
* @return resource link or null.
*/
public ResourceLink resourceLinkFor(final Method method) {
ResourceLink resourceLink = null;
if (isCandidateForAnalysis(method)) {
final String resourcePath = overriddenOrCalculatedResourcePathFor(method);
final URI relationType = relationTypeFrom(method);
final URI varTypeBaseUri = this.varTypeBaseUri != null ? this.varTypeBaseUri : relationType;
if (relationType != null) {
final Hints hints = hintsGenerator.hintsOf(relationType, method);
if (resourcePath.matches(".*\\{.*\\}.*")) {
resourceLink = templatedLink(
relationType,
resourcePath,
hrefVarsGenerator.hrefVarsFor(varTypeBaseUri, this.varTypeBaseUri == null, method),
hints
);
} else {
resourceLink = directLink(
relationType,
create(resourcePath),
hints
);
}
}
}
return resourceLink;
}
/**
* Calculates the resource path (direct links or templated links) of the method.
*
* @param method the Method
* @return resource path or null.
*/
protected String overriddenOrCalculatedResourcePathFor(final Method method) {
final Href methodHrefAnnotation = findAnnotation(method, Href.class);
if (methodHrefAnnotation != null) {
return applicationBaseUri.resolve(methodHrefAnnotation.value()).toString();
} else {
final HrefTemplate hrefTemplateAnnotation = findAnnotation(method, HrefTemplate.class);
if (hrefTemplateAnnotation != null) {
return hrefTemplateAnnotation.value().startsWith("http://")
? hrefTemplateAnnotation.value()
: normalized(applicationBaseUri).withPathSegment(hrefTemplateAnnotation.value()).toString();
} else {
return resourcePathFor(method);
}
}
}
/**
* Parses the method parameter annotations, looking for parameters annotated as @RequestParam, and returns the
* query part of a href-template.
*
* @param method method, optionally having parameters annotated as being a @RequestParam.
* @return query part of a href-template, like {?param1,param2,param3}
*/
protected String queryTemplateFrom(final Method method) {
final StringBuilder sb = new StringBuilder();
final List<ParameterInfo> parameterInfos = getParameterInfos(method);
boolean first = true;
for (final ParameterInfo parameterInfo : parameterInfos) {
if (hrefVarsGenerator.hasRequestParam(parameterInfo)) {
if (first) {
first = false;
sb.append("{?").append(parameterInfo.getName());
} else {
sb.append(",").append(parameterInfo.getName());
}
}
}
final String s = sb.toString();
return s.isEmpty() ? s : s + "}";
}
/**
* Analyses a method of a controller and returns the fully qualified URI of the link-relation type.
*
* If the neither the method, nor the controller is annotated with Rel, null is returned.
*
* The Rel of the method is overriding the Rel of the Controller.
*
* @param method the method
* @return URI of the link-relation type, or null
*/
protected URI relationTypeFrom(final Method method) {
final Rel controllerRel = findAnnotation(method.getDeclaringClass(), Rel.class);
final Rel methodRel = findAnnotation(method, Rel.class);
if (controllerRel == null && methodRel == null) {
return null;
} else {
final String linkRelationType = methodRel != null
? methodRel.value()
: controllerRel.value();
if (linkRelationType.startsWith("http://")) {
return create(linkRelationType);
} else {
return normalized(relationTypeBaseUri).withPathSegment(linkRelationType).toUri();
}
}
}
/**
* Returns true if the method is a candidate for further processing, false otherwise.
*
* @param method the method to check
* @return boolean
*/
protected abstract boolean isCandidateForAnalysis(final Method method);
/**
* Returns the resource path for the given method.
*
* If the method is able to handle multiple URIs, the implementation should select the first or
* best URI.
*
* @param method the method of the controller, possibly handling one or more REST resources.
* @return list of resource paths
*/
protected abstract String resourcePathFor(final Method method);
}