Java tutorial
/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package com.xpn.xwiki.internal.template; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.apache.commons.io.IOUtils; import org.apache.velocity.VelocityContext; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.component.manager.ComponentLookupException; import org.xwiki.component.manager.ComponentManager; import org.xwiki.configuration.ConfigurationSource; import org.xwiki.environment.Environment; import org.xwiki.filter.input.InputSource; import org.xwiki.filter.input.InputStreamInputSource; import org.xwiki.filter.input.ReaderInputSource; import org.xwiki.filter.input.StringInputSource; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.LocalDocumentReference; import org.xwiki.properties.BeanManager; import org.xwiki.properties.PropertyException; import org.xwiki.properties.RawProperties; import org.xwiki.properties.annotation.PropertyId; import org.xwiki.rendering.block.Block; import org.xwiki.rendering.block.GroupBlock; import org.xwiki.rendering.block.RawBlock; import org.xwiki.rendering.block.VerbatimBlock; import org.xwiki.rendering.block.WordBlock; import org.xwiki.rendering.block.XDOM; import org.xwiki.rendering.internal.transformation.MutableRenderingContext; import org.xwiki.rendering.parser.ContentParser; import org.xwiki.rendering.renderer.BlockRenderer; import org.xwiki.rendering.renderer.printer.WikiPrinter; import org.xwiki.rendering.renderer.printer.WriterWikiPrinter; import org.xwiki.rendering.syntax.Syntax; import org.xwiki.rendering.transformation.RenderingContext; import org.xwiki.rendering.transformation.TransformationContext; import org.xwiki.rendering.transformation.TransformationManager; import org.xwiki.velocity.VelocityEngine; import org.xwiki.velocity.VelocityManager; import com.xpn.xwiki.XWiki; import com.xpn.xwiki.internal.skin.AbstractEnvironmentResource; import com.xpn.xwiki.internal.skin.Resource; import com.xpn.xwiki.internal.skin.Skin; import com.xpn.xwiki.internal.skin.SkinManager; import com.xpn.xwiki.internal.skin.WikiResource; import com.xpn.xwiki.user.api.XWikiRightService; /** * Internal toolkit to experiment on wiki bases templates. * * @version $Id: 0b6f90266ef5bcfac607295b63c9adf18ac5c20f $ * @since 6.3M2 */ @Component(roles = TemplateManager.class) @Singleton public class TemplateManager { static final LocalDocumentReference SKINCLASS_REFERENCE = new LocalDocumentReference("XWiki", "XWikiSkins"); private static final Pattern PROPERTY_LINE = Pattern.compile("^##!(.+)=(.*)$\r?\n?", Pattern.MULTILINE); /** * The reference of the superadmin user. */ private static final DocumentReference SUPERADMIN_REFERENCE = new DocumentReference("xwiki", XWiki.SYSTEM_SPACE, XWikiRightService.SUPERADMIN_USER); @Inject private Environment environment; @Inject private ContentParser parser; @Inject private VelocityManager velocityManager; /** * Used to execute transformations. */ @Inject private TransformationManager transformationManager; @Inject @Named("context") private Provider<ComponentManager> componentManagerProvider; @Inject private RenderingContext renderingContext; @Inject @Named("plain/1.0") private BlockRenderer plainRenderer; @Inject @Named("xwikicfg") private ConfigurationSource xwikicfg; @Inject @Named("all") private ConfigurationSource allConfiguration; @Inject @Named("currentmixed") private DocumentReferenceResolver<String> currentMixedDocumentReferenceResolver; @Inject private BeanManager beanManager; @Inject private SUExecutor suExecutor; @Inject private SkinManager skins; @Inject private Logger logger; private static abstract class AbtractTemplate<T extends TemplateContent, R extends Resource<?>> implements Template { protected R resource; protected T content; public AbtractTemplate(R resource) { this.resource = resource; } @Override public String getId() { return this.resource.getId(); } @Override public String getPath() { return this.resource.getPath(); } @Override public TemplateContent getContent() throws Exception { if (this.content == null) { // TODO: work with streams instead of forcing String String strinContent; try (InputSource source = this.resource.getInputSource()) { if (source instanceof StringInputSource) { strinContent = source.toString(); } else if (source instanceof ReaderInputSource) { strinContent = IOUtils.toString(((ReaderInputSource) source).getReader()); } else if (source instanceof InputStreamInputSource) { // It's impossible to know the real attachment encoding, but let's assume that they respect the // standard and use UTF-8 (which is required for the files located on the filesystem) strinContent = IOUtils.toString(((InputStreamInputSource) source).getInputStream()); } else { return null; } } this.content = getContentInternal(strinContent); } return this.content; } protected abstract T getContentInternal(String content) throws Exception; } private class EnvironmentTemplate extends AbtractTemplate<FilesystemTemplateContent, AbstractEnvironmentResource> { public EnvironmentTemplate(AbstractEnvironmentResource resource) { super(resource); } @Override protected FilesystemTemplateContent getContentInternal(String content) { return new FilesystemTemplateContent(content); } } private class DefaultTemplate extends AbtractTemplate<DefaultTemplateContent, Resource<?>> { public DefaultTemplate(Resource<?> resource) { super(resource); } @Override protected DefaultTemplateContent getContentInternal(String content) { if (this.resource instanceof WikiResource) { return new DefaultTemplateContent(content, ((WikiResource<?>) this.resource).getAuthorReference()); } else { return new DefaultTemplateContent(content); } } } private class DefaultTemplateContent implements RawProperties, TemplateContent { // TODO: work with streams instead protected String content; protected boolean authorProvided; protected DocumentReference authorReference; @PropertyId("source.syntax") public Syntax sourceSyntax; @PropertyId("raw.syntax") public Syntax rawSyntax; public Map<String, Object> properties = new HashMap<String, Object>(); public DefaultTemplateContent(String content) { this.content = content; init(); } public DefaultTemplateContent(String content, DocumentReference authorReference) { this(content); setAuthorReference(authorReference); } protected void init() { Matcher matcher = PROPERTY_LINE.matcher(this.content); Map<String, String> properties = new HashMap<String, String>(); while (matcher.find()) { String key = matcher.group(1); String value = matcher.group(2); properties.put(key, value); // Remove the line from the content this.content = this.content.substring(matcher.end()); } try { TemplateManager.this.beanManager.populate(this, properties); } catch (PropertyException e) { // Should never happen TemplateManager.this.logger.error("Failed to populate properties of template", e); } // The default is xhtml to support old templates if (this.rawSyntax == null && this.sourceSyntax == null) { this.rawSyntax = Syntax.XHTML_1_0; } } @Override public String getContent() { return this.content; } @PropertyId("author") @Override public DocumentReference getAuthorReference() { return this.authorReference; } protected void setAuthorReference(DocumentReference authorReference) { this.authorReference = authorReference; this.authorProvided = true; } // RawProperties @Override public void set(String propertyName, Object value) { this.properties.put(propertyName, value); } } private class FilesystemTemplateContent extends DefaultTemplateContent { public FilesystemTemplateContent(String content) { super(content); } /** * {@inheritDoc} * <p> * Allow filesystem template to indicate the user to executed them with. * * @see #setAuthorReference(DocumentReference) */ @Override public void setAuthorReference(DocumentReference authorReference) { super.setAuthorReference(authorReference); } /** * Made public to be seen as bean property. * * @since 6.3.1, 6.4M1 */ @SuppressWarnings("unused") public boolean isPrivileged() { return SUPERADMIN_REFERENCE.equals(getAuthorReference()); } /** * Made public to be seen as bean property. * * @since 6.3.1, 6.4M1 */ @SuppressWarnings("unused") public void setPrivileged(boolean privileged) { if (privileged) { setAuthorReference(SUPERADMIN_REFERENCE); } } } private String getResourcePath(String suffixPath, String templateName, boolean testExist) { String templatePath = suffixPath + templateName; // Prevent inclusion of templates from other directories String normalizedTemplate = URI.create(templatePath).normalize().toString(); if (!normalizedTemplate.startsWith(suffixPath)) { this.logger.warn("Direct access to template file [{}] refused. Possible break-in attempt!", normalizedTemplate); return null; } if (testExist) { // Check if the resource exist if (this.environment.getResource(templatePath) == null) { return null; } } return templatePath; } private void renderError(Throwable throwable, Writer writer) { XDOM xdom = generateError(throwable); render(xdom, writer); } private XDOM generateError(Throwable throwable) { List<Block> errorBlocks = new ArrayList<Block>(); // Add short message Map<String, String> errorBlockParams = Collections.singletonMap("class", "xwikirenderingerror"); errorBlocks.add(new GroupBlock(Arrays.<Block>asList(new WordBlock("Failed to render step content")), errorBlockParams)); // Add complete error StringWriter writer = new StringWriter(); throwable.printStackTrace(new PrintWriter(writer)); Block descriptionBlock = new VerbatimBlock(writer.toString(), false); Map<String, String> errorDescriptionBlockParams = Collections.singletonMap("class", "xwikirenderingerrordescription hidden"); errorBlocks.add(new GroupBlock(Arrays.asList(descriptionBlock), errorDescriptionBlockParams)); return new XDOM(errorBlocks); } private void transform(Block block) { TransformationContext txContext = new TransformationContext( block instanceof XDOM ? (XDOM) block : new XDOM(Arrays.asList(block)), this.renderingContext.getDefaultSyntax(), this.renderingContext.isRestricted()); txContext.setId(this.renderingContext.getTransformationId()); txContext.setTargetSyntax(getTargetSyntax()); try { this.transformationManager.performTransformations(block, txContext); } catch (Exception e) { throw new RuntimeException(e); } } /** * @param templateName the template to parse * @return the result of the template parsing */ public XDOM getXDOMNoException(String templateName) { XDOM xdom; try { xdom = getXDOM(templateName); } catch (Throwable e) { xdom = generateError(e); } return xdom; } private XDOM getXDOM(Template template) throws Exception { XDOM xdom; if (template != null) { DefaultTemplateContent content = (DefaultTemplateContent) template.getContent(); xdom = getXDOM(template, content); } else { xdom = new XDOM(Collections.<Block>emptyList()); } return xdom; } private XDOM getXDOM(Template template, DefaultTemplateContent content) throws Exception { XDOM xdom; if (content.sourceSyntax != null) { xdom = this.parser.parse(content.content, content.sourceSyntax); } else { String result = evaluateContent(template, content); xdom = new XDOM(Arrays.asList(new RawBlock(result, content.rawSyntax))); } return xdom; } public XDOM getXDOM(String templateName) throws Exception { Template template = getTemplate(templateName); return getXDOM(template); } public String renderNoException(String template) { Writer writer = new StringWriter(); renderNoException(template, writer); return writer.toString(); } public void renderNoException(String template, Writer writer) { try { render(template, writer); } catch (Exception e) { renderError(e, writer); } } public String render(String template) throws Exception { return renderFromSkin(template, (Skin) null); } public String renderFromSkin(String template, String skinId) throws Exception { Skin skin = this.skins.getSkin(skinId); return skin != null ? renderFromSkin(template, skin) : null; } public String renderFromSkin(String template, Skin skin) throws Exception { Writer writer = new StringWriter(); renderFromSkin(template, skin, writer); return writer.toString(); } public void render(String template, Writer writer) throws Exception { renderFromSkin(template, null, writer); } public void renderFromSkin(final String templateName, Skin skin, final Writer writer) throws Exception { final Template template = skin != null ? getTemplate(templateName, skin) : getTemplate(templateName); if (template != null) { final DefaultTemplateContent content = (DefaultTemplateContent) template.getContent(); if (content.authorProvided) { this.suExecutor.call(new Callable<Void>() { @Override public Void call() throws Exception { render(template, content, writer); return null; } }, content.getAuthorReference()); } else { render(template, content, writer); } } } public void render(Template template, Writer writer) throws Exception { DefaultTemplateContent content = (DefaultTemplateContent) template.getContent(); render(template, content, writer); } private void render(Template template, DefaultTemplateContent content, Writer writer) throws Exception { if (content.sourceSyntax != null) { XDOM xdom = execute(template, content); render(xdom, writer); } else { evaluateContent(template, content, writer); } } private void render(XDOM xdom, Writer writer) { WikiPrinter printer = new WriterWikiPrinter(writer); BlockRenderer blockRenderer; try { blockRenderer = this.componentManagerProvider.get().getInstance(BlockRenderer.class, getTargetSyntax().toIdString()); } catch (ComponentLookupException e) { blockRenderer = this.plainRenderer; } blockRenderer.render(xdom, printer); } public XDOM executeNoException(String template) { XDOM xdom; try { xdom = execute(template); } catch (Throwable e) { xdom = generateError(e); } return xdom; } private XDOM execute(Template template, DefaultTemplateContent content) throws Exception { XDOM xdom = getXDOM(template, content); transform(xdom); return xdom; } public XDOM execute(String templateName) throws Exception { final Template template = getTemplate(templateName); if (template != null) { final DefaultTemplateContent content = (DefaultTemplateContent) template.getContent(); if (content.authorProvided) { return this.suExecutor.call(new Callable<XDOM>() { @Override public XDOM call() throws Exception { return execute(template, content); } }, content.getAuthorReference()); } else { return execute(template, content); } } return null; } private String evaluateContent(Template template, DefaultTemplateContent content) throws Exception { Writer writer = new StringWriter(); evaluateContent(template, content, writer); return writer.toString(); } private void evaluateContent(Template template, DefaultTemplateContent content, Writer writer) throws Exception { VelocityContext velocityContext = this.velocityManager.getVelocityContext(); // Use the Transformation id as the name passed to the Velocity Engine. This name is used internally // by Velocity as a cache index key for caching macros. String namespace = this.renderingContext.getTransformationId(); boolean renderingContextPushed = false; if (namespace == null) { namespace = template.getId() != null ? template.getId() : "unknown namespace"; if (this.renderingContext instanceof MutableRenderingContext) { // Make the current velocity template id available ((MutableRenderingContext) this.renderingContext).push(this.renderingContext.getTransformation(), this.renderingContext.getXDOM(), this.renderingContext.getDefaultSyntax(), namespace, this.renderingContext.isRestricted(), this.renderingContext.getTargetSyntax()); renderingContextPushed = true; } } VelocityEngine velocityEngine = this.velocityManager.getVelocityEngine(); velocityEngine.startedUsingMacroNamespace(namespace); try { velocityEngine.evaluate(velocityContext, writer, namespace, content.content); } finally { velocityEngine.stoppedUsingMacroNamespace(namespace); // Get rid of temporary rendering context if (renderingContextPushed) { ((MutableRenderingContext) this.renderingContext).pop(); } } } private Syntax getTargetSyntax() { Syntax targetSyntax = this.renderingContext.getTargetSyntax(); return targetSyntax != null ? targetSyntax : Syntax.PLAIN_1_0; } private EnvironmentTemplate getFileSystemTemplate(String suffixPath, String templateName) { String path = getResourcePath(suffixPath, templateName, true); return path != null ? new EnvironmentTemplate(new TemplateEnvironmentResource(path, templateName, this.environment)) : null; } private Template createTemplate(Resource<?> resource) { Template template; if (resource instanceof AbstractEnvironmentResource) { template = new EnvironmentTemplate((AbstractEnvironmentResource) resource); } else { template = new DefaultTemplate(resource); } return template; } public Template getSkinTemplate(String templateName, Skin skin) { Resource<?> resource = skin.getLocalResource(templateName); if (resource != null) { return createTemplate(resource); } return null; } public Template getTemplate(String templateName, Skin skin) { Resource<?> resource = skin.getResource(templateName); if (resource != null) { return createTemplate(resource); } return null; } public Template getTemplate(String templateName) { Template template = null; // Try from skin Skin skin = this.skins.getCurrentSkin(false); if (skin != null) { template = getTemplate(templateName, skin); } // Try from base skin if no skin is set if (skin == null) { if (template == null) { Skin baseSkin = this.skins.getCurrentParentSkin(false); if (baseSkin != null) { template = getTemplate(templateName, baseSkin); } } } // Try from /template/ resources if (template == null) { template = getFileSystemTemplate("/templates/", templateName); } return template; } }