From e9d0731a65b0d3523906c58f987ad9610c4286b7 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Fri, 15 Jul 2022 13:35:23 +0200 Subject: [PATCH] Introduce WebDav support --- org.argeo.cms/.classpath | 2 +- .../org/argeo/cms/acr/CmsContentTypes.java | 2 + .../src/org/argeo/cms/acr/dav/DavContent.java | 110 +++++ .../argeo/cms/acr/dav/DavContentProvider.java | 76 +++ .../src/org/argeo/cms/acr/schemas/webdav.xsd | 449 ++++++++++++++++++ .../runtime/DeployedContentRepository.java | 17 +- .../src/org/argeo/util/dav/DavClient.java | 146 ++++++ .../src/org/argeo/util/dav/DavHeader.java | 5 + .../src/org/argeo/util/dav/DavMethod.java | 10 + .../src/org/argeo/util/dav/DavResponse.java | 49 ++ .../src/org/argeo/util/dav/DavXmlElement.java | 45 ++ .../org/argeo/util/dav/MultiStatusReader.java | 192 ++++++++ 12 files changed, 1092 insertions(+), 11 deletions(-) create mode 100644 org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java create mode 100644 org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java create mode 100644 org.argeo.cms/src/org/argeo/cms/acr/schemas/webdav.xsd create mode 100644 org.argeo.util/src/org/argeo/util/dav/DavClient.java create mode 100644 org.argeo.util/src/org/argeo/util/dav/DavHeader.java create mode 100644 org.argeo.util/src/org/argeo/util/dav/DavMethod.java create mode 100644 org.argeo.util/src/org/argeo/util/dav/DavResponse.java create mode 100644 org.argeo.util/src/org/argeo/util/dav/DavXmlElement.java create mode 100644 org.argeo.util/src/org/argeo/util/dav/MultiStatusReader.java diff --git a/org.argeo.cms/.classpath b/org.argeo.cms/.classpath index 4a00becd8..3628e3368 100644 --- a/org.argeo.cms/.classpath +++ b/org.argeo.cms/.classpath @@ -1,6 +1,6 @@ - + diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentTypes.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentTypes.java index 3f8fd9568..c4ab0b685 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentTypes.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentTypes.java @@ -24,6 +24,8 @@ public enum CmsContentTypes { // XLINK_1999("xlink", "http://www.w3.org/1999/xlink", "xlink.xsd", "http://www.w3.org/XML/2008/06/xlink.xsd"), // +// WEBDAV("dav", "DAV:", "webdav.xsd", "https://raw.githubusercontent.com/lookfirst/sardine/master/webdav.xsd"), + // XSLT_2_0("xsl", "http://www.w3.org/1999/XSL/Transform", "schema-for-xslt20.xsd", "https://www.w3.org/2007/schema-for-xslt20.xsd"), // diff --git a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java new file mode 100644 index 000000000..0003e5334 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContent.java @@ -0,0 +1,110 @@ +package org.argeo.cms.acr.dav; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.acr.AbstractContent; +import org.argeo.cms.acr.ContentUtils; +import org.argeo.util.dav.DavResponse; + +public class DavContent extends AbstractContent { + private final DavContentProvider provider; + private final URI uri; + + private Set keyNames; + private Optional> values; + + public DavContent(ProvidedSession session, DavContentProvider provider, URI uri, Set keyNames) { + this(session, provider, uri, keyNames, Optional.empty()); + } + + public DavContent(ProvidedSession session, DavContentProvider provider, URI uri, Set keyNames, + Optional> values) { + super(session); + this.provider = provider; + this.uri = uri; + this.keyNames = keyNames; + this.values = values; + } + + @Override + public QName getName() { + String fileName = ContentUtils.getParentPath(uri.getPath())[1]; + ContentName name = NamespaceUtils.parsePrefixedName(provider, fileName); + return name; + } + + @Override + public Content getParent() { + try { + String parentPath = ContentUtils.getParentPath(uri.getPath())[0]; + URI parentUri = new URI(uri.getScheme(), uri.getHost(), parentPath, null); + return provider.getDavContent(getSession(), parentUri); + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot create parent", e); + } + } + + @Override + public Iterator iterator() { + Iterator responses = provider.getDavClient().listChildren(uri); + return new DavResponseIterator(responses); + } + + @Override + protected Iterable keys() { + return keyNames; + } + + @SuppressWarnings("unchecked") + @Override + public Optional get(QName key, Class clss) { + if (values.isEmpty()) { + DavResponse response = provider.getDavClient().get(uri); + values = Optional.of(response.getProperties()); + } + String valueStr = values.get().get(key); + if (valueStr == null) + return Optional.empty(); + // TODO convert + return Optional.of((A) valueStr); + } + + @Override + public ContentProvider getProvider() { + return provider; + } + + class DavResponseIterator implements Iterator { + private final Iterator responses; + + public DavResponseIterator(Iterator responses) { + this.responses = responses; + } + + @Override + public boolean hasNext() { + return responses.hasNext(); + } + + @Override + public Content next() { + DavResponse response = responses.next(); + String relativePath = response.getHref(); + URI contentUri = provider.relativePathToUri(relativePath); + return new DavContent(getSession(), provider, contentUri, response.getPropertyNames()); + } + + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java new file mode 100644 index 000000000..4f7699b8d --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/dav/DavContentProvider.java @@ -0,0 +1,76 @@ +package org.argeo.cms.acr.dav; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Iterator; + +import org.argeo.api.acr.RuntimeNamespaceContext; +import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedContent; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.util.dav.DavClient; +import org.argeo.util.dav.DavResponse; + +public class DavContentProvider implements ContentProvider { + private String mountPath; + private URI baseUri; + + private DavClient davClient; + + public DavContentProvider(String mountPath, URI baseUri) { + this.mountPath = mountPath; + this.baseUri = baseUri; + if (!baseUri.getPath().endsWith("/")) + throw new IllegalArgumentException("Base URI " + baseUri + " path does not end with /"); + this.davClient = new DavClient(); + } + + @Override + public String getNamespaceURI(String prefix) { + // FIXME retrieve mappings from WebDav + return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + // FIXME retrieve mappings from WebDav + return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI); + } + + @Override + public ProvidedContent get(ProvidedSession session, String relativePath) { + URI contentUri = relativePathToUri(relativePath); + return getDavContent(session, contentUri); + } + + DavContent getDavContent(ProvidedSession session, URI uri) { + DavResponse response = davClient.get(uri); + return new DavContent(session, this, uri, response.getPropertyNames()); + } + + @Override + public boolean exists(ProvidedSession session, String relativePath) { + URI contentUri = relativePathToUri(relativePath); + return davClient.exists(contentUri); + } + + @Override + public String getMountPath() { + return mountPath; + } + + DavClient getDavClient() { + return davClient; + } + + URI relativePathToUri(String relativePath) { + try { + // TODO check last slash + String path = relativePath.startsWith("/") ? relativePath : baseUri.getPath() + relativePath; + URI uri = new URI(baseUri.getScheme(), baseUri.getHost(), path, baseUri.getFragment()); + return uri; + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot build URI for " + relativePath + " relatively to " + baseUri, e); + } + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/acr/schemas/webdav.xsd b/org.argeo.cms/src/org/argeo/cms/acr/schemas/webdav.xsd new file mode 100644 index 000000000..e7443f718 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/acr/schemas/webdav.xsd @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java index aa7d8f884..72a30fb08 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/DeployedContentRepository.java @@ -39,11 +39,16 @@ public class DeployedContentRepository extends CmsContentRepository { DirectoryContentProvider directoryContentProvider = new DirectoryContentProvider( CmsContentRepository.DIRECTORY_BASE, userManager); addProvider(directoryContentProvider); + + // remote +// DavContentProvider davContentProvider = new DavContentProvider("/srv", +// URI.create("http://localhost/unstable/a2/")); +// addProvider(davContentProvider); } catch (IOException e) { throw new IllegalStateException("Cannot start content repository", e); } - long duration = System.currentTimeMillis()-begin; - log.debug(() -> "CMS content repository available (initialisation took "+duration+" ms)"); + long duration = System.currentTimeMillis() - begin; + log.debug(() -> "CMS content repository available (initialisation took " + duration + " ms)"); } @Override @@ -51,14 +56,6 @@ public class DeployedContentRepository extends CmsContentRepository { super.stop(); } -// public void addContentProvider(ContentProvider provider, Map properties) { -//// String base = LangUtils.get(properties, CmsContentRepository.ACR_MOUNT_PATH_PROPERTY); -// addProvider(provider); -// } - -// public void removeContentProvider(ContentProvider provider, Map properties) { -// } - public void setUserManager(CmsUserManager userManager) { this.userManager = userManager; } diff --git a/org.argeo.util/src/org/argeo/util/dav/DavClient.java b/org.argeo.util/src/org/argeo/util/dav/DavClient.java new file mode 100644 index 000000000..4fa8648cf --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/dav/DavClient.java @@ -0,0 +1,146 @@ +package org.argeo.util.dav; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandler; +import java.net.http.HttpResponse.BodyHandlers; +import java.util.Iterator; + +import javax.xml.namespace.QName; + +public class DavClient { + + private HttpClient httpClient; + + public DavClient() { + httpClient = HttpClient.newBuilder() // +// .sslContext(insecureContext()) // + .version(HttpClient.Version.HTTP_1_1) // + .build(); + } + + public void setProperty(String url, QName key, String value) { + try { + String body = """ + + " + // + """ + + + """ // + + "<" + key.getPrefix() + ":" + key.getLocalPart() + ">" + value + "" + // + """ + + + + """; + System.out.println(body); + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)) // + .header("Depth", "1") // + .method(DavMethod.PROPPATCH.name(), BodyPublishers.ofString(body)) // + .build(); + BodyHandler bodyHandler = BodyHandlers.ofString(); + HttpResponse response = httpClient.send(request, bodyHandler); + System.out.println(response.body()); + } catch (IOException | InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public Iterator listChildren(URI uri) { + try { + String body = """ + + + + """; + HttpRequest request = HttpRequest.newBuilder().uri(uri) // + .header(DavHeader.Depth.name(), "1") // + .method(DavMethod.PROPFIND.name(), BodyPublishers.ofString(body)) // + .build(); + +// HttpResponse responseStr = httpClient.send(request, BodyHandlers.ofString()); +// System.out.println(responseStr.body()); + + HttpResponse response = httpClient.send(request, BodyHandlers.ofInputStream()); + MultiStatusReader msReader = new MultiStatusReader(response.body(), uri.getPath()); + return msReader; + } catch (IOException | InterruptedException e) { + throw new IllegalStateException("Cannot list children of " + uri, e); + } + + } + + public boolean exists(URI uri) { + try { + HttpRequest request = HttpRequest.newBuilder().uri(uri) // + .header(DavHeader.Depth.name(), "0") // + .method(DavMethod.HEAD.name(), BodyPublishers.noBody()) // + .build(); + BodyHandler bodyHandler = BodyHandlers.ofString(); + HttpResponse response = httpClient.send(request, bodyHandler); + System.out.println(response.body()); + int responseStatusCode = response.statusCode(); + if (responseStatusCode == 404) + return false; + if (responseStatusCode >= 200 && responseStatusCode < 300) + return true; + throw new IllegalStateException( + "Cannot check whether " + uri + " exists: Unknown response status code " + responseStatusCode); + } catch (IOException | InterruptedException e) { + throw new IllegalStateException("Cannot check whether " + uri + " exists", e); + } + + } + + public DavResponse get(URI uri) { + try { + String body = """ + + + + """; + HttpRequest request = HttpRequest.newBuilder().uri(uri) // + .header(DavHeader.Depth.name(), "0") // + .method(DavMethod.PROPFIND.name(), BodyPublishers.ofString(body)) // + .build(); + +// HttpResponse responseStr = httpClient.send(request, BodyHandlers.ofString()); +// System.out.println(responseStr.body()); + + HttpResponse response = httpClient.send(request, BodyHandlers.ofInputStream()); + MultiStatusReader msReader = new MultiStatusReader(response.body()); + if (!msReader.hasNext()) + throw new IllegalArgumentException(uri + " does not exist"); + return msReader.next(); + } catch (IOException | InterruptedException e) { + throw new IllegalStateException("Cannot list children of " + uri, e); + } + + } + + public static void main(String[] args) { + DavClient davClient = new DavClient(); + Iterator responses = davClient + .listChildren(URI.create("http://localhost/unstable/a2/org.argeo.tp.sdk/")); + while (responses.hasNext()) { + DavResponse response = responses.next(); + System.out.println(response.getHref() + (response.isCollection() ? " (collection)" : "")); + System.out.println(" " + response.getPropertyNames()); + + } +// davClient.setProperty("http://localhost/unstable/a2/org.argeo.tp.sdk/org.opentest4j.1.2.jar", +// CrName.uuid.qName(), UUID.randomUUID().toString()); + + } + +} diff --git a/org.argeo.util/src/org/argeo/util/dav/DavHeader.java b/org.argeo.util/src/org/argeo/util/dav/DavHeader.java new file mode 100644 index 000000000..e527ba7a8 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/dav/DavHeader.java @@ -0,0 +1,5 @@ +package org.argeo.util.dav; + +public enum DavHeader { + Depth; +} diff --git a/org.argeo.util/src/org/argeo/util/dav/DavMethod.java b/org.argeo.util/src/org/argeo/util/dav/DavMethod.java new file mode 100644 index 000000000..1472c9b30 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/dav/DavMethod.java @@ -0,0 +1,10 @@ +package org.argeo.util.dav; + +public enum DavMethod { + // Generic HTTP + HEAD, // + // WebDav specific + PROPFIND, // + PROPPATCH, // + ; +} diff --git a/org.argeo.util/src/org/argeo/util/dav/DavResponse.java b/org.argeo.util/src/org/argeo/util/dav/DavResponse.java new file mode 100644 index 000000000..22ffa17d1 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/dav/DavResponse.java @@ -0,0 +1,49 @@ +package org.argeo.util.dav; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.xml.namespace.QName; + +public class DavResponse { + final static String MODE_DAV_NAMESPACE = "http://apache.org/dav/props/"; + + private String href; + private boolean collection; + private Set propertyNames = new HashSet<>(); + private Map properties = new HashMap<>(); + private List resourceTypes = new ArrayList<>(); + + public Map getProperties() { + return properties; + } + + void setHref(String href) { + this.href = href; + } + + public String getHref() { + return href; + } + + public boolean isCollection() { + return collection; + } + + void setCollection(boolean collection) { + this.collection = collection; + } + + public List getResourceTypes() { + return resourceTypes; + } + + public Set getPropertyNames() { + return propertyNames; + } + +} diff --git a/org.argeo.util/src/org/argeo/util/dav/DavXmlElement.java b/org.argeo.util/src/org/argeo/util/dav/DavXmlElement.java new file mode 100644 index 000000000..c05425298 --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/dav/DavXmlElement.java @@ -0,0 +1,45 @@ +package org.argeo.util.dav; + +import javax.xml.namespace.QName; + +import org.argeo.util.naming.QNamed; + +public enum DavXmlElement implements QNamed { + response, // + href, // + collection, // + prop, // + resourcetype, // + + // locking + lockscope, // + locktype, // + supportedlock, // + lockentry, // + lockdiscovery, // + write, // + shared, // + exclusive, // + ; + + final static String WEBDAV_NAMESPACE_URI = "DAV:"; + final static String WEBDAV_DEFAULT_PREFIX = "D"; + + @Override + public String getNamespace() { + return WEBDAV_NAMESPACE_URI; + } + + @Override + public String getDefaultPrefix() { + return WEBDAV_DEFAULT_PREFIX; + } + + public static DavXmlElement toEnum(QName name) { + for (DavXmlElement e : values()) { + if (e.qName().equals(name)) + return e; + } + return null; + } +} diff --git a/org.argeo.util/src/org/argeo/util/dav/MultiStatusReader.java b/org.argeo.util/src/org/argeo/util/dav/MultiStatusReader.java new file mode 100644 index 000000000..966100e4a --- /dev/null +++ b/org.argeo.util/src/org/argeo/util/dav/MultiStatusReader.java @@ -0,0 +1,192 @@ +package org.argeo.util.dav; + +import java.io.InputStream; +import java.util.Iterator; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.xml.namespace.QName; +import javax.xml.stream.FactoryConfigurationError; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +class MultiStatusReader implements Iterator { + private CompletableFuture empty = new CompletableFuture(); + private AtomicBoolean processed = new AtomicBoolean(false); + + private BlockingQueue queue = new ArrayBlockingQueue<>(64); + + private final String ignoredHref; + + public MultiStatusReader(InputStream in) { + this(in, null); + } + + /** Typically ignoring self */ + public MultiStatusReader(InputStream in, String ignoredHref) { + this.ignoredHref = ignoredHref; + ForkJoinPool.commonPool().execute(() -> process(in)); + + } + + protected void process(InputStream in) { + try { + XMLInputFactory inputFactory = XMLInputFactory.newFactory(); + XMLStreamReader reader = inputFactory.createXMLStreamReader(in); + + DavResponse currentResponse = null; + boolean collectiongProperties = false; + + final QName COLLECTION = DavXmlElement.collection.qName(); // optimisation + elements: while (reader.hasNext()) { + reader.next(); + if (reader.isStartElement()) { + QName name = reader.getName(); +// System.out.println(name); + DavXmlElement davXmlElement = DavXmlElement.toEnum(name); + if (davXmlElement != null) { + switch (davXmlElement) { + case response: + currentResponse = new DavResponse(); + break; + case href: + assert currentResponse != null; + while (reader.hasNext() && !reader.hasText()) + reader.next(); + String href = reader.getText(); + currentResponse.setHref(href); + break; +// case collection: +// currentResponse.setCollection(true); +// break; + case prop: + collectiongProperties = true; + break; + case resourcetype: + while (reader.hasNext()) { + int event = reader.nextTag(); + QName resourceType = reader.getName(); + if (event == XMLStreamConstants.END_ELEMENT && name.equals(resourceType)) + break; + assert currentResponse != null; + if (event == XMLStreamConstants.START_ELEMENT) { + if (COLLECTION.equals(resourceType)) + currentResponse.setCollection(true); + else + currentResponse.getResourceTypes().add(resourceType); + } + } + break; + default: + // ignore + } + } else { + if (collectiongProperties) { + String value = null; + // TODO deal with complex properties + readProperty: while (reader.hasNext()) { + reader.next(); + if (reader.getEventType() == XMLStreamConstants.END_ELEMENT) + break readProperty; + if (reader.getEventType() == XMLStreamConstants.CHARACTERS) + value = reader.getText(); + } + + if (name.getNamespaceURI().equals(DavResponse.MODE_DAV_NAMESPACE)) + continue elements; // skip mod_dav properties + + assert currentResponse != null; + currentResponse.getPropertyNames().add(name); + if (value != null) + currentResponse.getProperties().put(name, value); + + } + } + } else if (reader.isEndElement()) { + QName name = reader.getName(); +// System.out.println(name); + DavXmlElement davXmlElement = DavXmlElement.toEnum(name); + if (davXmlElement != null) + switch (davXmlElement) { + case response: + assert currentResponse != null; + if (ignoredHref == null || !ignoredHref.equals(currentResponse.getHref())) { + if (!empty.isDone()) + empty.complete(false); + publish(currentResponse); + } + case prop: + collectiongProperties = false; + break; + default: + // ignore + } + } + } + + if (!empty.isDone()) + empty.complete(true); + } catch (FactoryConfigurationError | XMLStreamException e) { + throw new IllegalStateException("Cannot process DAV response", e); + } finally { + processed(); + } + } + + protected synchronized void publish(DavResponse response) { + try { + queue.put(response); + } catch (InterruptedException e) { + throw new IllegalStateException("Cannot put response " + response, e); + } finally { + notifyAll(); + } + } + + protected synchronized void processed() { + processed.set(true); + notifyAll(); + } + + @Override + public synchronized boolean hasNext() { + try { + if (empty.get()) + return false; + while (!processed.get() && queue.isEmpty()) { + wait(); + } + if (!queue.isEmpty()) + return true; + if (processed.get()) + return false; + throw new IllegalStateException("Cannot determine hasNext"); + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException("Cannot determine hasNext", e); + } finally { + notifyAll(); + } + } + + @Override + public synchronized DavResponse next() { + try { + if (!hasNext()) + throw new IllegalStateException("No fursther items are available"); + + DavResponse response = queue.take(); + return response; + } catch (InterruptedException e) { + throw new IllegalStateException("Cannot get next", e); + } finally { + notifyAll(); + } + } + +} -- 2.30.2