All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.trellisldp.webdav.TrellisWebDAV Maven / Gradle / Ivy

/*
 * Copyright (c) 2020 Aaron Coburn and individual contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.trellisldp.webdav;

import static java.time.ZoneOffset.UTC;
import static java.time.ZonedDateTime.ofInstant;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
import static java.time.temporal.ChronoUnit.SECONDS;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static javax.ws.rs.core.MediaType.APPLICATION_XML;
import static javax.ws.rs.core.Response.Status.CONFLICT;
import static javax.ws.rs.core.Response.Status.GONE;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static javax.ws.rs.core.Response.status;
import static javax.ws.rs.core.UriBuilder.fromUri;
import static javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD;
import static javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA;
import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.jena.util.SplitIRI.localnameXML;
import static org.apache.jena.util.SplitIRI.namespaceXML;
import static org.eclipse.microprofile.config.ConfigProvider.getConfig;
import static org.slf4j.LoggerFactory.getLogger;
import static org.trellisldp.api.Resource.SpecialResources.DELETED_RESOURCE;
import static org.trellisldp.api.Resource.SpecialResources.MISSING_RESOURCE;
import static org.trellisldp.api.TrellisUtils.TRELLIS_DATA_PREFIX;
import static org.trellisldp.api.TrellisUtils.getContainer;
import static org.trellisldp.http.core.HttpConstants.CONFIG_HTTP_BASE_URL;
import static org.trellisldp.http.core.TrellisExtensions.buildExtensionMapFromConfig;
import static org.trellisldp.webdav.Depth.DEPTH.INFINITY;
import static org.trellisldp.webdav.Depth.DEPTH.ONE;
import static org.trellisldp.webdav.Depth.DEPTH.ZERO;
import static org.trellisldp.webdav.impl.WebDAVUtils.closeDataset;
import static org.trellisldp.webdav.impl.WebDAVUtils.copy;
import static org.trellisldp.webdav.impl.WebDAVUtils.depth1Copy;
import static org.trellisldp.webdav.impl.WebDAVUtils.externalUrl;
import static org.trellisldp.webdav.impl.WebDAVUtils.recursiveCopy;
import static org.trellisldp.webdav.impl.WebDAVUtils.recursiveDelete;
import static org.trellisldp.webdav.impl.WebDAVUtils.skolemizeQuads;
import static org.trellisldp.webdav.xml.DavUtils.DAV_NAMESPACE;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.stream.Stream;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.Consumes;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import javax.ws.rs.core.UriInfo;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.rdf.api.Dataset;
import org.apache.commons.rdf.api.IRI;
import org.apache.commons.rdf.api.Literal;
import org.apache.commons.rdf.api.Quad;
import org.apache.commons.rdf.api.RDF;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.metrics.annotation.Timed;
import org.slf4j.Logger;
import org.trellisldp.api.BinaryMetadata;
import org.trellisldp.api.Metadata;
import org.trellisldp.api.RDFFactory;
import org.trellisldp.api.Resource;
import org.trellisldp.api.Session;
import org.trellisldp.http.core.HttpSession;
import org.trellisldp.http.core.ServiceBundler;
import org.trellisldp.http.core.SimpleEvent;
import org.trellisldp.http.core.TrellisRequest;
import org.trellisldp.vocabulary.AS;
import org.trellisldp.vocabulary.LDP;
import org.trellisldp.vocabulary.PROV;
import org.trellisldp.vocabulary.Trellis;
import org.trellisldp.webdav.xml.DavMultiStatus;
import org.trellisldp.webdav.xml.DavProp;
import org.trellisldp.webdav.xml.DavPropFind;
import org.trellisldp.webdav.xml.DavPropStat;
import org.trellisldp.webdav.xml.DavPropertyUpdate;
import org.trellisldp.webdav.xml.DavRemove;
import org.trellisldp.webdav.xml.DavResponse;
import org.trellisldp.webdav.xml.DavSet;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Implements WebDAV HTTP methods.
 */
@ApplicationScoped
@Path("{path: .*}")
public class TrellisWebDAV {

    private static final String SUCCESS = "HTTP/1.1 200 OK";
    private static final int MULTI_STATUS = 207;
    private static final RDF rdf = RDFFactory.getInstance();
    private static final Logger LOGGER = getLogger(TrellisWebDAV.class);

    private final ServiceBundler services;
    private final Map extensions;
    private final String userBaseUrl;

    /**
     * Create a Trellis HTTP resource matcher.
     *
     * @param services the Trellis application bundle
     */
    @Inject
    public TrellisWebDAV(final ServiceBundler services) {
        this(services, getConfig());
    }

    /**
     * For use with RESTeasy and CDI proxies.
     *
     * @apiNote This construtor is used by CDI runtimes that require a public, no-argument constructor.
     *          It should not be invoked directly in user code.
     */
    public TrellisWebDAV() {
        this(null);
    }

    private TrellisWebDAV(final ServiceBundler services, final Config config) {
        this(services, buildExtensionMapFromConfig(config), config.getOptionalValue(CONFIG_HTTP_BASE_URL,
                    String.class).orElse(null));
    }

    /**
     * Create a Trellis HTTP resource matcher.
     *
     * @param services the Trellis application bundle
     * @param extensions the extension graphs
     * @param baseUrl the base URL
     */
    public TrellisWebDAV(final ServiceBundler services, final Map extensions, final String baseUrl) {
        this.extensions = requireNonNull(extensions, "Extensions may not be null!");
        this.services = services;
        this.userBaseUrl = baseUrl;
    }

    /**
     * Copy a resource.
     * @param response the async response
     * @param request the request
     * @param uriInfo the URI info
     * @param headers the headers
     * @param security the security context
     */
    @COPY
    @Timed
    public void copyResource(@Suspended final AsyncResponse response, @Context final Request request,
            @Context final UriInfo uriInfo, @Context final HttpHeaders headers,
            @Context final SecurityContext security) {
        final TrellisRequest req = new TrellisRequest(request, uriInfo, headers, security);
        final Session session = HttpSession.from(security);
        final IRI destination = getDestination(headers, getBaseUrl(req));
        final IRI identifier = rdf.createIRI(TRELLIS_DATA_PREFIX + req.getPath());
        // Default is recursive copy as per RFC-4918
        final Depth.DEPTH depth = getDepth(headers.getHeaderString("Depth"));
        getParent(destination).thenCombine(services.getResourceService().get(destination), this::checkResources)
            .thenCompose(parent -> services.getResourceService().touch(parent.getIdentifier()))
            .thenCompose(future -> services.getResourceService().get(identifier))
            .thenApply(this::checkResource)
            .thenCompose(res -> copyTo(res, session, depth, destination, getBaseUrl(req)))
            .thenApply(future -> status(NO_CONTENT).build())
            .exceptionally(this::handleException).thenApply(response::resume);
    }

    /**
     * Move a resource.
     * @param response the async response
     * @param request the request
     * @param uriInfo the URI info
     * @param headers the headers
     * @param security the security context
     */
    @MOVE
    @Timed
    public void moveResource(@Suspended final AsyncResponse response, @Context final Request request,
            @Context final UriInfo uriInfo, @Context final HttpHeaders headers,
            @Context final SecurityContext security) {
        final TrellisRequest req = new TrellisRequest(request, uriInfo, headers, security);
        final String baseUrl = getBaseUrl(req);
        final IRI identifier = rdf.createIRI(TRELLIS_DATA_PREFIX + req.getPath());
        final IRI destination = getDestination(headers, baseUrl);
        final Session session = HttpSession.from(security);

        getParent(destination)
            .thenCombine(services.getResourceService().get(destination), this::checkResources)
            .thenCompose(parent -> services.getResourceService().touch(parent.getIdentifier()))
            .thenCompose(future -> services.getResourceService().get(identifier))
            .thenApply(this::checkResource)
            // Note: all MOVE operations are recursive (Depth: infinity), hence recursiveCopy
            .thenAccept(res -> recursiveCopy(services, session, res, destination, baseUrl))
            .thenAccept(future -> recursiveDelete(services, session, identifier, baseUrl))
            .thenCompose(future -> services.getResourceService().delete(Metadata.builder(identifier)
                    .interactionModel(LDP.Resource).build()))
            .thenCompose(future -> {
                final Dataset immutable = rdf.createDataset();
                services.getAuditService().creation(identifier, session).stream()
                    .map(skolemizeQuads(services.getResourceService(), baseUrl)).forEachOrdered(immutable::add);
                return services.getResourceService().add(identifier, immutable)
                    .whenComplete((a, b) -> closeDataset(immutable));
            })
            .thenAccept(future -> services.getEventService().emit(new SimpleEvent(externalUrl(identifier,
                baseUrl), session.getAgent(), asList(PROV.Activity, AS.Delete), singletonList(LDP.Resource))))
            .thenApply(future -> status(NO_CONTENT).build())
            .exceptionally(this::handleException).thenApply(response::resume);
    }

    /**
     * Get properties for a resource.
     * @param response the response
     * @param request the request
     * @param uriInfo the URI info
     * @param headers the headers
     * @param propfind the propfind
     * @throws ParserConfigurationException if the XML parser is not properly configured
     */
    @PROPFIND
    @Consumes({APPLICATION_XML})
    @Produces({APPLICATION_XML})
    @Timed
    public void getProperties(@Suspended final AsyncResponse response, @Context final Request request,
            @Context final UriInfo uriInfo, @Context final HttpHeaders headers, final DavPropFind propfind)
            throws ParserConfigurationException {
        final TrellisRequest req = new TrellisRequest(request, uriInfo, headers);
        final IRI identifier = rdf.createIRI(TRELLIS_DATA_PREFIX + req.getPath());
        final String location = fromUri(getBaseUrl(req)).path(req.getPath()).build().toString();
        final Document doc = getDocument();
        services.getResourceService().get(identifier)
            .thenApply(this::checkResource)
            .thenApply(propertiesToMultiStatus(doc, location, propfind))
            .thenApply(multistatus -> status(MULTI_STATUS).entity(multistatus).build())
            .exceptionally(this::handleException).thenApply(response::resume);
    }

    /**
     * Update properties on a resource.
     * @param response the async response
     * @param request the request
     * @param uriInfo the URI info
     * @param headers the headers
     * @param security the security context
     * @param propertyUpdate the property update request
     * @throws ParserConfigurationException if the XML parser is not properly configured
     */
    @PROPPATCH
    @Consumes({APPLICATION_XML})
    @Produces({APPLICATION_XML})
    @Timed
    public void updateProperties(@Suspended final AsyncResponse response,
            @Context final Request request, @Context final UriInfo uriInfo,
            @Context final HttpHeaders headers, @Context final SecurityContext security,
            final DavPropertyUpdate propertyUpdate) throws ParserConfigurationException {

        final Document doc = getDocument();
        final TrellisRequest req = new TrellisRequest(request, uriInfo, headers, security);
        final IRI identifier = rdf.createIRI(TRELLIS_DATA_PREFIX + req.getPath());
        final String baseUrl = getBaseUrl(req);
        final String location = fromUri(baseUrl).path(req.getPath()).build().toString();
        final Session session = HttpSession.from(security);
        services.getResourceService().get(identifier)
            .thenApply(this::checkResource)
            .thenCompose(resourceToMultiStatus(doc, identifier, location, baseUrl, session, propertyUpdate))
            .thenApply(multistatus -> status(MULTI_STATUS).entity(multistatus).build())
            .exceptionally(this::handleException).thenApply(response::resume);
    }

    static Function> elementToQuads(final IRI identifier) {
        return el -> {
            if (el.getNamespaceURI() != null) {
                final Stream.Builder builder = Stream.builder();
                final IRI predicate = rdf.createIRI(el.getNamespaceURI() + el.getLocalName());
                // iterate over child nodes
                Node node = el.getFirstChild();
                while (node != null) {
                    if (Node.TEXT_NODE == node.getNodeType() && !isBlank(node.getNodeValue())) {
                        builder.accept(rdf.createQuad(Trellis.PreferUserManaged, identifier,
                                    predicate, rdf.createLiteral(node.getNodeValue())));
                    } else if (Node.ELEMENT_NODE == node.getNodeType()
                            && node.getNamespaceURI() != null) {
                        builder.accept(rdf.createQuad(Trellis.PreferUserManaged, identifier,
                                    predicate,
                                    rdf.createIRI(node.getNamespaceURI() + node.getLocalName())));
                    }
                    node = node.getNextSibling();
                }
                return builder.build();
            }
            return Stream.empty();
        };
    }

    private CompletionStage getParent(final IRI identifier) {
        final Optional parent = getContainer(identifier);
        if (parent.isPresent()) {
            return services.getResourceService().get(parent.get());
        }
        return completedFuture(MISSING_RESOURCE);
    }

    private IRI getDestination(final HttpHeaders headers, final String baseUrl) {
        final String destination = headers.getHeaderString("Destination");
        if (destination == null) {
            throw new BadRequestException("Missing Destination header");
        } else if (!destination.startsWith(baseUrl)) {
            throw new BadRequestException("Out-of-domain Destination!");
        }
        return services.getResourceService().toInternal(rdf.createIRI(destination), baseUrl);
    }

    private Resource checkResources(final Resource from, final Resource to) {
        if (MISSING_RESOURCE.equals(from)) {
            throw new NotFoundException();
        } else if (DELETED_RESOURCE.equals(from)) {
            throw new ClientErrorException(GONE);
        } else if (exists(to)) {
            throw new ClientErrorException(CONFLICT);
        }
        return from;
    }

    private Resource checkResource(final Resource res) {
        if (MISSING_RESOURCE.equals(res)) {
            throw new NotFoundException();
        } else if (DELETED_RESOURCE.equals(res)) {
            throw new ClientErrorException(GONE);
        }
        return res;
    }

    private Response handleException(final Throwable err) {
        final Throwable cause = err.getCause();
        if (cause instanceof WebApplicationException) return ((WebApplicationException) cause).getResponse();
        LOGGER.error("Trellis Error:", err);
        return new WebApplicationException(err).getResponse();
    }

    private Function> resourceToMultiStatus(final Document doc,
            final IRI identifier, final String location, final String baseUrl, final Session session,
            final DavPropertyUpdate propertyUpdate) {
        return resource -> {
            final Dataset dataset = rdf.createDataset();
            final Set removeProperties = getRemoveProperties(propertyUpdate);
            final DavMultiStatus multistatus = new DavMultiStatus();
            final DavResponse response = new DavResponse();
            final Set modifiedProperties = new HashSet<>();
            response.setHref(location);
            // Keep any extension data
            try (final Stream stream = resource.stream(extensions.values())) {
                stream.forEach(dataset::add);
            }
            // Filter out any removable properties
            try (final Stream stream = resource.stream(Trellis.PreferUserManaged)) {
                stream.forEach(quad -> {
                    if (removeProperties.contains(quad.getPredicate())) {
                        modifiedProperties.add(quad.getPredicate());
                    } else {
                        dataset.add(rdf.createQuad(Trellis.PreferUserManaged, identifier, quad.getPredicate(),
                                    quad.getObject()));
                    }
                });
            }

            final DavSet set = propertyUpdate.getSet();
            if (set != null) {
                final DavProp prop = set.getProp();
                if (prop != null) {
                    Optional.ofNullable(prop.getNodes()).ifPresent(nodes -> nodes.stream()
                            .flatMap(elementToQuads(identifier)).forEach(quad -> {
                                modifiedProperties.add(quad.getPredicate());
                                dataset.add(quad);
                            }));
                }
            }

            response.setPropStats(modifiedProperties.stream().map(predicate -> {
                final DavPropStat stat = new DavPropStat();
                final DavProp prop = new DavProp();
                prop.setNodes(singletonList(doc.createElementNS(namespaceXML(predicate.getIRIString()),
                                localnameXML(predicate.getIRIString()))));
                stat.setProp(prop);
                stat.setStatus(SUCCESS);
                return stat;
            }).collect(toList()));
            multistatus.setDescription("Response to property update request");
            multistatus.setResponses(singletonList(response));

            final Dataset immutable = rdf.createDataset();
            services.getAuditService().creation(resource.getIdentifier(), session).stream()
                .map(skolemizeQuads(services.getResourceService(), baseUrl)).forEachOrdered(immutable::add);

            return services.getResourceService()
                .replace(Metadata.builder(resource).build(), dataset)
                .whenComplete((a, b) -> closeDataset(dataset))
                .thenCompose(future ->
                        services.getResourceService().add(resource.getIdentifier(), immutable))
                .whenComplete((a, b) -> closeDataset(immutable))
                .thenCompose(future ->
                        services.getMementoService().put(services.getResourceService(), resource.getIdentifier()))
                .thenAccept(future -> services.getEventService().emit(new SimpleEvent(location, session.getAgent(),
                            asList(PROV.Activity, AS.Update), singletonList(resource.getInteractionModel()))))
                .thenApply(future -> multistatus);
        };
    }

    static Document getDocument() throws ParserConfigurationException {
        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setIgnoringComments(true);
        factory.setIgnoringElementContentWhitespace(true);
        factory.setExpandEntityReferences(false);
        factory.setFeature(FEATURE_SECURE_PROCESSING, Boolean.TRUE);
        factory.setAttribute(ACCESS_EXTERNAL_DTD, "");
        factory.setAttribute(ACCESS_EXTERNAL_SCHEMA, "");
        return factory.newDocumentBuilder().newDocument();
    }

    private Function propertiesToMultiStatus(final Document doc,
            final String baseUrl, final DavPropFind propfind) {

        final Set properties = getProperties(propfind);
        final boolean allprops = propfind.getAllProp() != null;
        final boolean propname = propfind.getPropName() != null;

        return res -> {
            final DavProp prop = new DavProp();
            prop.setNodes(getPropertyElements(doc, res, properties, allprops, propname));

            final DavPropStat propstat = new DavPropStat();
            propstat.setProp(prop);
            propstat.setStatus(SUCCESS);

            final DavResponse response = new DavResponse();
            response.setHref(externalUrl(res.getIdentifier(), baseUrl));
            response.setDescription("PROPFIND request for " + externalUrl(res.getIdentifier(), baseUrl));
            response.setPropStats(singletonList(propstat));

            final List responses = new ArrayList<>();
            responses.add(response);

            try (final Stream children = res.stream(LDP.PreferContainment)) {
                children.map(Quad::getObject).filter(IRI.class::isInstance).map(IRI.class::cast)
                    .parallel().map(id -> services.getResourceService().get(id).thenApply(r -> {
                        final DavProp childProp = new DavProp();
                        childProp.setNodes(getPropertyElements(doc, r, properties, allprops, propname));

                        final DavPropStat childPropstat = new DavPropStat();
                        childPropstat.setProp(childProp);
                        childPropstat.setStatus(SUCCESS);

                        final DavResponse childResponse = new DavResponse();
                        childResponse.setHref(externalUrl(r.getIdentifier(), baseUrl));
                        childResponse.setDescription("PROPFIND request for " + externalUrl(r.getIdentifier(), baseUrl));
                        childResponse.setPropStats(singletonList(childPropstat));

                        return childResponse;
                    })).map(CompletionStage::toCompletableFuture).map(CompletableFuture::join).forEach(responses::add);
            }

            final DavMultiStatus multistatus = new DavMultiStatus();
            multistatus.setResponses(responses);
            multistatus.setDescription("Response to property find request");
            return multistatus;
        };
    }

    private CompletionStage copyTo(final Resource resource, final Session session, final Depth.DEPTH depth,
            final IRI destination, final String baseUrl) {
        if (ZERO.equals(depth)) {
            return copy(services, session, resource, destination, baseUrl);
        } else if (ONE.equals(depth)) {
            return depth1Copy(services, session, resource, destination, baseUrl);
        }
        return recursiveCopy(services, session, resource, destination, baseUrl);
    }

    private String getBaseUrl(final TrellisRequest req) {
        if (userBaseUrl != null) {
            return userBaseUrl;
        }
        return req.getBaseUrl();
    }

    static Set getProperties(final DavPropFind propfind) {
        final DavProp prop = propfind.getProp();
        if (prop == null) return emptySet();
        final List nodes = prop.getNodes();
        if (nodes == null) return emptySet();
        return nodes.stream().filter(el -> el.getNamespaceURI() != null)
                        .map(el -> rdf.createIRI(el.getNamespaceURI() + el.getLocalName())).collect(toSet());
    }

    static Set getRemoveProperties(final DavPropertyUpdate propertyUpdate) {
        final DavRemove remove = propertyUpdate.getRemove();
        final DavSet set = propertyUpdate.getSet();
        final Set props = new HashSet<>();
        if (remove != null) {
             props.addAll(getProperties(remove.getProp()));
        }
        // Note: also clear any set properties before adding them back
        if (set != null) {
             props.addAll(getProperties(set.getProp()));
        }
        return props;
    }

    static Set getProperties(final DavProp prop) {
        if (prop != null) {
            return prop.getNodes().stream().filter(el -> el.getNamespaceURI() != null)
                .map(el -> rdf.createIRI(el.getNamespaceURI() + el.getLocalName())).collect(toSet());
        }
        return emptySet();
    }

    static Element getContentTypeElement(final Document doc, final Resource res, final boolean propname) {
        final Element el = doc.createElementNS(DAV_NAMESPACE, "getcontenttype");
        if (!propname) {
            el.setTextContent(res.getBinaryMetadata().flatMap(BinaryMetadata::getMimeType)
                .orElse("text/turtle"));
        }
        return el;
    }

    static Element getLastModifiedElement(final Document doc, final Resource res, final boolean propname) {
        final Element el = doc.createElementNS(DAV_NAMESPACE, "getlastmodified");
        if (!propname) {
            el.setTextContent(ofInstant(res.getModified().truncatedTo(SECONDS), UTC).format(RFC_1123_DATE_TIME));
        }
        return el;
    }

    static Optional getResourceTypeElement(final Document doc, final Resource res,
            final boolean propname) {
        if (!propname && res.getInteractionModel().getIRIString().endsWith("Container")) {
            final Element el = doc.createElementNS(DAV_NAMESPACE, "resourcetype");
            el.appendChild(doc.createElementNS(DAV_NAMESPACE, "collection"));
            return Optional.of(el);
        }
        return Optional.empty();
    }

    static Function quadToElement(final Document doc, final boolean propname) {
        return quad -> {
            if (quad.getObject() instanceof Literal) {
                final Element el = doc.createElementNS(namespaceXML(quad.getPredicate().getIRIString()),
                            localnameXML(quad.getPredicate().getIRIString()));
                if (!propname) {
                    el.setTextContent(((Literal) quad.getObject()).getLexicalForm());
                }
                return el;
            } else if (quad.getObject() instanceof IRI) {
                final Element el = doc.createElementNS(namespaceXML(quad.getPredicate().getIRIString()),
                            localnameXML(quad.getPredicate().getIRIString()));
                if (!propname) {
                    el.setTextContent(((IRI) quad.getObject()).getIRIString());
                }
                return el;
            }
            return null;
        };
    }

    static List getPropertyElements(final Document doc, final Resource res,
            final Set properties, final boolean allproperties, final boolean propname) {

        final List allProperties = new ArrayList<>();
        getResourceTypeElement(doc, res, propname).ifPresent(allProperties::add);
        allProperties.add(getContentTypeElement(doc, res, propname));
        allProperties.add(getLastModifiedElement(doc, res, propname));

        try (final Stream stream = res.stream(Trellis.PreferUserManaged)) {
            final List elements = stream
                .filter(t -> allproperties || properties.contains(t.getPredicate()))
                .map(quadToElement(doc, propname)).filter(Objects::nonNull).collect(toList());
            allProperties.addAll(elements);
        }
        return allProperties;
    }

    static boolean exists(final Resource res) {
        return !MISSING_RESOURCE.equals(res) && !DELETED_RESOURCE.equals(res);
    }

    static Depth.DEPTH getDepth(final String depth) {
        if (depth != null) {
            return new Depth(depth).getDepth();
        }
        return INFINITY;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy