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 }