View Javadoc
1   /*
2    * Copyright 2016-2024 Bloomreach B.V. (https://www.bloomreach.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.onehippo.forge.content.exim.core.util;
17  
18  import java.util.Collection;
19  import java.util.LinkedList;
20  import java.util.List;
21  
22  import javax.jcr.ItemNotFoundException;
23  import javax.jcr.Node;
24  import javax.jcr.RepositoryException;
25  import javax.jcr.Session;
26  
27  import org.apache.commons.collections4.CollectionUtils;
28  import org.apache.commons.lang3.StringUtils;
29  import org.onehippo.forge.content.pojo.model.ContentNode;
30  import org.onehippo.forge.content.pojo.model.ContentProperty;
31  import org.onehippo.forge.content.pojo.model.ContentPropertyType;
32  
33  /**
34   * Utilities to handle {@link ContentNode} objects.
35   */
36  public class ContentNodeUtils {
37  
38      /**
39       * Default <a href="https://commons.apache.org/proper/commons-jxpath/">JXPath</a> expression
40       * to select all the nodes having <code>hippo:docbase</code> property.
41       */
42      public static final String MIRROR_DOCBASES_XPATH = "//nodes[properties[@itemName='hippo:docbase']]";
43  
44      /**
45       * The ROOT node identifier of Jackrabbit Repository.
46       */
47      private static final String ROOT_NODE_UUID = "cafebabe-cafe-babe-cafe-babecafebabe";
48  
49      private ContentNodeUtils() {
50      }
51  
52      /**
53       * Selects all the {@link ContentNode} objects under {@code baseContentNode}
54       * by the default <a href="https://commons.apache.org/proper/commons-jxpath/">JXPath</a> expression, {@link #MIRROR_DOCBASES_XPATH}
55       * and replace the <code>hippo:docbase</code> property value by the path of the JCR node found by the existing UUID string value.
56       * {@code session} is used when finding a JCR node associated by the UUID value at the existing {@code hippo:docbase} property.
57       * @param session JCR session
58       * @param baseContentNode base {@link ContentNode} instance
59       * @throws RepositoryException if fails to find a JCR node associated by the UUID value
60       */
61      public static void replaceDocbasesByPaths(final Session session, final ContentNode baseContentNode)
62              throws RepositoryException {
63          replaceDocbasesByPaths(session, baseContentNode, MIRROR_DOCBASES_XPATH);
64      }
65  
66      /**
67       * Selects all the {@link ContentNode} objects under {@code baseContentNode}
68       * by the given <a href="https://commons.apache.org/proper/commons-jxpath/">JXPath</a> expression, {@code jxpath}
69       * and replace the <code>hippo:docbase</code> property value by the path of the JCR node found by the existing UUID string value.
70       * {@code session} is used when finding a JCR node associated by the UUID value at the existing {@code hippo:docbase} property.
71       * @param session JCR session
72       * @param baseContentNode base {@link ContentNode} instance
73       * @param jxpath <a href="https://commons.apache.org/proper/commons-jxpath/">JXPath</a> expression
74       * @throws RepositoryException if fails to find a JCR node associated by the UUID value
75       */
76      public static void replaceDocbasesByPaths(final Session session, final ContentNode baseContentNode,
77              final String jxpath) throws RepositoryException {
78          replaceDocbasesByPaths(session, baseContentNode, jxpath, null);
79      }
80  
81      /**
82       * Selects all the {@link ContentNode} objects under {@code baseContentNode}
83       * by the given <a href="https://commons.apache.org/proper/commons-jxpath/">JXPath</a> expression, {@code jxpath}
84       * and replace the <code>hippo:docbase</code> property value by the path of the JCR node found by the existing UUID string value,
85       * and add those paths to {@code paths} collection.
86       * {@code session} is used when finding a JCR node associated by the UUID value at the existing {@code hippo:docbase} property.
87       * @param session JCR session
88       * @param baseContentNode base {@link ContentNode} instance
89       * @param jxpath <a href="https://commons.apache.org/proper/commons-jxpath/">JXPath</a> expression
90       * @param paths replaced paths collection
91       * @throws RepositoryException if fails to find a JCR node associated by the UUID value
92       */
93      public static void replaceDocbasesByPaths(final Session session, final ContentNode baseContentNode,
94              final String jxpath, final Collection<String> paths) throws RepositoryException {
95          List<ContentNode> mirrors = baseContentNode.queryNodesByXPath(jxpath);
96          Node linkedNode;
97          String linkedNodePath;
98  
99          for (ContentNode mirror : mirrors) {
100             String docbase = mirror.getProperty("hippo:docbase").getValue();
101 
102             if (StringUtils.isNotBlank(docbase) && !StringUtils.equals(ROOT_NODE_UUID, docbase)) {
103                 try {
104                     if (StringUtils.startsWith(docbase, "/")) {
105                         linkedNodePath = docbase;
106                     } else {
107                         linkedNode = session.getNodeByIdentifier(docbase);
108                         linkedNodePath = linkedNode.getPath();
109                     }
110 
111                     mirror.setProperty("hippo:docbase", linkedNodePath);
112 
113                     if (paths != null) {
114                         paths.add(linkedNodePath);
115                     }
116                 } catch (ItemNotFoundException ignore) {
117                 }
118             }
119         }
120     }
121 
122     /**
123      * Selects all the {@link ContentProperty} objects under {@code baseContentNode}
124      * by the given <a href="https://commons.apache.org/proper/commons-jxpath/">JXPath</a> expression, {@code jxpath}
125      * and replace the string docbase property value by the path of the JCR node found by the existing UUID string value.
126      * {@code session} is used when finding a JCR node associated by the UUID value at the existing string UUID property value.
127      * @param session JCR session
128      * @param baseContentNode base {@link ContentNode} instance
129      * @param jxpath <a href="https://commons.apache.org/proper/commons-jxpath/">JXPath</a> expression
130      * @throws RepositoryException if fails to find a JCR node associated by the UUID value
131      */
132     public static void replaceDocbasePropertiesByPaths(final Session session, final ContentNode baseContentNode,
133             final String jxpath) throws RepositoryException {
134         List<ContentProperty> docbaseProps = baseContentNode.queryPropertiesByXPath(jxpath);
135         Node linkedNode;
136         List<String> docbases;
137         List<String> docpaths;
138 
139         for (ContentProperty docbaseProp : docbaseProps) {
140             docbases = docbaseProp.getValues();
141 
142             if (CollectionUtils.isNotEmpty(docbases)) {
143                 docpaths = new LinkedList<>();
144 
145                 for (String docbase : docbases) {
146                     String docpath = docbase;
147 
148                     try {
149                         linkedNode = session.getNodeByIdentifier(docbase);
150                         docpath = linkedNode.getPath();
151                     } catch (ItemNotFoundException ignore) {
152                     } finally {
153                         docpaths.add(docpath);
154                     }
155                 }
156 
157                 docbaseProp.removeValues();
158 
159                 for (String docpath : docpaths) {
160                     docbaseProp.addValue(docpath);
161                 }
162             }
163         }
164     }
165 
166     /**
167      * Find a given {@code urlPrefix} in the URL value of <code>jcr:data</code> property, and remove the prefix
168      * if the value starts with the {@code urlPrefix}.
169      * @param baseContentNode base content node
170      * @param urlPrefix url prefix
171      */
172     public static void removeUrlPrefixInJcrDataValues(ContentNode baseContentNode, String urlPrefix) {
173         final int urlPrefixLen = urlPrefix.length();
174         List<ContentNode> childNodes = baseContentNode.queryNodesByXPath("//nodes[properties[@itemName='jcr:data']]");
175 
176         for (ContentNode childNode : childNodes) {
177             String value = childNode.getProperty("jcr:data").getValue();
178 
179             if (StringUtils.startsWith(value, urlPrefix)) {
180                 childNode.setProperty("jcr:data", value.substring(urlPrefixLen));
181             }
182         }
183     }
184 
185     /**
186      * Find a given {@code urlPrefix} in the URL value of <code>jcr:data</code> property, and prepend it with the
187      * given {@code urlPrefix} if the value starts with the {@code startsWith}.
188      * @param baseContentNode base content node
189      * @param startsWith start with string of the value
190      * @param urlPrefix url prefix
191      */
192     public static void prependUrlPrefixInJcrDataValues(ContentNode baseContentNode, String startsWith,
193             String urlPrefix) {
194         List<ContentNode> childNodes = baseContentNode.queryNodesByXPath("//nodes[properties[@itemName='jcr:data']]");
195 
196         for (ContentNode childNode : childNodes) {
197             String value = childNode.getProperty("jcr:data").getValue();
198 
199             if (StringUtils.startsWith(value, startsWith)) {
200                 childNode.setProperty("jcr:data", ContentPropertyType.BINARY, urlPrefix + value);
201             }
202         }
203     }
204 
205     /**
206      * Return true if the property (by the given {@code propertyName}) in the {@code contentNode} has the same string value
207      * as {@code value}.
208      * @param contentNode content node
209      * @param propertyName property name
210      * @param value string value
211      * @return true if the property (by the given {@code propertyName}) in the {@code contentNode} has the same string value
212      * as {@code value}
213      */
214     public static boolean containsStringValueInProperty(final ContentNode contentNode, final String propertyName,
215             final String value) {
216         if (!contentNode.hasProperty(propertyName)) {
217             return false;
218         }
219 
220         ContentProperty prop = contentNode.getProperty(propertyName);
221 
222         if (prop.getValueCount() == 0) {
223             return false;
224         }
225 
226         List<String> values = prop.getValues();
227         return values.contains(value);
228     }
229 }