View Javadoc
1   /**
2    * This file Copyright (c) 2020 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.rendering.spa.rest.v1;
35  
36  import info.magnolia.cms.beans.config.ServerConfiguration;
37  import info.magnolia.context.WebContext;
38  import info.magnolia.jcr.predicate.AbstractPredicate;
39  import info.magnolia.jcr.util.NodeTypes;
40  import info.magnolia.jcr.util.NodeUtil;
41  import info.magnolia.registry.RegistrationException;
42  import info.magnolia.rendering.context.RenderingContext;
43  import info.magnolia.rendering.engine.OutputProvider;
44  import info.magnolia.rendering.engine.RenderException;
45  import info.magnolia.rendering.spa.renderer.AnnotationRenderingEngine;
46  import info.magnolia.rendering.template.TemplateDefinition;
47  import info.magnolia.rendering.template.assignment.TemplateDefinitionAssignment;
48  import info.magnolia.rendering.template.variation.RenderableVariationResolver;
49  import info.magnolia.repository.RepositoryConstants;
50  import info.magnolia.rest.AbstractEndpoint;
51  import info.magnolia.rest.EndpointDefinition;
52  import info.magnolia.templating.elements.AbstractContentTemplatingElement;
53  import info.magnolia.templating.elements.AreaElement;
54  import info.magnolia.templating.elements.ComponentElement;
55  import info.magnolia.templating.elements.PageElement;
56  import info.magnolia.templating.elements.TemplatingElement;
57  import info.magnolia.templating.module.TemplatingModule;
58  
59  import javax.inject.Inject;
60  import javax.inject.Provider;
61  import javax.jcr.Node;
62  import javax.jcr.RepositoryException;
63  import javax.jcr.Session;
64  import javax.ws.rs.GET;
65  import javax.ws.rs.Path;
66  import javax.ws.rs.PathParam;
67  import javax.ws.rs.Produces;
68  import javax.ws.rs.core.MediaType;
69  import java.io.IOException;
70  import java.io.OutputStream;
71  import java.util.HashMap;
72  import java.util.LinkedList;
73  import java.util.Map;
74  
75  /**
76   * Template annotations endpoint.
77   *
78   * @param <D> The endpoint definition
79   */
80  @Path("/{path:template-annotations}/v1")
81  public class TemplateAnnotationEndpoint<D extends EndpointDefinition> extends AbstractEndpoint<D> {
82  
83      private static final String OPENING_TAG = "<!-- ";
84      private static final String CLOSING_TAG = " -->\n";
85  
86      private final AnnotationRenderingEngine renderingEngine;
87      private final TemplateDefinitionAssignment templateDefinitionAssignment;
88      private final Provider<TemplatingModule> templateModuleProvider;
89      private final ServerConfiguration serverConfiguration;
90      private final RenderableVariationResolver variationResolver;
91      private final Provider<WebContext> contextProvider;
92  
93      @Inject
94      public TemplateAnnotationEndpoint(D endpointDefinition, AnnotationRenderingEngine renderingEngine, TemplateDefinitionAssignment templateDefinitionAssignment, Provider<TemplatingModule> templatingModuleProvider, ServerConfiguration serverConfiguration, RenderableVariationResolver variationResolver, Provider<WebContext> contextProvider) {
95          super(endpointDefinition);
96          this.renderingEngine = renderingEngine;
97          this.templateDefinitionAssignment = templateDefinitionAssignment;
98          this.templateModuleProvider = templatingModuleProvider;
99          this.serverConfiguration = serverConfiguration;
100         this.variationResolver = variationResolver;
101         this.contextProvider = contextProvider;
102     }
103 
104     @Path("/{nodePath:(.+)?}")
105     @GET
106     @Produces(MediaType.APPLICATION_JSON)
107     public Map<String, String> getTemplateAnnotations(@PathParam("nodePath") String path) throws Exception {
108         Session session = contextProvider.get().getJCRSession(RepositoryConstants.WEBSITE);
109         Map<String, String> pageData = new HashMap<>();
110         Node node = session.getRootNode().getNode(path);
111         pageData.put(node.getPath(), renderAnnotations(node));
112         NodeUtil.visit(node, subNode -> pageData.put(subNode.getPath(), renderAnnotations(subNode)), new AreasAndComponentsPredicate());
113         return pageData;
114     }
115 
116     private String renderAnnotations(Node node) {
117         contextProvider.get().getAggregationState().setPreviewMode(false);
118         StringBuilder outputBuilder = new StringBuilder();
119         RenderingContext renderingContext = renderingEngine.getRenderingContext();
120         try {
121             renderingContext.push(node, getTemplateDefinition(node), new OutputProvider() {
122                 @Override
123                 public Appendable getAppendable() {
124                     return outputBuilder;
125                 }
126 
127                 @Override
128                 public OutputStream getOutputStream() {
129                     return null;
130                 }
131             });
132             TemplatingElement contentElement = createTemplatingElement(node);
133             contentElement.begin(outputBuilder);
134             return unwrapComment(outputBuilder.toString());
135         } catch (RepositoryException | RegistrationException | IOException | RenderException e) {
136             throw new RuntimeException("Failed to render template annotation for node " + node, e);
137         }
138     }
139 
140     private String unwrapComment(String htmlComment) {
141         String annotation = htmlComment.substring(OPENING_TAG.length());
142         annotation = annotation.substring(0, annotation.indexOf(CLOSING_TAG));
143         return annotation;
144     }
145 
146     private TemplateDefinition getTemplateDefinition(Node content) throws RepositoryException, RegistrationException {
147         // PageElement and ComponentElement expect their templating definition in the rendering context.
148         // AreaElement, on the other hand, expects the definition where the area is defined, which can be
149         // either page or component definition for normal areas, or another area definition in case of nested areas.
150         // Because we can't retrieve area definitions directly from registry, we need to backtrack our way from nearest
151         // page/component definition to given area's parent definition.
152         Node pageNode = content;
153         LinkedList<String> parents = new LinkedList<>();
154         while (pageNode.getPrimaryNodeType().getName().equals(NodeTypes.Area.NAME)) {
155             parents.addFirst(pageNode.getName());
156             pageNode = pageNode.getParent();
157         }
158         TemplateDefinition definition = templateDefinitionAssignment.getAssignedTemplateDefinition(pageNode);
159         if (parents.size() > 0) {
160             parents.removeLast();
161             for (String parent : parents) {
162                 definition = definition.getAreas().get(parent);
163             }
164         }
165         if (!content.getPrimaryNodeType().getName().equals(NodeTypes.Area.NAME) || definition.getAreas().containsKey(content.getName())) {
166             return definition;
167         } else {
168             throw new RuntimeException("Area " + content.getName() + " is not defined in template definition of " + pageNode);
169         }
170     }
171 
172     private TemplatingElement createTemplatingElement(Node node) throws RepositoryException {
173         String nodeType = node.getPrimaryNodeType().getName();
174         AbstractContentTemplatingElement contentElement = null;
175         if (NodeTypes.Page.NAME.equals(nodeType)) {
176             contentElement = new PageElement(serverConfiguration, renderingEngine.getRenderingContext(), templateModuleProvider, contextProvider.get());
177         } else if (NodeTypes.Component.NAME.equals(nodeType)) {
178             contentElement = new ComponentElement(serverConfiguration, renderingEngine.getRenderingContext(), renderingEngine, templateDefinitionAssignment, contextProvider.get(), templateModuleProvider);
179         } else if (NodeTypes.Area.NAME.equals(nodeType)) {
180             contentElement = new AreaElement(serverConfiguration, renderingEngine.getRenderingContext(), renderingEngine, variationResolver, templateModuleProvider, contextProvider.get());
181             ((AreaElement) contentElement).setName(node.getName());
182         }
183         contentElement.setContent(node);
184         contentElement.setWorkspace(RepositoryConstants.WEBSITE);
185         contentElement.setNodeIdentifier(node.getIdentifier());
186         contentElement.setPath(node.getPath());
187         return contentElement;
188     }
189 
190     private static class AreasAndComponentsPredicate extends AbstractPredicate<Node> {
191         @Override
192         public boolean evaluateTyped(Node node) {
193             try {
194                 String nodeType = node.getPrimaryNodeType().getName();
195                 return nodeType.equals(NodeTypes.Area.NAME) || nodeType.equals(NodeTypes.Component.NAME);
196             } catch (RepositoryException e) {
197                 new RuntimeException("Failed to access node [" + node + "]", e);
198             }
199             return false;
200         }
201     }
202 }