| /* |
| * 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 freemarker.core; |
| |
| import java.io.IOException; |
| |
| import freemarker.template.Template; |
| import freemarker.template.TemplateDateModel; |
| import freemarker.template.TemplateException; |
| |
| /** |
| * Not yet public; subject to change. |
| * |
| * <p> |
| * Known compatibility risks when using this post-processor: |
| * <ul> |
| * <li>{@link TemplateDateModel}-s that care to explicitly check if their nested content is {@code null} might start to |
| * complain that you have specified a body despite that the directive doesn't support that. Directives should use |
| * {@link NestedContentNotSupportedException#check(freemarker.template.TemplateDirectiveBody)} instead of a simple |
| * {@code null}-check to avoid this problem.</li> |
| * <li> |
| * Software that uses {@link DirectiveCallPlace#isNestedOutputCacheable()} will always get {@code false}, because |
| * interruption checks ({@link ThreadInterruptionCheck} elements) are, obviously, not cacheable. This should only |
| * impact the performance. |
| * <li> |
| * Software that investigates the AST will see the injected {@link ThreadInterruptionCheck} elements. As of this |
| * writing the AST API-s aren't published, also such software need to be able to deal with new kind of elements |
| * anyway, so this shouldn't be a problem. |
| * </ul> |
| */ |
| class ThreadInterruptionSupportTemplatePostProcessor extends TemplatePostProcessor { |
| |
| @Override |
| public void postProcess(Template t) throws TemplatePostProcessorException { |
| final TemplateElement te = t.getRootTreeNode(); |
| addInterruptionChecks(te); |
| } |
| |
| private void addInterruptionChecks(final TemplateElement te) throws TemplatePostProcessorException { |
| if (te == null) { |
| return; |
| } |
| |
| final int regulatedChildrenCount = te.getChildCount(); |
| for (int i = 0; i < regulatedChildrenCount; i++) { |
| addInterruptionChecks(te.getChild(i)); |
| } |
| |
| if (te.isNestedBlockRepeater()) { |
| try { |
| te.addChild(0, new ThreadInterruptionCheck(te)); |
| } catch (ParseException e) { |
| throw new TemplatePostProcessorException("Unexpected error; see cause", e); |
| } |
| } |
| } |
| |
| /** |
| * Check if the current thread's "interrupted" flag is set, and throws |
| * {@link TemplateProcessingThreadInterruptedException} if it is. We inject this to some points in the AST. |
| */ |
| static class ThreadInterruptionCheck extends TemplateElement { |
| |
| private ThreadInterruptionCheck(TemplateElement te) throws ParseException { |
| setLocation(te.getTemplate(), te.beginColumn, te.beginLine, te.beginColumn, te.beginLine); |
| } |
| |
| @Override |
| TemplateElement[] accept(Environment env) throws TemplateException, IOException { |
| // As the API doesn't allow throwing InterruptedException here (nor anywhere else, most importantly, |
| // Template.process can't throw it), we must not clear the "interrupted" flag of the thread. |
| if (Thread.currentThread().isInterrupted()) { |
| throw new TemplateProcessingThreadInterruptedException(); |
| } |
| return null; |
| } |
| |
| @Override |
| protected String dump(boolean canonical) { |
| return canonical ? "" : "<#--" + getNodeTypeSymbol() + "--#>"; |
| } |
| |
| @Override |
| String getNodeTypeSymbol() { |
| return "##threadInterruptionCheck"; |
| } |
| |
| @Override |
| int getParameterCount() { |
| return 0; |
| } |
| |
| @Override |
| Object getParameterValue(int idx) { |
| throw new IndexOutOfBoundsException(); |
| } |
| |
| @Override |
| ParameterRole getParameterRole(int idx) { |
| throw new IndexOutOfBoundsException(); |
| } |
| |
| @Override |
| boolean isNestedBlockRepeater() { |
| return false; |
| } |
| |
| } |
| |
| /** |
| * Indicates that the template processing thread's "interrupted" flag was found to be set. |
| * |
| * <p>ATTENTION: This is used by https://github.com/kenshoo/freemarker-online. Don't break backward |
| * compatibility without updating that project too! |
| */ |
| static class TemplateProcessingThreadInterruptedException extends RuntimeException { |
| |
| TemplateProcessingThreadInterruptedException() { |
| super("Template processing thread \"interrupted\" flag was set."); |
| } |
| |
| } |
| |
| } |