/* * 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; } }