1 package org
.argeo
.cms
.internal
.runtime
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.io
.OutputStream
;
6 import java
.util
.Collection
;
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
;
14 import javax
.xml
.namespace
.QName
;
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
;
35 import com
.sun
.net
.httpserver
.HttpExchange
;
36 import com
.sun
.net
.httpserver
.HttpHandler
;
38 public class CmsAcrHttpHandler
implements HttpHandler
{
39 private ProvidedRepository contentRepository
;
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
)) {
49 throw new IllegalArgumentException("Unsupported method " + method
);
54 protected void handlePROPFIND(HttpExchange exchange
) throws IOException
{
55 String relativePath
= HttpServerUtils
.relativize(exchange
);
57 DavDepth depth
= DavDepth
.fromHttpExchange(exchange
);
59 // default, as per http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
60 depth
= DavDepth
.DEPTH_INFINITY
;
63 ContentSession session
= RemoteAuthUtils
.doAs(() -> contentRepository
.get(),
64 new RemoteAuthHttpExchange(exchange
));
66 String path
= ContentUtils
.ROOT_SLASH
+ relativePath
;
67 if (!session
.exists(path
)) {// not found
68 exchange
.sendResponseHeaders(HttpResponseStatus
.NOT_FOUND
.getCode(), -1);
71 Content content
= session
.get(path
);
73 CompletableFuture
<Void
> published
= new CompletableFuture
<Void
>();
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);
82 exchange
.sendResponseHeaders(HttpResponseStatus
.MULTI_STATUS
.getCode(), 0l);
83 try (OutputStream out
= exchange
.getResponseBody()) {
84 msWriter
.process(session
, out
, published
.minimalCompletionStage(), davPropfind
.isPropname());
89 protected void publishDavResponses(Content content
, DavPropfind davPropfind
, Consumer
<DavResponse
> consumer
) {
90 publishDavResponse(content
, davPropfind
, consumer
, 0);
93 protected void publishDavResponse(Content content
, DavPropfind davPropfind
, Consumer
<DavResponse
> consumer
,
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());
105 davResponse
.getResourceTypes().addAll(content
.getContentClasses());
106 } else if (davPropfind
.isPropname()) {
107 for (QName key
: content
.keySet()) {
108 davResponse
.getPropertyNames().add(key
);
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
);
117 if (DavXmlElement
.resourcetype
.qName().equals(key
)) {
118 davResponse
.getResourceTypes().addAll(content
.getContentClasses());
124 consumer
.accept(davResponse
);
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);
137 protected void processMapEntry(DavResponse davResponse
, QName key
, Object value
) {
138 // ignore content classes
139 if (CrName
.cc
.qName().equals(key
))
142 if (value
instanceof Collection
) {
143 StringJoiner sj
= new StringJoiner("\n");
144 for (Object v
: (Collection
<?
>) value
) {
145 sj
.add(v
.toString());
149 str
= value
.toString();
151 davResponse
.getProperties().put(key
, str
);
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
);
169 public void setContentRepository(ProvidedRepository contentRepository
) {
170 this.contentRepository
= contentRepository
;