]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/acr/ContentUtils.java
Releasing
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / acr / ContentUtils.java
1 package org.argeo.cms.acr;
2
3 import java.io.PrintStream;
4 import java.net.URLEncoder;
5 import java.nio.charset.StandardCharsets;
6 import java.util.ArrayList;
7 import java.util.List;
8 import java.util.Objects;
9 import java.util.StringJoiner;
10 import java.util.StringTokenizer;
11 import java.util.function.BiConsumer;
12
13 import javax.security.auth.login.LoginContext;
14 import javax.security.auth.login.LoginException;
15 import javax.xml.namespace.QName;
16
17 import org.argeo.api.acr.Content;
18 import org.argeo.api.acr.ContentRepository;
19 import org.argeo.api.acr.ContentSession;
20 import org.argeo.api.acr.DName;
21 import org.argeo.api.cms.CmsAuth;
22 import org.argeo.api.cms.CmsConstants;
23 import org.argeo.api.cms.CmsSession;
24 import org.argeo.api.cms.directory.CmsDirectory;
25 import org.argeo.api.cms.directory.CmsUserManager;
26 import org.argeo.api.cms.directory.HierarchyUnit;
27 import org.argeo.api.cms.directory.UserDirectory;
28 import org.argeo.cms.util.CurrentSubject;
29 import org.osgi.service.useradmin.Role;
30
31 /** Utilities and routines around {@link Content}. */
32 public class ContentUtils {
33 public static void traverse(Content content, BiConsumer<Content, Integer> doIt) {
34 traverse(content, doIt, (Integer) null);
35 }
36
37 public static void traverse(Content content, BiConsumer<Content, Integer> doIt, Integer maxDepth) {
38 doTraverse(content, doIt, 0, maxDepth);
39 }
40
41 private static void doTraverse(Content content, BiConsumer<Content, Integer> doIt, int currentDepth,
42 Integer maxDepth) {
43 doIt.accept(content, currentDepth);
44 if (maxDepth != null && currentDepth == maxDepth)
45 return;
46 int nextDepth = currentDepth + 1;
47 for (Content child : content) {
48 doTraverse(child, doIt, nextDepth, maxDepth);
49 }
50 }
51
52 public static void print(Content content, PrintStream out, int depth, boolean printText) {
53 StringBuilder sb = new StringBuilder();
54 for (int i = 0; i < depth; i++) {
55 sb.append(" ");
56 }
57 String prefix = sb.toString();
58 out.println(prefix + content.getName());
59 for (QName key : content.keySet()) {
60 out.println(prefix + " " + key + "=" + content.get(key));
61 }
62 if (printText) {
63 if (content.hasText()) {
64 out.println("<![CDATA[" + content.getText().trim() + "]]>");
65 }
66 }
67 }
68
69 // public static <T> boolean isString(T t) {
70 // return t instanceof String;
71 // }
72
73 public static final char SLASH = '/';
74 public static final String SLASH_STRING = Character.toString(SLASH);
75 public static final String EMPTY = "";
76
77 /**
78 * Split a path (with '/' separator) in an array of length 2, the first part
79 * being the parent path (which could be either absolute or relative), the
80 * second one being the last segment, (guaranteed to be without a '/').
81 */
82 public static String[] getParentPath(String path) {
83 if (path == null)
84 throw new IllegalArgumentException("Path cannot be null");
85 if (path.length() == 0)
86 throw new IllegalArgumentException("Path cannot be empty");
87 checkDoubleSlash(path);
88 int parentIndex = path.lastIndexOf(SLASH);
89 if (parentIndex == path.length() - 1) {// trailing '/'
90 path = path.substring(0, path.length() - 1);
91 parentIndex = path.lastIndexOf(SLASH);
92 }
93
94 if (parentIndex == -1) // no '/'
95 return new String[] { EMPTY, path };
96
97 return new String[] { parentIndex != 0 ? path.substring(0, parentIndex) : "" + SLASH,
98 path.substring(parentIndex + 1) };
99 }
100
101 public static String toPath(List<String> segments) {
102 // TODO checks
103 StringJoiner sj = new StringJoiner("/");
104 segments.forEach((s) -> sj.add(s));
105 return sj.toString();
106 }
107
108 public static List<String> toPathSegments(String path) {
109 List<String> res = new ArrayList<>();
110 if (EMPTY.equals(path) || Content.ROOT_PATH.equals(path))
111 return res;
112 collectPathSegments(path, res);
113 return res;
114 }
115
116 private static void collectPathSegments(String path, List<String> segments) {
117 String[] parent = getParentPath(path);
118 if (EMPTY.equals(parent[1])) // root
119 return;
120 segments.add(0, parent[1]);
121 if (EMPTY.equals(parent[0])) // end
122 return;
123 collectPathSegments(parent[0], segments);
124 }
125
126 public static void checkDoubleSlash(String path) {
127 if (path.contains(SLASH + "" + SLASH))
128 throw new IllegalArgumentException("Path " + path + " contains //");
129 }
130
131 /** The last element of a path. */
132 public static String lastPathElement(String path) {
133 if (path.charAt(path.length() - 1) == '/')
134 throw new IllegalArgumentException("Path " + path + " cannot end with '/'");
135 int index = path.lastIndexOf('/');
136 if (index < 0)
137 return path;
138 return path.substring(index + 1);
139 }
140
141 /*
142 * DIRECTORY
143 */
144
145 public static Content roleToContent(CmsUserManager userManager, ContentSession contentSession, Role role) {
146 UserDirectory userDirectory = userManager.getDirectory(role);
147 String path = directoryPath(userDirectory) + userDirectory.getRolePath(role);
148 Content content = contentSession.get(path);
149 return content;
150 }
151
152 public static Content hierarchyUnitToContent(ContentSession contentSession, HierarchyUnit hierarchyUnit) {
153 CmsDirectory directory = hierarchyUnit.getDirectory();
154 StringJoiner relativePath = new StringJoiner(SLASH_STRING);
155 buildHierarchyUnitPath(hierarchyUnit, relativePath);
156 String path = directoryPath(directory) + relativePath.toString();
157 Content content = contentSession.get(path);
158 return content;
159 }
160
161 /** The path to this {@link CmsDirectory}. Ends with a /. */
162 private static String directoryPath(CmsDirectory directory) {
163 return CmsContentRepository.DIRECTORY_BASE + SLASH + directory.getName() + SLASH;
164 }
165
166 /** Recursively build a relative path of a {@link HierarchyUnit}. */
167 private static void buildHierarchyUnitPath(HierarchyUnit current, StringJoiner relativePath) {
168 if (current.getParent() == null) // directory
169 return;
170 buildHierarchyUnitPath(current.getParent(), relativePath);
171 relativePath.add(current.getHierarchyUnitName());
172 }
173
174 /*
175 * CONSUMER UTILS
176 */
177
178 public static Content createCollections(ContentSession session, String path) {
179 if (session.exists(path)) {
180 Content content = session.get(path);
181 if (!content.isContentClass(DName.collection.qName())) {
182 throw new IllegalStateException("Content " + path + " already exists, but is not a collection");
183 } else {
184 return content;
185 }
186 } else {
187 String[] parentPath = getParentPath(path);
188 Content parent = createCollections(session, parentPath[0]);
189 Content content = parent.add(parentPath[1], DName.collection.qName());
190 return content;
191 }
192 }
193
194 public static ContentSession openDataAdminSession(ContentRepository contentRepository) {
195 LoginContext loginContext;
196 try {
197 loginContext = CmsAuth.DATA_ADMIN.newLoginContext();
198 loginContext.login();
199 } catch (LoginException e1) {
200 throw new RuntimeException("Could not login as data admin", e1);
201 } finally {
202 }
203
204 ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
205 try {
206 Thread.currentThread().setContextClassLoader(ContentUtils.class.getClassLoader());
207 return CurrentSubject.callAs(loginContext.getSubject(), () -> contentRepository.get());
208 } finally {
209 Thread.currentThread().setContextClassLoader(currentCl);
210 }
211 }
212
213 public static ContentSession openSession(ContentRepository contentRepository, CmsSession cmsSession) {
214 return CurrentSubject.callAs(cmsSession.getSubject(), () -> contentRepository.get());
215 }
216
217 /**
218 * Constructs a relative path between a base path and a given path.
219 *
220 * @throws IllegalArgumentException if the base path is not an ancestor of the
221 * path
222 */
223 public static String relativize(String basePath, String path) throws IllegalArgumentException {
224 Objects.requireNonNull(basePath);
225 Objects.requireNonNull(path);
226 if (!path.startsWith(basePath))
227 throw new IllegalArgumentException(basePath + " is not an ancestor of " + path);
228 String relativePath = path.substring(basePath.length());
229 if (relativePath.length() > 0 && relativePath.charAt(0) == '/')
230 relativePath = relativePath.substring(1);
231 return relativePath;
232 }
233
234 /** A path in the node repository */
235 public static String getDataPath(Content node) {
236 // TODO make it more configurable?
237 StringBuilder buf = new StringBuilder(CmsConstants.PATH_API_ACR);
238 buf.append(node.getPath());
239 return buf.toString();
240 }
241
242 /** A path in the node repository */
243 public static String getDataPathForUrl(Content node) {
244 return cleanPathForUrl(getDataPath(node));
245 }
246
247 /** Clean reserved URL characters for use in HTTP links. */
248 public static String cleanPathForUrl(String path) {
249 StringTokenizer st = new StringTokenizer(path, "/");
250 StringBuilder sb = new StringBuilder();
251 while (st.hasMoreElements()) {
252 sb.append('/');
253 String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8);
254 encoded = encoded.replace("+", "%20");
255 sb.append(encoded);
256
257 }
258 return sb.toString();
259 }
260
261 /** Singleton. */
262 private ContentUtils() {
263
264 }
265
266 }