/**
* 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 io.neba.core.mvc;
import io.neba.api.annotations.ResourceParam;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.sling.api.resource.ResourceUtil.isNonExistingResource;
/**
* Supports {@link io.neba.api.annotations.ResourceModel} arguments of a
* {@link org.springframework.web.bind.annotation.RequestMapping}.
* <br />
*
* Example:<br />
* <p>
* <pre>
* @{@link org.springframework.web.bind.annotation.RequestMapping}(...)
* public void myHandlerMethod(@{@link io.neba.api.annotations.ResourceParam} MyModel model, ...) {
* ...
* }
* </pre>
* </p>
*
* This will expect a String parameter "model", which is a path to a JCR resource adapting to "MyModel". This argument resolver
* will resolve the path and adapt to the desired model. Unless the resource param is not
* {@link io.neba.api.annotations.ResourceParam#required() required}, an exception will be thrown
* if the parameter is missing, the path is unresolvable or the resource cannot be adapted to the desired model,
* i.e. the resulting model instance is guaranteed not to be <code>null</code>.
*
* @author Olaf Otto
*/
public class ResourceParamArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return getParameterAnnotation(parameter) != null;
}
private ResourceParam getParameterAnnotation(MethodParameter parameter) {
return parameter.getParameterAnnotation(ResourceParam.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer container,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
final Object nativeRequest = webRequest.getNativeRequest();
if (!(nativeRequest instanceof SlingHttpServletRequest)) {
throw new IllegalStateException("Expected a " + SlingHttpServletRequest.class.getName() +
" request, but got: " + nativeRequest + ".");
}
final SlingHttpServletRequest request = (SlingHttpServletRequest) nativeRequest;
final ResourceParam resourceParam = getParameterAnnotation(parameter);
final String parameterName = resolveParameterName(parameter, resourceParam);
final boolean required = resourceParam.required() && isEmpty(resourceParam.defaultValue());
final String resourcePath = resolveResourcePath(request, resourceParam, parameterName, required);
if (resourcePath == null) {
return null;
}
// We must resolve (and not use getResource()) as the resource path may be mapped.
ResourceResolver resolver = request.getResourceResolver();
Resource resource = resolver.resolve(request, resourcePath);
if (resource == null || isNonExistingResource(resource)) {
if (required) {
throw new UnresolvableResourceException("Unable to resolve resource " + resourcePath +
" for the required parameter '" + parameterName + "'.");
}
return null;
}
if (parameter.getParameterType().isAssignableFrom(Resource.class)) {
return resource;
}
Object adapted = resource.adaptTo(parameter.getParameterType());
if (adapted == null && required) {
throw new MissingAdapterException("Unable to adapt " + resource + " to " + parameter.getParameterType() +
" for required parameter '" + parameterName + "'.");
}
return adapted;
}
private String resolveResourcePath(SlingHttpServletRequest request,
ResourceParam resourceParam,
String parameterName,
boolean required) throws MissingServletRequestParameterException {
String resourcePath = request.getParameter(parameterName);
if (isEmpty(resourcePath)) {
resourcePath = resourceParam.defaultValue();
}
if (isEmpty(resourcePath)) {
if (required) {
throw new MissingServletRequestParameterException(parameterName, String.class.getSimpleName());
}
return null;
}
if (!isEmpty(resourceParam.append())) {
resourcePath += resourceParam.append();
}
return resourcePath;
}
private String resolveParameterName(MethodParameter parameter, ResourceParam param) {
String parameterName = param.value();
if (isEmpty(parameterName)) {
parameterName = parameter.getParameterName();
}
return parameterName;
}
}