1 package org
.argeo
.cms
.acr
;
3 import static org
.argeo
.api
.acr
.Content
.PATH_SEPARATOR
;
5 import java
.io
.PrintStream
;
6 import java
.net
.URLEncoder
;
7 import java
.nio
.charset
.StandardCharsets
;
8 import java
.util
.ArrayList
;
10 import java
.util
.StringJoiner
;
11 import java
.util
.StringTokenizer
;
12 import java
.util
.function
.BiConsumer
;
14 import javax
.security
.auth
.login
.LoginContext
;
15 import javax
.security
.auth
.login
.LoginException
;
16 import javax
.xml
.namespace
.QName
;
18 import org
.argeo
.api
.acr
.Content
;
19 import org
.argeo
.api
.acr
.ContentRepository
;
20 import org
.argeo
.api
.acr
.ContentSession
;
21 import org
.argeo
.api
.acr
.DName
;
22 import org
.argeo
.api
.cms
.CmsAuth
;
23 import org
.argeo
.api
.cms
.CmsConstants
;
24 import org
.argeo
.api
.cms
.CmsSession
;
25 import org
.argeo
.api
.cms
.directory
.CmsDirectory
;
26 import org
.argeo
.api
.cms
.directory
.CmsRole
;
27 import org
.argeo
.api
.cms
.directory
.CmsUserManager
;
28 import org
.argeo
.api
.cms
.directory
.HierarchyUnit
;
29 import org
.argeo
.api
.cms
.directory
.UserDirectory
;
30 import org
.argeo
.cms
.util
.CurrentSubject
;
32 /** Utilities and routines around {@link Content}. */
33 public class ContentUtils
{
35 static final String PATH_SEPARATOR_STRING
= Character
.toString(PATH_SEPARATOR
);
36 private static final String DOUBLE_PATH_SEPARATOR
= PATH_SEPARATOR_STRING
+ PATH_SEPARATOR_STRING
;
38 public static void traverse(Content content
, BiConsumer
<Content
, Integer
> doIt
) {
39 traverse(content
, doIt
, (Integer
) null);
42 public static void traverse(Content content
, BiConsumer
<Content
, Integer
> doIt
, Integer maxDepth
) {
43 doTraverse(content
, doIt
, 0, maxDepth
);
46 private static void doTraverse(Content content
, BiConsumer
<Content
, Integer
> doIt
, int currentDepth
,
48 doIt
.accept(content
, currentDepth
);
49 if (maxDepth
!= null && currentDepth
== maxDepth
)
51 int nextDepth
= currentDepth
+ 1;
52 for (Content child
: content
) {
53 doTraverse(child
, doIt
, nextDepth
, maxDepth
);
57 public static void print(Content content
, PrintStream out
, int depth
, boolean printText
) {
58 StringBuilder sb
= new StringBuilder();
59 for (int i
= 0; i
< depth
; i
++) {
62 String prefix
= sb
.toString();
65 if (content
.hasText()) {
66 final int MAX_LENGTH
= 64;
67 txt
= content
.getText().trim();
68 if (txt
.length() > MAX_LENGTH
)
69 txt
= txt
.substring(0, 64) + " ...";
73 out
.println(prefix
+ content
.getName() + txt
);
74 for (QName key
: content
.keySet()) {
75 out
.println(prefix
+ " " + key
+ "=" + content
.get(key
));
79 // public static <T> boolean isString(T t) {
80 // return t instanceof String;
83 public static String
toPath(List
<String
> segments
) {
85 StringJoiner sj
= new StringJoiner(PATH_SEPARATOR_STRING
);
86 segments
.forEach((s
) -> sj
.add(s
));
90 static List
<String
> toPathSegments(String path
) {
91 List
<String
> res
= new ArrayList
<>();
92 if ("".equals(path
) || Content
.ROOT_PATH
.equals(path
))
94 collectPathSegments(path
, res
);
98 static void collectPathSegments(String path
, List
<String
> segments
) {
99 String
[] parent
= CmsContent
.getParentPath(path
);
100 if ("".equals(parent
[1])) // root
102 segments
.add(0, parent
[1]);
103 if ("".equals(parent
[0])) // end
105 collectPathSegments(parent
[0], segments
);
108 static void checkDoubleSlash(String path
) {
109 if (path
.contains(DOUBLE_PATH_SEPARATOR
))
110 throw new IllegalArgumentException("Path " + path
+ " contains //");
113 /** The last element of a path. */
114 public static String
lastPathElement(String path
) {
115 if (path
.charAt(path
.length() - 1) == '/')
116 throw new IllegalArgumentException("Path " + path
+ " cannot end with '/'");
117 int index
= path
.lastIndexOf('/');
120 return path
.substring(index
+ 1);
127 public static Content
roleToContent(CmsUserManager userManager
, ContentSession contentSession
, CmsRole role
) {
128 UserDirectory userDirectory
= userManager
.getDirectory(role
);
129 String path
= directoryPath(userDirectory
) + userDirectory
.getRolePath(role
);
130 Content content
= contentSession
.get(path
);
134 public static Content
hierarchyUnitToContent(ContentSession contentSession
, HierarchyUnit hierarchyUnit
) {
135 CmsDirectory directory
= hierarchyUnit
.getDirectory();
136 StringJoiner relativePath
= new StringJoiner(PATH_SEPARATOR_STRING
);
137 buildHierarchyUnitPath(hierarchyUnit
, relativePath
);
138 String path
= directoryPath(directory
) + relativePath
.toString();
139 Content content
= contentSession
.get(path
);
143 /** The path to this {@link CmsDirectory}. Ends with a /. */
144 private static String
directoryPath(CmsDirectory directory
) {
145 return CmsContentRepository
.DIRECTORY_BASE
+ PATH_SEPARATOR
+ directory
.getName() + PATH_SEPARATOR
;
148 /** Recursively build a relative path of a {@link HierarchyUnit}. */
149 private static void buildHierarchyUnitPath(HierarchyUnit current
, StringJoiner relativePath
) {
150 if (current
.getParent() == null) // directory
152 buildHierarchyUnitPath(current
.getParent(), relativePath
);
153 relativePath
.add(current
.getHierarchyUnitName());
160 public static Content
createCollections(ContentSession session
, String path
) {
161 if (session
.exists(path
)) {
162 Content content
= session
.get(path
);
163 if (!content
.isContentClass(DName
.collection
.qName())) {
164 throw new IllegalStateException("Content " + path
+ " already exists, but is not a collection");
169 String
[] parentPath
= CmsContent
.getParentPath(path
);
170 Content parent
= createCollections(session
, parentPath
[0]);
171 Content content
= parent
.add(parentPath
[1], DName
.collection
.qName());
176 public static ContentSession
openDataAdminSession(ContentRepository contentRepository
) {
177 LoginContext loginContext
;
179 loginContext
= CmsAuth
.DATA_ADMIN
.newLoginContext();
180 loginContext
.login();
181 } catch (LoginException e1
) {
182 throw new RuntimeException("Could not login as data admin", e1
);
186 ClassLoader currentCl
= Thread
.currentThread().getContextClassLoader();
188 Thread
.currentThread().setContextClassLoader(ContentUtils
.class.getClassLoader());
189 return CurrentSubject
.callAs(loginContext
.getSubject(), () -> contentRepository
.get());
191 Thread
.currentThread().setContextClassLoader(currentCl
);
195 public static ContentSession
openSession(ContentRepository contentRepository
, CmsSession cmsSession
) {
196 return CurrentSubject
.callAs(cmsSession
.getSubject(), () -> contentRepository
.get());
199 /** A path in the node repository */
200 public static String
getDataPath(Content node
) {
201 // TODO make it more configurable?
202 StringBuilder buf
= new StringBuilder(CmsConstants
.PATH_API_ACR
);
203 buf
.append(node
.getPath());
204 return buf
.toString();
207 /** A path in the node repository */
208 public static String
getDataPathForUrl(Content node
) {
209 return cleanPathForUrl(getDataPath(node
));
212 /** Clean reserved URL characters for use in HTTP links. */
213 public static String
cleanPathForUrl(String path
) {
214 StringTokenizer st
= new StringTokenizer(path
, "/");
215 StringBuilder sb
= new StringBuilder();
216 while (st
.hasMoreElements()) {
218 String encoded
= URLEncoder
.encode(st
.nextToken(), StandardCharsets
.UTF_8
);
219 encoded
= encoded
.replace("+", "%20");
223 return sb
.toString();
227 private ContentUtils() {