blob: 802b8659d1c7cdd58c957014df9dcb692fb2dc80 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.sling.scripting.thymeleaf.internal.processor;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestDispatcherOptions;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.SyntheticResource;
import org.apache.sling.api.scripting.SlingBindings;
import org.apache.sling.scripting.core.servlet.CaptureResponseWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.AttributeName;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
import org.thymeleaf.processor.element.IElementTagStructureHandler;
import org.thymeleaf.standard.expression.IStandardExpression;
import org.thymeleaf.standard.expression.IStandardExpressionParser;
import org.thymeleaf.standard.expression.StandardExpressions;
import org.thymeleaf.templatemode.TemplateMode;
public class SlingIncludeAttributeTagProcessor extends AbstractAttributeTagProcessor {
public static final int ATTRIBUTE_PRECEDENCE = 100;
public static final String ATTRIBUTE_NAME = "include";
public static final String ADD_SELECTORS_ATTRIBUTE_NAME = "addSelectors";
public static final String REPLACE_SELECTORS_ATTRIBUTE_NAME = "replaceSelectors";
public static final String REPLACE_SUFFIX_ATTRIBUTE_NAME = "replaceSuffix";
public static final String RESOURCE_TYPE_ATTRIBUTE_NAME = "resourceType";
public static final String UNWRAP_ATTRIBUTE_NAME = "unwrap";
private final Logger logger = LoggerFactory.getLogger(SlingIncludeAttributeTagProcessor.class);
public SlingIncludeAttributeTagProcessor(final String dialectPrefix) {
super(TemplateMode.HTML, dialectPrefix, null, true, ATTRIBUTE_NAME, true, ATTRIBUTE_PRECEDENCE, true);
}
@Override
protected void doProcess(final ITemplateContext templateContext, final IProcessableElementTag processableElementTag, final AttributeName attributeName, final String attributeValue, final IElementTagStructureHandler elementTagStructureHandler) {
try {
final SlingHttpServletRequest slingHttpServletRequest = (SlingHttpServletRequest) templateContext.getVariable(SlingBindings.REQUEST);
final SlingHttpServletResponse slingHttpServletResponse = (SlingHttpServletResponse) templateContext.getVariable(SlingBindings.RESPONSE);
final IEngineConfiguration configuration = templateContext.getConfiguration();
final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration);
final IStandardExpression expression = expressionParser.parseExpression(templateContext, attributeValue);
final Object include = expression.execute(templateContext);
String path = null;
if (include instanceof String) {
path = (String) include;
}
Resource resource = null;
if (include instanceof Resource) {
resource = (Resource) include;
}
// request dispatcher options
final RequestDispatcherOptions requestDispatcherOptions = prepareRequestDispatcherOptions(expressionParser, templateContext, processableElementTag, elementTagStructureHandler);
// dispatch
final String content = dispatch(resource, path, slingHttpServletRequest, slingHttpServletResponse, requestDispatcherOptions);
// add output
final Boolean unwrap = (Boolean) parseAttribute(expressionParser, templateContext, processableElementTag, elementTagStructureHandler, UNWRAP_ATTRIBUTE_NAME);
if (unwrap != null && unwrap) {
elementTagStructureHandler.replaceWith(content, false);
} else {
elementTagStructureHandler.setBody(content, false);
}
} catch (Exception e) {
throw new RuntimeException("unable to process include attribute", e);
}
}
protected Object parseAttribute(final IStandardExpressionParser expressionParser, final ITemplateContext templateContext, final IProcessableElementTag processableElementTag, final IElementTagStructureHandler elementTagStructureHandler, final String name) {
final String value = processableElementTag.getAttributeValue(getDialectPrefix(), name);
Object result = null;
if (value != null) {
final IStandardExpression expression = expressionParser.parseExpression(templateContext, value);
result = expression.execute(templateContext);
}
elementTagStructureHandler.removeAttribute(getDialectPrefix(), name);
return result;
}
protected RequestDispatcherOptions prepareRequestDispatcherOptions(final IStandardExpressionParser expressionParser, final ITemplateContext templateContext, final IProcessableElementTag processableElementTag, final IElementTagStructureHandler elementTagStructureHandler) {
final String resourceType = (String) parseAttribute(expressionParser, templateContext, processableElementTag, elementTagStructureHandler, RESOURCE_TYPE_ATTRIBUTE_NAME);
final String replaceSelectors = (String) parseAttribute(expressionParser, templateContext, processableElementTag, elementTagStructureHandler, REPLACE_SELECTORS_ATTRIBUTE_NAME);
final String addSelectors = (String) parseAttribute(expressionParser, templateContext, processableElementTag, elementTagStructureHandler, ADD_SELECTORS_ATTRIBUTE_NAME);
final String replaceSuffix = (String) parseAttribute(expressionParser, templateContext, processableElementTag, elementTagStructureHandler, REPLACE_SUFFIX_ATTRIBUTE_NAME);
final RequestDispatcherOptions options = new RequestDispatcherOptions();
options.setForceResourceType(resourceType);
options.setReplaceSelectors(replaceSelectors);
options.setAddSelectors(addSelectors);
options.setReplaceSuffix(replaceSuffix);
return options;
}
/**
* @param resource the resource to include
* @param path the path to include
* @param slingHttpServletRequest the current request
* @param slingHttpServletResponse the current response
* @param requestDispatcherOptions the options for the request dispatcher
* @return the character response from the include call to request dispatcher
* @see "org.apache.sling.scripting.jsp.taglib.IncludeTagHandler"
*/
protected String dispatch(Resource resource, String path, final SlingHttpServletRequest slingHttpServletRequest, final SlingHttpServletResponse slingHttpServletResponse, final RequestDispatcherOptions requestDispatcherOptions) {
// ensure the path (if set) is absolute and normalized
if (path != null) {
if (!path.startsWith("/")) {
path = slingHttpServletRequest.getResource().getPath() + "/" + path;
}
path = ResourceUtil.normalize(path);
}
// check the resource
if (resource == null) {
if (path == null) {
// neither resource nor path is defined, use current resource
resource = slingHttpServletRequest.getResource();
} else {
// check whether the path (would) resolve, else SyntheticRes.
final String resourceType = requestDispatcherOptions.getForceResourceType();
final Resource tmp = slingHttpServletRequest.getResourceResolver().resolve(path);
if (tmp == null && resourceType != null) {
resource = new SyntheticResource(slingHttpServletRequest.getResourceResolver(), path, resourceType); // TODO DispatcherSyntheticResource?
// remove resource type overwrite as synthetic resource is correctly typed as requested
requestDispatcherOptions.remove(RequestDispatcherOptions.OPT_FORCE_RESOURCE_TYPE);
}
}
}
try {
// create a dispatcher for the resource or path
final RequestDispatcher dispatcher;
if (resource != null) {
dispatcher = slingHttpServletRequest.getRequestDispatcher(resource, requestDispatcherOptions);
} else {
dispatcher = slingHttpServletRequest.getRequestDispatcher(path, requestDispatcherOptions);
}
if (dispatcher != null) {
try {
final CaptureResponseWrapper wrapper = new CaptureResponseWrapper(slingHttpServletResponse);
dispatcher.include(slingHttpServletRequest, wrapper);
if (!wrapper.isBinaryResponse()) {
return wrapper.getCapturedCharacterResponse();
}
} catch (ServletException e) {
logger.error(e.getMessage(), e);
}
} else {
logger.error("no request dispatcher: unable to include {}/'{}'", resource, path);
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return null;
}
}