Search in sources :

Example 1 with IElementModelProcessor

use of org.thymeleaf.processor.element.IElementModelProcessor in project thymeleaf by thymeleaf.

the class ProcessorTemplateHandler method handleStandaloneElement.

@Override
public void handleStandaloneElement(final IStandaloneElementTag istandaloneElementTag) {
    /*
         * If processing is stopped, we should queue this for later handling
         * In theory, given the origin of events (parser or cache) should get stopped immediately, this should
         * only happen if a pre-processor is producing additional events.
         */
    if (this.throttleEngine && this.flowController.stopProcessing) {
        queueEvent(istandaloneElementTag);
        return;
    }
    /*
         * CHECK WHETHER WE ARE GATHERING AN ELEMENT's MODEL
         */
    if (!this.modelController.shouldProcessStandaloneElement(istandaloneElementTag)) {
        return;
    }
    /*
         * CAST TO ENGINE-SPECIFIC IMPLEMENTATION, which will ease the handling of the structure during processing
         */
    StandaloneElementTag standaloneElementTag = StandaloneElementTag.asEngineStandaloneElementTag(istandaloneElementTag);
    /*
         * OBTAIN THE CURRENT SYNTHETIC MODEL (if any). This is needed in case this event was previously being handled,
         * then a gathering process started (as a consequence of the execution of one of its processors), and then
         * once the model was gathered the process started again by handling the first event, which was the one
         * suspended. By obtaining the current gathering model here we can reinitialize all the handling variables and
         * flags to their original state before being suspended.
         */
    final IGatheringModelProcessable currentGatheringModel = obtainCurrentGatheringModel();
    /*
         * If we are resuming an execution after suspending it, we want to retire the register of the element tag
         * that was added by the controller. The reason we want this is that the current tag was already registered
         * by the controller when the execution was suspended, and we don't want it duplicated (nor altered).
         */
    if (currentGatheringModel != null && this.engineContext != null) {
        this.engineContext.setElementTag(null);
    }
    /*
         * FAIL FAST in case this tag has no associated processors and we have no reason to pay attention to it
         * anyway (because of having been suspended).
         */
    if (currentGatheringModel == null && !standaloneElementTag.hasAssociatedProcessors()) {
        this.next.handleStandaloneElement(standaloneElementTag);
        if (!this.throttleEngine || !this.flowController.stopProcessing) {
            if (this.engineContext != null) {
                this.engineContext.decreaseLevel();
            }
        } else {
            queueProcessable(this.decreaseContextLevelProcessable);
        }
        return;
    }
    /*
         * DECLARE THE STATE VARS NEEDED FOR PROCESSOR EXECUTION. If we are executing the first event of a gathered
         * model, we will just re-initialize to the original variables, the ones we had before suspending.
         */
    final ProcessorExecutionVars vars = (currentGatheringModel == null ? new ProcessorExecutionVars() : currentGatheringModel.initializeProcessorExecutionVars());
    /*
         * GET THE STRUCTURE HANDLERS INTO LOCAL VARS
         */
    final ElementTagStructureHandler tagStructureHandler = this.elementTagStructureHandler;
    final ElementModelStructureHandler modelStructureHandler = this.elementModelStructureHandler;
    /*
         * EXECUTE PROCESSORS
         */
    IElementProcessor processor;
    while (!vars.discardEvent && (processor = vars.processorIterator.next(standaloneElementTag)) != null) {
        tagStructureHandler.reset();
        modelStructureHandler.reset();
        if (processor instanceof IElementTagProcessor) {
            final IElementTagProcessor elementProcessor = ((IElementTagProcessor) processor);
            elementProcessor.process(this.context, standaloneElementTag, tagStructureHandler);
            // Apply any context modifications made by the processor (local vars, inlining, etc.)
            tagStructureHandler.applyContextModifications(this.engineContext);
            // Apply any modifications to the tag itself: new/removed/replace attributes, etc. Note this
            // creates a new tag object because tag objects are immutable.
            standaloneElementTag = tagStructureHandler.applyAttributes(this.attributeDefinitions, standaloneElementTag);
            if (tagStructureHandler.iterateElement) {
                // Initialize a gathering model
                this.modelController.startGatheringIteratedModel(standaloneElementTag, vars, tagStructureHandler.iterVariableName, tagStructureHandler.iterStatusVariableName, tagStructureHandler.iteratedObject);
                // Obtain the gathered model (this is a standalone tag, so no additional events needed in iteration)
                final IGatheringModelProcessable gatheredModel = this.modelController.getGatheredModel();
                this.modelController.resetGathering();
                // Process the gathering model, or queue for throttled execution
                if (!this.throttleEngine) {
                    gatheredModel.process();
                } else {
                    queueProcessable(gatheredModel);
                }
                // Complete exit of the handler method: no more processing to do from here
                return;
            } else if (tagStructureHandler.setBodyText) {
                // Reset model, we need it clean
                vars.modelAfter = resetModel(vars.modelAfter, true);
                // Prepare the text node that will be added to the queue (which will be suspended)
                final Text text = new Text(tagStructureHandler.setBodyTextValue);
                vars.modelAfter.add(text);
                // If processable, events will be executed by the ProcessorTemplateHandler. If not, by this.next
                vars.modelAfterProcessable = tagStructureHandler.setBodyTextProcessable;
                // Initialize the gathered model object (open+close equivalent to this standalone tag)
                final GatheringModelProcessable equivalentSyntheticModel = this.modelController.createStandaloneEquivalentModel(standaloneElementTag, vars);
                // Fire the now-equivalent events. Note the handleOpenElement event will take care of the suspended queue
                if (!this.throttleEngine) {
                    equivalentSyntheticModel.process();
                } else {
                    queueProcessable(equivalentSyntheticModel);
                }
                // Complete exit of the handler method: no more processing to do from here
                return;
            } else if (tagStructureHandler.setBodyModel) {
                // Reset model, we need it clean
                vars.modelAfter = resetModel(vars.modelAfter, true);
                // Prepare the queue (that we will suspend)
                vars.modelAfter.addModel(tagStructureHandler.setBodyModelValue);
                // If processable, events will be executed by the ProcessorTemplateHandler. If not, by this.next
                vars.modelAfterProcessable = tagStructureHandler.setBodyModelProcessable;
                // Initialize the gathered model object (open+close equivalent to this standalone tag)
                final GatheringModelProcessable equivalentSyntheticModel = this.modelController.createStandaloneEquivalentModel(standaloneElementTag, vars);
                // Fire the now-equivalent events. Note the handleOpenElement event will take care of the suspended queue
                if (!this.throttleEngine) {
                    equivalentSyntheticModel.process();
                } else {
                    queueProcessable(equivalentSyntheticModel);
                }
                // Complete exit of the handler method: no more processing to do from here
                return;
            } else if (tagStructureHandler.insertBeforeModel) {
                // Reset BEFORE model, we need it clean
                vars.modelBefore = resetModel(vars.modelBefore, true);
                // Add model to be passed to this.next BEFORE delegating the event. Note this cannot be processable.
                vars.modelBefore.addModel(tagStructureHandler.insertBeforeModelValue);
            } else if (tagStructureHandler.insertImmediatelyAfterModel) {
                // will not be resetting it because we will be inserting our model at the very beginning of it.
                if (vars.modelAfter == null) {
                    vars.modelAfter = resetModel(vars.modelAfter, true);
                }
                // If processable, events will be executed by the ProcessorTemplateHandler. If not, by this.next
                vars.modelAfterProcessable = tagStructureHandler.insertImmediatelyAfterModelProcessable;
                // Insert the new model
                vars.modelAfter.insertModel(0, tagStructureHandler.insertImmediatelyAfterModelValue);
            } else if (tagStructureHandler.replaceWithText) {
                // Reset model, we need it clean
                vars.modelAfter = resetModel(vars.modelAfter, true);
                // If processable, events will be executed by the ProcessorTemplateHandler. If not, by this.next
                vars.modelAfterProcessable = tagStructureHandler.replaceWithTextProcessable;
                // Create the new replacement Text event and add it to the model
                vars.modelAfter.add(new Text(tagStructureHandler.replaceWithTextValue));
                // This tag, the standalone tag itself, will be replaced, so it has to be removed
                vars.discardEvent = true;
            } else if (tagStructureHandler.replaceWithModel) {
                // Reset model, we need it clean
                vars.modelAfter = resetModel(vars.modelAfter, true);
                // If processable, events will be executed by the ProcessorTemplateHandler. If not, by this.next
                vars.modelAfterProcessable = tagStructureHandler.replaceWithModelProcessable;
                // Add the new replacement model
                vars.modelAfter.addModel(tagStructureHandler.replaceWithModelValue);
                // This tag, the standalone tag itself, will be replaced, so it has to be removed
                vars.discardEvent = true;
            } else if (tagStructureHandler.removeElement) {
                // Reset model, but only if it already exists
                vars.modelAfter = resetModel(vars.modelAfter, false);
                // We are removing the element (the standalone tag), so no further processing will be allowed
                vars.discardEvent = true;
            } else if (tagStructureHandler.removeTags) {
                // No modifications to the queue - it's just the tag that will be removed, not its possible contents
                vars.discardEvent = true;
            }
        // --------------
        // No way to process 'removeBody' or 'removeAllButFirstChild' on a standalone tag
        // --------------
        } else if (processor instanceof IElementModelProcessor) {
            if (!vars.processorIterator.lastWasRepeated()) {
                if ((vars.modelBefore != null && vars.modelBefore.size() > 0) || (vars.modelAfter != null && vars.modelAfter.size() > 0)) {
                    throw new TemplateProcessingException("Cannot execute model processor " + processor.getClass().getName() + " as the body " + "of the target element has already been modified by a previously executed processor " + "on the same tag. Model processors cannot execute on already-modified bodies as these " + "might contain unprocessable events (e.g. as a result of a 'th:text' or similar)", standaloneElementTag.getTemplateName(), standaloneElementTag.getLine(), standaloneElementTag.getCol());
                }
                // Set the processor to be executed again, because this time we will just set the "model gathering" mechanism
                vars.processorIterator.setLastToBeRepeated(standaloneElementTag);
                // Initialize the gathering model, and close it quickly because this is a standalone tag so there
                // is only one event to be gathered.
                this.modelController.startGatheringDelayedModel(standaloneElementTag, vars);
                final IGatheringModelProcessable newModel = this.modelController.getGatheredModel();
                this.modelController.resetGathering();
                // Process the new gathering model (no need to wait for a "close" event, as this is a standalone)
                if (!this.throttleEngine) {
                    newModel.process();
                } else {
                    queueProcessable(newModel);
                }
                // Nothing else to be done by this handler... let's just queue the rest of the events in this element
                return;
            }
            /*
                 * This is not the first time we try to execute this processor, which means the model gathering
                 * process has already taken place.
                 */
            // Create the actual Model instance (a clone) that will be passed to the processor to execute on
            final Model gatheredModel = currentGatheringModel.getInnerModel();
            final Model processedModel = new Model(gatheredModel);
            // Execute the processor on the just-created Model
            ((IElementModelProcessor) processor).process(this.context, processedModel, modelStructureHandler);
            // Apply any context modifications made by the processor (local vars, inlining, etc.)
            modelStructureHandler.applyContextModifications(this.engineContext);
            // Reset the skipbody flags so that the processed model can be executed in the same conditions as the original
            currentGatheringModel.resetGatheredSkipFlags();
            /*
                 * Before making any changes and queue the new model for execution, check that it actually is
                 * a "new" model (the processor might have been no-op on the tag and changes might have been
                 * only on the local variables, for example.)
                 */
            if (!gatheredModel.sameAs(processedModel)) {
                /*
                     * Now we will do the exact equivalent to what is performed for an Element Tag processor, when this
                     * returns a result of type "replaceWithModel".
                     */
                // Reset model
                vars.modelAfter = resetModel(vars.modelAfter, true);
                // Set the model to be executed, and set it to be processable (that is a MUST in this case)
                vars.modelAfter.addModel(processedModel);
                vars.modelAfterProcessable = true;
                // We will discard this event (the standalone one) because we are going to process the new, modified
                // model instead. Note we do not need to set the body to skip or anything because we know this is a
                // standalone tag.
                vars.discardEvent = true;
            }
        } else {
            throw new IllegalStateException("An element has been found with an associated processor of type " + processor.getClass().getName() + " which is neither a Tag Element Processor nor a Model Element Processor.");
        }
    }
    /*
         * QUEUE MODEL HANDLING (IF WE ARE THROTTLING)
         */
    if (this.throttleEngine && ((vars.modelAfter != null && vars.modelAfter.size() > 0) || (vars.modelBefore != null && vars.modelBefore.size() > 0))) {
        queueProcessable(new StandaloneElementTagModelProcessable(standaloneElementTag, vars, this.engineContext, this.modelController, this.flowController, this, this.next));
        return;
    }
    /*
         * PROCESS THE QUEUE BEFORE DELEGATING, if specified to do so
         */
    if (vars.modelBefore != null) {
        // This is never processable
        vars.modelBefore.process(this.next);
    }
    /*
         * PROCESS THE REST OF THE HANDLER CHAIN
         */
    if (!vars.discardEvent) {
        this.next.handleStandaloneElement(standaloneElementTag);
    }
    /*
         * PROCESS THE QUEUE, launching all the queued events
         */
    if (vars.modelAfter != null) {
        vars.modelAfter.process(vars.modelAfterProcessable ? this : this.next);
    }
    /*
         * DECREASE THE CONTEXT LEVEL once we have executed all the processors (and maybe a body if we added
         * one to the tag converting it into an open tag)
         */
    if (!this.throttleEngine || !this.flowController.stopProcessing) {
        if (this.engineContext != null) {
            this.engineContext.decreaseLevel();
        }
    } else {
        queueProcessable(this.decreaseContextLevelProcessable);
    }
}
Also used : IElementTagProcessor(org.thymeleaf.processor.element.IElementTagProcessor) IStandaloneElementTag(org.thymeleaf.model.IStandaloneElementTag) IText(org.thymeleaf.model.IText) IElementProcessor(org.thymeleaf.processor.element.IElementProcessor) IElementModelProcessor(org.thymeleaf.processor.element.IElementModelProcessor) TemplateProcessingException(org.thymeleaf.exceptions.TemplateProcessingException)

Example 2 with IElementModelProcessor

use of org.thymeleaf.processor.element.IElementModelProcessor in project thymeleaf by thymeleaf.

the class ConfigurationPrinterHelper method printProcessorsForTemplateMode.

private static void printProcessorsForTemplateMode(final ConfigLogBuilder logBuilder, final Set<IProcessor> processors, final TemplateMode templateMode) {
    if (processors == null || processors.isEmpty()) {
        return;
    }
    final List<ICDATASectionProcessor> cdataSectionProcessors = new ArrayList<ICDATASectionProcessor>();
    final List<ICommentProcessor> commentProcessors = new ArrayList<ICommentProcessor>();
    final List<IDocTypeProcessor> docTypeProcessors = new ArrayList<IDocTypeProcessor>();
    final List<IElementTagProcessor> elementTagProcessors = new ArrayList<IElementTagProcessor>();
    final List<IElementModelProcessor> elementModelProcessors = new ArrayList<IElementModelProcessor>();
    final List<IProcessingInstructionProcessor> processingInstructionProcessors = new ArrayList<IProcessingInstructionProcessor>();
    final List<ITextProcessor> textProcessors = new ArrayList<ITextProcessor>();
    final List<IXMLDeclarationProcessor> xmlDeclarationProcessors = new ArrayList<IXMLDeclarationProcessor>();
    boolean processorsForTemplateModeExist = false;
    for (final IProcessor processor : processors) {
        if (!templateMode.equals(processor.getTemplateMode())) {
            continue;
        }
        processorsForTemplateModeExist = true;
        if (processor instanceof ICDATASectionProcessor) {
            cdataSectionProcessors.add((ICDATASectionProcessor) processor);
        } else if (processor instanceof ICommentProcessor) {
            commentProcessors.add((ICommentProcessor) processor);
        } else if (processor instanceof IDocTypeProcessor) {
            docTypeProcessors.add((IDocTypeProcessor) processor);
        } else if (processor instanceof IElementTagProcessor) {
            elementTagProcessors.add((IElementTagProcessor) processor);
        } else if (processor instanceof IElementModelProcessor) {
            elementModelProcessors.add((IElementModelProcessor) processor);
        } else if (processor instanceof IProcessingInstructionProcessor) {
            processingInstructionProcessors.add((IProcessingInstructionProcessor) processor);
        } else if (processor instanceof ITextProcessor) {
            textProcessors.add((ITextProcessor) processor);
        } else if (processor instanceof IXMLDeclarationProcessor) {
            xmlDeclarationProcessors.add((IXMLDeclarationProcessor) processor);
        }
    }
    if (!processorsForTemplateModeExist) {
        // Nothing to show, there are no processors for this template mode
        return;
    }
    logBuilder.line("[THYMELEAF]     * Processors for Template Mode: {}", templateMode);
    Collections.sort(cdataSectionProcessors, ProcessorComparators.PROCESSOR_COMPARATOR);
    Collections.sort(commentProcessors, ProcessorComparators.PROCESSOR_COMPARATOR);
    Collections.sort(docTypeProcessors, ProcessorComparators.PROCESSOR_COMPARATOR);
    Collections.sort(elementTagProcessors, ProcessorComparators.PROCESSOR_COMPARATOR);
    Collections.sort(elementModelProcessors, ProcessorComparators.PROCESSOR_COMPARATOR);
    Collections.sort(processingInstructionProcessors, ProcessorComparators.PROCESSOR_COMPARATOR);
    Collections.sort(textProcessors, ProcessorComparators.PROCESSOR_COMPARATOR);
    Collections.sort(xmlDeclarationProcessors, ProcessorComparators.PROCESSOR_COMPARATOR);
    if (!elementTagProcessors.isEmpty()) {
        logBuilder.line("[THYMELEAF]         * Element Tag Processors by [matching element and attribute name] [precedence]:");
        for (final IElementTagProcessor processor : elementTagProcessors) {
            final MatchingElementName matchingElementName = processor.getMatchingElementName();
            final MatchingAttributeName matchingAttributeName = processor.getMatchingAttributeName();
            final String elementName = (matchingElementName == null ? "*" : matchingElementName.toString());
            final String attributeName = (matchingAttributeName == null ? "*" : matchingAttributeName.toString());
            logBuilder.line("[THYMELEAF]             * [{} {}] [{}]: {}", new Object[] { elementName, attributeName, Integer.valueOf(processor.getPrecedence()), processor.getClass().getName() });
        }
    }
    if (!elementModelProcessors.isEmpty()) {
        logBuilder.line("[THYMELEAF]         * Element Model Processors by [matching element and attribute name] [precedence]:");
        for (final IElementModelProcessor processor : elementModelProcessors) {
            final MatchingElementName matchingElementName = processor.getMatchingElementName();
            final MatchingAttributeName matchingAttributeName = processor.getMatchingAttributeName();
            final String elementName = (matchingElementName == null ? "*" : matchingElementName.toString());
            final String attributeName = (matchingAttributeName == null ? "*" : matchingAttributeName.toString());
            logBuilder.line("[THYMELEAF]             * [{} {}] [{}]: {}", new Object[] { elementName, attributeName, Integer.valueOf(processor.getPrecedence()), processor.getClass().getName() });
        }
    }
    if (!textProcessors.isEmpty()) {
        logBuilder.line("[THYMELEAF]         * Text Processors by [precedence]:");
        for (final ITextProcessor processor : textProcessors) {
            logBuilder.line("[THYMELEAF]             * [{}]: {}", new Object[] { Integer.valueOf(processor.getPrecedence()), processor.getClass().getName() });
        }
    }
    if (!docTypeProcessors.isEmpty()) {
        logBuilder.line("[THYMELEAF]         * DOCTYPE Processors by [precedence]:");
        for (final IDocTypeProcessor processor : docTypeProcessors) {
            logBuilder.line("[THYMELEAF]             * [{}]: {}", new Object[] { Integer.valueOf(processor.getPrecedence()), processor.getClass().getName() });
        }
    }
    if (!cdataSectionProcessors.isEmpty()) {
        logBuilder.line("[THYMELEAF]         * CDATA Section Processors by [precedence]:");
        for (final ICDATASectionProcessor processor : cdataSectionProcessors) {
            logBuilder.line("[THYMELEAF]             * [{}]: {}", new Object[] { Integer.valueOf(processor.getPrecedence()), processor.getClass().getName() });
        }
    }
    if (!commentProcessors.isEmpty()) {
        logBuilder.line("[THYMELEAF]         * Comment Processors by [precedence]:");
        for (final ICommentProcessor processor : commentProcessors) {
            logBuilder.line("[THYMELEAF]             * [{}]: {}", new Object[] { Integer.valueOf(processor.getPrecedence()), processor.getClass().getName() });
        }
    }
    if (!xmlDeclarationProcessors.isEmpty()) {
        logBuilder.line("[THYMELEAF]         * XML Declaration Processors by [precedence]:");
        for (final IXMLDeclarationProcessor processor : xmlDeclarationProcessors) {
            logBuilder.line("[THYMELEAF]             * [{}]: {}", new Object[] { Integer.valueOf(processor.getPrecedence()), processor.getClass().getName() });
        }
    }
    if (!processingInstructionProcessors.isEmpty()) {
        logBuilder.line("[THYMELEAF]         * Processing Instruction Processors by [precedence]:");
        for (final IProcessingInstructionProcessor processor : processingInstructionProcessors) {
            logBuilder.line("[THYMELEAF]             * [{}]: {}", new Object[] { Integer.valueOf(processor.getPrecedence()), processor.getClass().getName() });
        }
    }
}
Also used : IElementTagProcessor(org.thymeleaf.processor.element.IElementTagProcessor) ITextProcessor(org.thymeleaf.processor.text.ITextProcessor) MatchingAttributeName(org.thymeleaf.processor.element.MatchingAttributeName) IProcessingInstructionProcessor(org.thymeleaf.processor.processinginstruction.IProcessingInstructionProcessor) ArrayList(java.util.ArrayList) IProcessor(org.thymeleaf.processor.IProcessor) IDocTypeProcessor(org.thymeleaf.processor.doctype.IDocTypeProcessor) ICommentProcessor(org.thymeleaf.processor.comment.ICommentProcessor) MatchingElementName(org.thymeleaf.processor.element.MatchingElementName) IElementModelProcessor(org.thymeleaf.processor.element.IElementModelProcessor) IXMLDeclarationProcessor(org.thymeleaf.processor.xmldeclaration.IXMLDeclarationProcessor) ICDATASectionProcessor(org.thymeleaf.processor.cdatasection.ICDATASectionProcessor)

Example 3 with IElementModelProcessor

use of org.thymeleaf.processor.element.IElementModelProcessor in project thymeleaf by thymeleaf.

the class ProcessorTemplateHandler method handleOpenElement.

@Override
public void handleOpenElement(final IOpenElementTag iopenElementTag) {
    /*
         * If processing is stopped, we should queue this for later handling
         * In theory, given the origin of events (parser or cache) should get stopped immediately, this should
         * only happen if a pre-processor is producing additional events.
         */
    if (this.throttleEngine && this.flowController.stopProcessing) {
        queueEvent(iopenElementTag);
        return;
    }
    /*
         * CHECK WHETHER WE ARE GATHERING AN ELEMENT's MODEL
         */
    if (!this.modelController.shouldProcessOpenElement(iopenElementTag)) {
        return;
    }
    /*
         * CAST TO ENGINE-SPECIFIC IMPLEMENTATION, which will ease the handling of the structure during processing
         */
    OpenElementTag openElementTag = OpenElementTag.asEngineOpenElementTag(iopenElementTag);
    /*
         * OBTAIN THE CURRENT SYNTHETIC MODEL (if any). This is needed in case this event was previously being handled,
         * then a gathering process started (as a consequence of the execution of one of its processors), and then
         * once the model was gathered the process started again by handling the first event, which was the one
         * suspended. By obtaining the current gathering model here we can reinitialize all the handling variables and
         * flags to their original state before being suspended.
         */
    final IGatheringModelProcessable currentGatheringModel = obtainCurrentGatheringModel();
    /*
         * If we are resuming an execution after suspending it, we want to retire the register of the element tag
         * that was added by the controller. The reason we want this is that the current tag was already registered
         * by the controller when the execution was suspended, and we don't want it duplicated (nor altered).
         */
    if (currentGatheringModel != null && this.engineContext != null) {
        this.engineContext.setElementTag(null);
    }
    /*
         * FAIL FAST in case this tag has no associated processors and we have no reason to pay attention to it
         * anyway (because of having been suspended).
         */
    if (currentGatheringModel == null && !openElementTag.hasAssociatedProcessors()) {
        this.next.handleOpenElement(openElementTag);
        return;
    }
    /*
         * DECLARE THE STATE VARS NEEDED FOR PROCESSOR EXECUTION. If we are executing the first event of a gathered
         * model, we will just re-initialize to the original variables, the ones we had before suspending.
         */
    final ProcessorExecutionVars vars = (currentGatheringModel == null ? new ProcessorExecutionVars() : currentGatheringModel.initializeProcessorExecutionVars());
    /*
         * GET THE STRUCTURE HANDLERS INTO LOCAL VARS
         */
    final ElementTagStructureHandler tagStructureHandler = this.elementTagStructureHandler;
    final ElementModelStructureHandler modelStructureHandler = this.elementModelStructureHandler;
    /*
         * EXECUTE PROCESSORS
         */
    IElementProcessor processor;
    while (!vars.discardEvent && (processor = vars.processorIterator.next(openElementTag)) != null) {
        tagStructureHandler.reset();
        modelStructureHandler.reset();
        if (processor instanceof IElementTagProcessor) {
            final IElementTagProcessor elementProcessor = ((IElementTagProcessor) processor);
            elementProcessor.process(this.context, openElementTag, tagStructureHandler);
            // Apply any context modifications made by the processor (local vars, inlining, etc.)
            tagStructureHandler.applyContextModifications(this.engineContext);
            // Apply any modifications to the tag itself: new/removed/replace attributes, etc. Note this
            // creates a new tag object because tag objects are immutable.
            openElementTag = tagStructureHandler.applyAttributes(this.attributeDefinitions, openElementTag);
            if (tagStructureHandler.iterateElement) {
                // Initialize the gathering model
                this.modelController.startGatheringIteratedModel(openElementTag, vars, tagStructureHandler.iterVariableName, tagStructureHandler.iterStatusVariableName, tagStructureHandler.iteratedObject);
                // Nothing else to be done by this handler... let's just queue the rest of the events to be iterated
                return;
            } else if (tagStructureHandler.setBodyText) {
                // Reset model, we need it clean
                vars.modelAfter = resetModel(vars.modelAfter, true);
                // If processable, events will be executed by the ProcessorTemplateHandler. If not, by this.next
                vars.modelAfterProcessable = tagStructureHandler.setBodyTextProcessable;
                // Add the new Text to the queue
                vars.modelAfter.add(new Text(tagStructureHandler.setBodyTextValue));
                // All the body of the original open tag should be skipped (has just been replaced)
                vars.skipBody = SkipBody.SKIP_ALL;
            } else if (tagStructureHandler.setBodyModel) {
                // Reset model, we need it clean
                vars.modelAfter = resetModel(vars.modelAfter, true);
                // If processable, events will be executed by the ProcessorTemplateHandler. If not, by this.next
                vars.modelAfterProcessable = tagStructureHandler.setBodyModelProcessable;
                // Add the new body model to the queue
                vars.modelAfter.addModel(tagStructureHandler.setBodyModelValue);
                // All the body of the original open tag should be skipped (has just been replaced)
                vars.skipBody = SkipBody.SKIP_ALL;
            } else if (tagStructureHandler.insertBeforeModel) {
                // Reset BEFORE model, we need it clean
                vars.modelBefore = resetModel(vars.modelBefore, true);
                // Add model to be passed to this.next BEFORE delegating the event. Note this cannot be processable.
                vars.modelBefore.addModel(tagStructureHandler.insertBeforeModelValue);
            } else if (tagStructureHandler.insertImmediatelyAfterModel) {
                // will not be resetting it because we will be inserting our model at the very beginning of it.
                if (vars.modelAfter == null) {
                    vars.modelAfter = resetModel(vars.modelAfter, true);
                }
                // If processable, events will be executed by the ProcessorTemplateHandler. If not, by this.next
                vars.modelAfterProcessable = tagStructureHandler.insertImmediatelyAfterModelProcessable;
                // Insert the new model
                vars.modelAfter.insertModel(0, tagStructureHandler.insertImmediatelyAfterModelValue);
            // No intervention on the body flags - we will not be removing the body, just inserting before it
            } else if (tagStructureHandler.replaceWithText) {
                // Reset model, we need it clean
                vars.modelAfter = resetModel(vars.modelAfter, true);
                // If processable, events will be executed by the ProcessorTemplateHandler. If not, by this.next
                vars.modelAfterProcessable = tagStructureHandler.replaceWithTextProcessable;
                // Create the new replacement Text event and add it to the model
                vars.modelAfter.add(new Text(tagStructureHandler.replaceWithTextValue));
                // This tag, its body and its corresponding close tag have to be replaced.
                vars.discardEvent = true;
                vars.skipBody = SkipBody.SKIP_ALL;
                vars.skipCloseTag = true;
            } else if (tagStructureHandler.replaceWithModel) {
                // Reset model, we need it clean
                vars.modelAfter = resetModel(vars.modelAfter, true);
                // If processable, events will be executed by the ProcessorTemplateHandler. If not, by this.next
                vars.modelAfterProcessable = tagStructureHandler.replaceWithModelProcessable;
                // Add the new replacement model
                vars.modelAfter.addModel(tagStructureHandler.replaceWithModelValue);
                // This tag, its body and its corresponding close tag have to be replaced.
                vars.discardEvent = true;
                vars.skipBody = SkipBody.SKIP_ALL;
                vars.skipCloseTag = true;
            } else if (tagStructureHandler.removeElement) {
                // Reset model, but only if it already exists
                vars.modelAfter = resetModel(vars.modelAfter, false);
                // We are removing the element (complete with body + close tag). No further processing will be allowed
                vars.discardEvent = true;
                vars.skipBody = SkipBody.SKIP_ALL;
                vars.skipCloseTag = true;
            } else if (tagStructureHandler.removeTags) {
                // No modifications to the queue - it's just the tag that will be removed, not its possible body
                vars.discardEvent = true;
                vars.skipCloseTag = true;
            } else if (tagStructureHandler.removeBody) {
                // Reset model, but only if it already exists
                vars.modelAfter = resetModel(vars.modelAfter, false);
                // We will be only removing the body contents, not the tag itself
                vars.skipBody = SkipBody.SKIP_ALL;
            } else if (tagStructureHandler.removeAllButFirstChild) {
                // Reset model, but only if it already exists
                vars.modelAfter = resetModel(vars.modelAfter, false);
                // This special SkipBody value will allow the first child element (open-body-close or standalone)
                // to be processed, but only that. Once it has been processed, the eventModelController will change
                // this value to SkipBody.SKIP_ELEMENTS.
                // 
                // Note that all non-element child events before and after the first element child event will be
                // processed normally.
                vars.skipBody = SkipBody.PROCESS_ONE_ELEMENT;
            }
        } else if (processor instanceof IElementModelProcessor) {
            if (!vars.processorIterator.lastWasRepeated()) {
                if ((vars.modelBefore != null && vars.modelBefore.size() > 0) || (vars.modelAfter != null && vars.modelAfter.size() > 0)) {
                    throw new TemplateProcessingException("Cannot execute model processor " + processor.getClass().getName() + " as the body " + "of the target element has already been modified by a previously executed processor " + "on the same tag. Model processors cannot execute on already-modified bodies as these " + "might contain unprocessable events (e.g. as a result of a 'th:text' or similar)", openElementTag.getTemplateName(), openElementTag.getLine(), openElementTag.getCol());
                }
                // Set the processor to be executed again, because this time we will just set the "model gathering" mechanism
                vars.processorIterator.setLastToBeRepeated(openElementTag);
                // Initialize the gathering model. This will be concluded (and the gathering model set for
                // processing) at the corresponding close tag.
                this.modelController.startGatheringDelayedModel(openElementTag, vars);
                // Nothing else to be done by this handler... let's just queue the rest of the events in this element
                return;
            }
            /*
                 * This is not the first time we try to execute this processor, which means the model gathering
                 * process has already taken place.
                 */
            // Create the actual Model instance (a clone) that will be passed to the processor to execute on
            final Model gatheredModel = currentGatheringModel.getInnerModel();
            final Model processedModel = new Model(gatheredModel);
            // Execute the processor on the just-created Model
            ((IElementModelProcessor) processor).process(this.context, processedModel, modelStructureHandler);
            // Apply any context modifications made by the processor (local vars, inlining, etc.)
            modelStructureHandler.applyContextModifications(this.engineContext);
            // Reset the skipbody flags so that the processed model can be executed in the same conditions as the original
            currentGatheringModel.resetGatheredSkipFlags();
            /*
                 * Before making any changes and queue the new model for execution, check that it actually is
                 * a "new" model (the processor might have been no-op on the tag and changes might have been
                 * only on the local variables, for example.)
                 */
            if (!gatheredModel.sameAs(processedModel)) {
                /*
                     * Now we will do the exact equivalent to what is performed for an Element Tag processor, when this
                     * returns a result of type "replaceWithModel".
                     */
                // Reset the model
                vars.modelAfter = resetModel(vars.modelAfter, true);
                // Set the model to be executed, and set it to be processable (that is a MUST in this case)
                vars.modelAfter.addModel(processedModel);
                vars.modelAfterProcessable = true;
                // Given we are going to execute the modified model instead of the gathered one, we will set all body
                // skipping flags just as if we had just executed a "replaceWithModel" operation.
                vars.discardEvent = true;
                vars.skipBody = SkipBody.SKIP_ALL;
                vars.skipCloseTag = true;
            }
        } else {
            throw new IllegalStateException("An element has been found with an associated processor of type " + processor.getClass().getName() + " which is neither a Tag Element Processor nor a Model Element Processor.");
        }
    }
    /*
         * QUEUE MODEL HANDLING (IF WE ARE THROTTLING)
         */
    if (this.throttleEngine && ((vars.modelAfter != null && vars.modelAfter.size() > 0) || (vars.modelBefore != null && vars.modelBefore.size() > 0))) {
        queueProcessable(new OpenElementTagModelProcessable(openElementTag, vars, this.modelController, this.flowController, this, this.next));
        return;
    }
    /*
         * PROCESS THE QUEUE BEFORE DELEGATING, if specified to do so
         */
    if (vars.modelBefore != null) {
        // This is never processable
        vars.modelBefore.process(this.next);
    }
    /*
         * PROCESS THE REST OF THE HANDLER CHAIN and INCREASE THE MODEL LEVEL RIGHT AFTERWARDS
         */
    if (!vars.discardEvent) {
        this.next.handleOpenElement(openElementTag);
    }
    /*
         * PROCESS THE QUEUE, launching all the queued events. Note executing the queue after increasing the model
         * level makes sense even if what the queue contains is a replacement for the complete element (including open
         * and close tags), because that way whatever comes in the queue will be encapsulated in a different model level
         * and its internal open/close tags should not affect the correct delimitation of this block.
         */
    if (vars.modelAfter != null) {
        vars.modelAfter.process(vars.modelAfterProcessable ? this : this.next);
    }
    /*
         * SET BODY TO BE SKIPPED, if required. Importantly, this has to be done AFTER executing the queue
         */
    this.modelController.skip(vars.skipBody, vars.skipCloseTag);
}
Also used : IElementTagProcessor(org.thymeleaf.processor.element.IElementTagProcessor) IText(org.thymeleaf.model.IText) IOpenElementTag(org.thymeleaf.model.IOpenElementTag) IElementProcessor(org.thymeleaf.processor.element.IElementProcessor) IElementModelProcessor(org.thymeleaf.processor.element.IElementModelProcessor) TemplateProcessingException(org.thymeleaf.exceptions.TemplateProcessingException)

Aggregations

IElementModelProcessor (org.thymeleaf.processor.element.IElementModelProcessor)3 IElementTagProcessor (org.thymeleaf.processor.element.IElementTagProcessor)3 TemplateProcessingException (org.thymeleaf.exceptions.TemplateProcessingException)2 IText (org.thymeleaf.model.IText)2 IElementProcessor (org.thymeleaf.processor.element.IElementProcessor)2 ArrayList (java.util.ArrayList)1 IOpenElementTag (org.thymeleaf.model.IOpenElementTag)1 IStandaloneElementTag (org.thymeleaf.model.IStandaloneElementTag)1 IProcessor (org.thymeleaf.processor.IProcessor)1 ICDATASectionProcessor (org.thymeleaf.processor.cdatasection.ICDATASectionProcessor)1 ICommentProcessor (org.thymeleaf.processor.comment.ICommentProcessor)1 IDocTypeProcessor (org.thymeleaf.processor.doctype.IDocTypeProcessor)1 MatchingAttributeName (org.thymeleaf.processor.element.MatchingAttributeName)1 MatchingElementName (org.thymeleaf.processor.element.MatchingElementName)1 IProcessingInstructionProcessor (org.thymeleaf.processor.processinginstruction.IProcessingInstructionProcessor)1 ITextProcessor (org.thymeleaf.processor.text.ITextProcessor)1 IXMLDeclarationProcessor (org.thymeleaf.processor.xmldeclaration.IXMLDeclarationProcessor)1