]> git.argeo.org Git - lgpl/argeo-commons.git/blob - cms/acr/CmsContentRepository.java
Prepare next development cycle
[lgpl/argeo-commons.git] / cms / acr / CmsContentRepository.java
1 package org.argeo.cms.acr;
2
3 import java.io.IOException;
4 import java.io.Writer;
5 import java.nio.charset.StandardCharsets;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.util.Collections;
9 import java.util.HashMap;
10 import java.util.Iterator;
11 import java.util.Locale;
12 import java.util.Map;
13 import java.util.NavigableMap;
14 import java.util.Set;
15 import java.util.TreeMap;
16 import java.util.concurrent.CompletableFuture;
17 import java.util.concurrent.CompletionStage;
18 import java.util.stream.Collectors;
19
20 import javax.security.auth.Subject;
21 import javax.security.auth.login.LoginContext;
22 import javax.security.auth.login.LoginException;
23 import javax.xml.parsers.DocumentBuilder;
24 import javax.xml.parsers.DocumentBuilderFactory;
25 import javax.xml.parsers.ParserConfigurationException;
26 import javax.xml.transform.Transformer;
27 import javax.xml.transform.TransformerException;
28 import javax.xml.transform.TransformerFactory;
29 import javax.xml.transform.TransformerFactoryConfigurationError;
30 import javax.xml.transform.dom.DOMSource;
31 import javax.xml.transform.stream.StreamResult;
32 import javax.xml.validation.Schema;
33
34 import org.argeo.api.acr.Content;
35 import org.argeo.api.acr.ContentSession;
36 import org.argeo.api.acr.ContentUtils;
37 import org.argeo.api.acr.CrName;
38 import org.argeo.api.acr.NamespaceUtils;
39 import org.argeo.api.acr.spi.ContentProvider;
40 import org.argeo.api.acr.spi.ProvidedRepository;
41 import org.argeo.api.acr.spi.ProvidedSession;
42 import org.argeo.api.cms.CmsAuth;
43 import org.argeo.api.cms.CmsLog;
44 import org.argeo.api.cms.CmsSession;
45 import org.argeo.cms.acr.xml.DomContentProvider;
46 import org.argeo.cms.auth.CurrentUser;
47 import org.argeo.cms.internal.runtime.CmsContextImpl;
48 import org.w3c.dom.DOMException;
49 import org.w3c.dom.Document;
50 import org.w3c.dom.Element;
51 import org.xml.sax.ErrorHandler;
52 import org.xml.sax.InputSource;
53 import org.xml.sax.SAXException;
54 import org.xml.sax.SAXParseException;
55
56 /**
57 * Base implementation of a {@link ProvidedRepository} integrated with a CMS.
58 */
59 public class CmsContentRepository implements ProvidedRepository {
60 private final static CmsLog log = CmsLog.getLog(CmsContentRepository.class);
61
62 private NavigableMap<String, ContentProvider> partitions = new TreeMap<>();
63
64 // TODO synchronize ?
65 private NavigableMap<String, String> prefixes = new TreeMap<>();
66
67 private Schema schema;
68
69 private CmsContentSession systemSession;
70
71 private Map<CmsSession, CmsContentSession> userSessions = Collections.synchronizedMap(new HashMap<>());
72
73 public CmsContentRepository() {
74 prefixes.put(CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI);
75 prefixes.put("basic", CrName.CR_NAMESPACE_URI);
76 prefixes.put("owner", CrName.CR_NAMESPACE_URI);
77 prefixes.put("posix", CrName.CR_NAMESPACE_URI);
78
79 systemSession = newSystemSession();
80 }
81
82 protected CmsContentSession newSystemSession() {
83 LoginContext loginContext;
84 try {
85 loginContext = new LoginContext(CmsAuth.DATA_ADMIN.getLoginContextName());
86 loginContext.login();
87 } catch (LoginException e1) {
88 throw new RuntimeException("Could not login as data admin", e1);
89 } finally {
90 }
91 return new CmsContentSession(loginContext.getSubject(), Locale.getDefault());
92 }
93
94 public void start() {
95 }
96
97 public void stop() {
98
99 }
100
101 /*
102 * REPOSITORY
103 */
104
105 @Override
106 public ContentSession get() {
107 return get(CmsContextImpl.getCmsContext().getDefaultLocale());
108 }
109
110 @Override
111 public ContentSession get(Locale locale) {
112 // Subject subject = Subject.getSubject(AccessController.getContext());
113 CmsSession cmsSession = CurrentUser.getCmsSession();
114 CmsContentSession contentSession = userSessions.get(cmsSession);
115 if (contentSession == null) {
116 final CmsContentSession newContentSession = new CmsContentSession(cmsSession.getSubject(), locale);
117 cmsSession.addOnCloseCallback((c) -> {
118 newContentSession.close();
119 userSessions.remove(cmsSession);
120 });
121 contentSession = newContentSession;
122 }
123 return contentSession;
124 }
125
126 public void addProvider(String base, ContentProvider provider) {
127 partitions.put(base, provider);
128 if ("/".equals(base))// root
129 return;
130 String[] parentPath = ContentUtils.getParentPath(base);
131 Content parent = systemSession.get(parentPath[0]);
132 Content mount = parent.add(parentPath[1]);
133 // TODO use a boolean
134 // ContentName name = new ContentName(CrName.MOUNT.getNamespaceURI(),
135 // CrName.MOUNT.name(), systemSession);
136 mount.put(CrName.MOUNT.get(), "true");
137 }
138
139 public void registerPrefix(String prefix, String namespaceURI) {
140 String registeredUri = prefixes.get(prefix);
141 if (registeredUri == null) {
142 prefixes.put(prefix, namespaceURI);
143 return;
144 }
145 if (!registeredUri.equals(namespaceURI))
146 throw new IllegalStateException("Prefix " + prefix + " is already registred for " + registeredUri);
147 // do nothing if same namespace is already registered
148 }
149
150 /*
151 * FACTORIES
152 */
153 public void initRootContentProvider(Path path) {
154 try {
155 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
156 factory.setNamespaceAware(true);
157 factory.setXIncludeAware(true);
158 // factory.setSchema(schema);
159
160 DocumentBuilder dBuilder = factory.newDocumentBuilder();
161 dBuilder.setErrorHandler(new ErrorHandler() {
162
163 @Override
164 public void warning(SAXParseException exception) throws SAXException {
165 }
166
167 @Override
168 public void fatalError(SAXParseException exception) throws SAXException {
169 }
170
171 @Override
172 public void error(SAXParseException exception) throws SAXException {
173 log.error(exception);
174
175 }
176 });
177
178 Document document;
179 if (Files.exists(path)) {
180 InputSource inputSource = new InputSource(path.toAbsolutePath().toUri().toString());
181 inputSource.setEncoding(StandardCharsets.UTF_8.name());
182 // TODO public id as well?
183 document = dBuilder.parse(inputSource);
184 } else {
185 document = dBuilder.newDocument();
186 // Element root = document.createElementNS(CrName.ROOT.getNamespaceURI(),
187 // CrName.ROOT.get().toPrefixedString());
188 Element root = document.createElement(CrName.ROOT.get().toPrefixedString());
189 // root.setAttribute("xmlns", "");
190 root.setAttribute("xmlns:" + CrName.CR_DEFAULT_PREFIX, CrName.CR_NAMESPACE_URI);
191 document.appendChild(root);
192
193 // write it
194 TransformerFactory transformerFactory = TransformerFactory.newInstance();
195 Transformer transformer = transformerFactory.newTransformer();
196 DOMSource source = new DOMSource(document);
197 try (Writer writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
198 StreamResult result = new StreamResult(writer);
199 transformer.transform(source, result);
200 }
201 }
202
203 DomContentProvider contentProvider = new DomContentProvider(document);
204 addProvider("/", contentProvider);
205 } catch (DOMException | ParserConfigurationException | SAXException | IOException
206 | TransformerFactoryConfigurationError | TransformerException e) {
207 throw new IllegalStateException("Cannot init ACR root " + path, e);
208 }
209
210 }
211
212 /*
213 * NAMESPACE CONTEXT
214 */
215
216 /*
217 * SESSION
218 */
219
220 class CmsContentSession implements ProvidedSession {
221 private Subject subject;
222 private Locale locale;
223
224 private CompletableFuture<ProvidedSession> closed = new CompletableFuture<>();
225
226 public CmsContentSession(Subject subject, Locale locale) {
227 this.subject = subject;
228 this.locale = locale;
229 }
230
231 public void close() {
232 closed.complete(this);
233 }
234
235 @Override
236 public CompletionStage<ProvidedSession> onClose() {
237 return closed.minimalCompletionStage();
238 }
239
240 @Override
241 public Content get(String path) {
242 Map.Entry<String, ContentProvider> entry = partitions.floorEntry(path);
243 String mountPath = entry.getKey();
244 ContentProvider provider = entry.getValue();
245 String relativePath = path.substring(mountPath.length());
246 if (relativePath.length() > 0 && relativePath.charAt(0) == '/')
247 relativePath = relativePath.substring(1);
248 return provider.get(CmsContentSession.this, mountPath, relativePath);
249 }
250
251 @Override
252 public Subject getSubject() {
253 return subject;
254 }
255
256 @Override
257 public Locale getLocale() {
258 return locale;
259 }
260
261 @Override
262 public ProvidedRepository getRepository() {
263 return CmsContentRepository.this;
264 }
265
266 /*
267 * NAMESPACE CONTEXT
268 */
269
270 @Override
271 public String getNamespaceURI(String prefix) {
272 return NamespaceUtils.getNamespaceURI((p) -> prefixes.get(p), prefix);
273 }
274
275 @Override
276 public Iterator<String> getPrefixes(String namespaceURI) {
277 return NamespaceUtils.getPrefixes((ns) -> prefixes.entrySet().stream().filter(e -> e.getValue().equals(ns))
278 .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), namespaceURI);
279 }
280
281 // @Override
282 // public String findNamespace(String prefix) {
283 // return prefixes.get(prefix);
284 // }
285 //
286 // @Override
287 // public Set<String> findPrefixes(String namespaceURI) {
288 // Set<String> res = prefixes.entrySet().stream().filter(e -> e.getValue().equals(namespaceURI))
289 // .map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
290 //
291 // return res;
292 // }
293 //
294 // @Override
295 // public String findPrefix(String namespaceURI) {
296 // if (CrName.CR_NAMESPACE_URI.equals(namespaceURI) && prefixes.containsKey(CrName.CR_DEFAULT_PREFIX))
297 // return CrName.CR_DEFAULT_PREFIX;
298 // return ProvidedSession.super.findPrefix(namespaceURI);
299 // }
300
301 }
302
303 }