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