1 package org
.argeo
.cms
.acr
;
3 import java
.io
.PrintStream
;
4 import java
.net
.URLEncoder
;
5 import java
.nio
.charset
.StandardCharsets
;
6 import java
.util
.ArrayList
;
8 import java
.util
.Objects
;
9 import java
.util
.StringJoiner
;
10 import java
.util
.StringTokenizer
;
11 import java
.util
.function
.BiConsumer
;
13 import javax
.security
.auth
.login
.LoginContext
;
14 import javax
.security
.auth
.login
.LoginException
;
15 import javax
.xml
.namespace
.QName
;
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
;
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);
37 public static void traverse(Content content
, BiConsumer
<Content
, Integer
> doIt
, Integer maxDepth
) {
38 doTraverse(content
, doIt
, 0, maxDepth
);
41 private static void doTraverse(Content content
, BiConsumer
<Content
, Integer
> doIt
, int currentDepth
,
43 doIt
.accept(content
, currentDepth
);
44 if (maxDepth
!= null && currentDepth
== maxDepth
)
46 int nextDepth
= currentDepth
+ 1;
47 for (Content child
: content
) {
48 doTraverse(child
, doIt
, nextDepth
, maxDepth
);
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
++) {
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
));
63 if (content
.hasText()) {
64 out
.println("<![CDATA[" + content
.getText().trim() + "]]>");
69 // public static <T> boolean isString(T t) {
70 // return t instanceof String;
73 public static final char SLASH
= '/';
74 public static final String SLASH_STRING
= Character
.toString(SLASH
);
75 public static final String EMPTY
= "";
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 '/').
82 public static String
[] getParentPath(String path
) {
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
);
94 if (parentIndex
== -1) // no '/'
95 return new String
[] { EMPTY
, path
};
97 return new String
[] { parentIndex
!= 0 ? path
.substring(0, parentIndex
) : "" + SLASH
,
98 path
.substring(parentIndex
+ 1) };
101 public static String
toPath(List
<String
> segments
) {
103 StringJoiner sj
= new StringJoiner("/");
104 segments
.forEach((s
) -> sj
.add(s
));
105 return sj
.toString();
108 public static List
<String
> toPathSegments(String path
) {
109 List
<String
> res
= new ArrayList
<>();
110 if (EMPTY
.equals(path
) || Content
.ROOT_PATH
.equals(path
))
112 collectPathSegments(path
, res
);
116 private static void collectPathSegments(String path
, List
<String
> segments
) {
117 String
[] parent
= getParentPath(path
);
118 if (EMPTY
.equals(parent
[1])) // root
120 segments
.add(0, parent
[1]);
121 if (EMPTY
.equals(parent
[0])) // end
123 collectPathSegments(parent
[0], segments
);
126 public static void checkDoubleSlash(String path
) {
127 if (path
.contains(SLASH
+ "" + SLASH
))
128 throw new IllegalArgumentException("Path " + path
+ " contains //");
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('/');
138 return path
.substring(index
+ 1);
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
);
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
);
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
;
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
170 buildHierarchyUnitPath(current
.getParent(), relativePath
);
171 relativePath
.add(current
.getHierarchyUnitName());
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");
187 String
[] parentPath
= getParentPath(path
);
188 Content parent
= createCollections(session
, parentPath
[0]);
189 Content content
= parent
.add(parentPath
[1], DName
.collection
.qName());
194 public static ContentSession
openDataAdminSession(ContentRepository contentRepository
) {
195 LoginContext loginContext
;
197 loginContext
= CmsAuth
.DATA_ADMIN
.newLoginContext();
198 loginContext
.login();
199 } catch (LoginException e1
) {
200 throw new RuntimeException("Could not login as data admin", e1
);
204 ClassLoader currentCl
= Thread
.currentThread().getContextClassLoader();
206 Thread
.currentThread().setContextClassLoader(ContentUtils
.class.getClassLoader());
207 return CurrentSubject
.callAs(loginContext
.getSubject(), () -> contentRepository
.get());
209 Thread
.currentThread().setContextClassLoader(currentCl
);
213 public static ContentSession
openSession(ContentRepository contentRepository
, CmsSession cmsSession
) {
214 return CurrentSubject
.callAs(cmsSession
.getSubject(), () -> contentRepository
.get());
218 * Constructs a relative path between a base path and a given path.
220 * @throws IllegalArgumentException if the base path is not an ancestor of the
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);
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();
242 /** A path in the node repository */
243 public static String
getDataPathForUrl(Content node
) {
244 return cleanPathForUrl(getDataPath(node
));
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()) {
253 String encoded
= URLEncoder
.encode(st
.nextToken(), StandardCharsets
.UTF_8
);
254 encoded
= encoded
.replace("+", "%20");
258 return sb
.toString();
262 private ContentUtils() {