]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsAcrHttpHandler.java
92da1b0c298ed3524087cf308ff0703ddc3af173
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / internal / runtime / CmsAcrHttpHandler.java
1 package org.argeo.cms.internal.runtime;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.util.Collection;
7 import java.util.Map;
8 import java.util.Optional;
9 import java.util.StringJoiner;
10 import java.util.concurrent.CompletableFuture;
11 import java.util.concurrent.ForkJoinPool;
12 import java.util.function.Consumer;
13
14 import javax.xml.namespace.QName;
15
16 import org.argeo.api.acr.Content;
17 import org.argeo.api.acr.ContentSession;
18 import org.argeo.api.acr.CrName;
19 import org.argeo.api.acr.spi.ProvidedRepository;
20 import org.argeo.api.cms.CmsConstants;
21 import org.argeo.cms.acr.ContentUtils;
22 import org.argeo.cms.auth.RemoteAuthUtils;
23 import org.argeo.cms.dav.DavDepth;
24 import org.argeo.cms.dav.DavMethod;
25 import org.argeo.cms.dav.DavPropfind;
26 import org.argeo.cms.dav.DavResponse;
27 import org.argeo.cms.dav.DavXmlElement;
28 import org.argeo.cms.dav.MultiStatusWriter;
29 import org.argeo.cms.internal.http.RemoteAuthHttpExchange;
30 import org.argeo.util.StreamUtils;
31 import org.argeo.util.http.HttpMethod;
32 import org.argeo.util.http.HttpResponseStatus;
33 import org.argeo.util.http.HttpServerUtils;
34
35 import com.sun.net.httpserver.HttpExchange;
36 import com.sun.net.httpserver.HttpHandler;
37
38 public class CmsAcrHttpHandler implements HttpHandler {
39 private ProvidedRepository contentRepository;
40
41 @Override
42 public void handle(HttpExchange exchange) throws IOException {
43 String method = exchange.getRequestMethod();
44 if (DavMethod.PROPFIND.name().equals(method)) {
45 handlePROPFIND(exchange);
46 } else if (HttpMethod.GET.name().equals(method)) {
47 handleGET(exchange);
48 } else {
49 throw new IllegalArgumentException("Unsupported method " + method);
50 }
51
52 }
53
54 protected void handlePROPFIND(HttpExchange exchange) throws IOException {
55 String relativePath = HttpServerUtils.relativize(exchange);
56
57 DavDepth depth = DavDepth.fromHttpExchange(exchange);
58 if (depth == null) {
59 // default, as per http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
60 depth = DavDepth.DEPTH_INFINITY;
61 }
62
63 ContentSession session = RemoteAuthUtils.doAs(() -> contentRepository.get(),
64 new RemoteAuthHttpExchange(exchange));
65
66 String path = ContentUtils.ROOT_SLASH + relativePath;
67 if (!session.exists(path)) {// not found
68 exchange.sendResponseHeaders(HttpResponseStatus.NOT_FOUND.getCode(), -1);
69 return;
70 }
71 Content content = session.get(path);
72
73 CompletableFuture<Void> published = new CompletableFuture<Void>();
74
75 try (InputStream in = exchange.getRequestBody()) {
76 DavPropfind davPropfind = DavPropfind.load(depth, in);
77 MultiStatusWriter msWriter = new MultiStatusWriter();
78 ForkJoinPool.commonPool().execute(() -> {
79 publishDavResponses(content, davPropfind, msWriter);
80 published.complete(null);
81 });
82 exchange.sendResponseHeaders(HttpResponseStatus.MULTI_STATUS.getCode(), 0l);
83 try (OutputStream out = exchange.getResponseBody()) {
84 msWriter.process(session, out, published.minimalCompletionStage(), davPropfind.isPropname());
85 }
86 }
87 }
88
89 protected void publishDavResponses(Content content, DavPropfind davPropfind, Consumer<DavResponse> consumer) {
90 publishDavResponse(content, davPropfind, consumer, 0);
91 }
92
93 protected void publishDavResponse(Content content, DavPropfind davPropfind, Consumer<DavResponse> consumer,
94 int currentDepth) {
95 DavResponse davResponse = new DavResponse();
96 String href = CmsConstants.PATH_API_ACR + content.getPath();
97 davResponse.setHref(href);
98 if (content.hasContentClass(CrName.collection))
99 davResponse.setCollection(true);
100 if (davPropfind.isAllprop()) {
101 for (Map.Entry<QName, Object> entry : content.entrySet()) {
102 davResponse.getPropertyNames().add(entry.getKey());
103 processMapEntry(davResponse, entry.getKey(), entry.getValue());
104 }
105 davResponse.getResourceTypes().addAll(content.getContentClasses());
106 } else if (davPropfind.isPropname()) {
107 for (QName key : content.keySet()) {
108 davResponse.getPropertyNames().add(key);
109 }
110 } else {
111 for (QName key : davPropfind.getProps()) {
112 if (content.containsKey(key)) {
113 davResponse.getPropertyNames().add(key);
114 Object value = content.get(key);
115 processMapEntry(davResponse, key, value);
116 }
117 if (DavXmlElement.resourcetype.qName().equals(key)) {
118 davResponse.getResourceTypes().addAll(content.getContentClasses());
119 }
120 }
121
122 }
123
124 consumer.accept(davResponse);
125
126 // recurse only on collections
127 if (content.hasContentClass(CrName.collection)) {
128 if (davPropfind.getDepth() == DavDepth.DEPTH_INFINITY
129 || (davPropfind.getDepth() == DavDepth.DEPTH_1 && currentDepth == 0)) {
130 for (Content child : content) {
131 publishDavResponse(child, davPropfind, consumer, currentDepth + 1);
132 }
133 }
134 }
135 }
136
137 protected void processMapEntry(DavResponse davResponse, QName key, Object value) {
138 // ignore content classes
139 if (CrName.cc.qName().equals(key))
140 return;
141 String str;
142 if (value instanceof Collection) {
143 StringJoiner sj = new StringJoiner("\n");
144 for (Object v : (Collection<?>) value) {
145 sj.add(v.toString());
146 }
147 str = sj.toString();
148 } else {
149 str = value.toString();
150 }
151 davResponse.getProperties().put(key, str);
152
153 }
154
155 protected void handleGET(HttpExchange exchange) {
156 String relativePath = HttpServerUtils.relativize(exchange);
157 ContentSession session = RemoteAuthUtils.doAs(() -> contentRepository.get(),
158 new RemoteAuthHttpExchange(exchange));
159 Content content = session.get(ContentUtils.ROOT_SLASH + relativePath);
160 Optional<Long> size = content.get(CrName.size, Long.class);
161 try (InputStream in = content.open(InputStream.class)) {
162 exchange.sendResponseHeaders(HttpResponseStatus.OK.getCode(), size.orElse(0l));
163 StreamUtils.copy(in, exchange.getResponseBody());
164 } catch (IOException e) {
165 throw new RuntimeException("Cannot process " + relativePath, e);
166 }
167 }
168
169 public void setContentRepository(ProvidedRepository contentRepository) {
170 this.contentRepository = contentRepository;
171 }
172
173 }