001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.commons.rdf.jsonldjava.experimental;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.net.MalformedURLException;
023import java.net.URI;
024import java.net.URISyntaxException;
025import java.net.URL;
026import java.nio.file.Files;
027import java.util.function.Predicate;
028
029import org.apache.commons.rdf.api.Dataset;
030import org.apache.commons.rdf.api.Graph;
031import org.apache.commons.rdf.api.IRI;
032import org.apache.commons.rdf.api.RDFSyntax;
033import org.apache.commons.rdf.jsonldjava.JsonLdDataset;
034import org.apache.commons.rdf.jsonldjava.JsonLdGraph;
035import org.apache.commons.rdf.jsonldjava.JsonLdRDF;
036import org.apache.commons.rdf.simple.experimental.AbstractRDFParser;
037
038import com.github.jsonldjava.core.JsonLdError;
039import com.github.jsonldjava.core.JsonLdOptions;
040import com.github.jsonldjava.core.JsonLdProcessor;
041import com.github.jsonldjava.core.RDFDataset;
042import com.github.jsonldjava.utils.JsonUtils;
043
044public class JsonLdParser extends AbstractRDFParser<JsonLdParser> {
045
046    @Override
047    protected JsonLdRDF createRDFTermFactory() {
048        return new JsonLdRDF();
049    }
050
051    @Override
052    public JsonLdParser contentType(final RDFSyntax rdfSyntax) throws IllegalArgumentException {
053        if (rdfSyntax != null && rdfSyntax != RDFSyntax.JSONLD) {
054            throw new IllegalArgumentException("Unsupported contentType: " + rdfSyntax);
055        }
056        return super.contentType(rdfSyntax);
057    }
058
059    @Override
060    public JsonLdParser contentType(final String contentType) throws IllegalArgumentException {
061        final JsonLdParser c = super.contentType(contentType);
062        if (c.getContentType().filter(Predicate.isEqual(RDFSyntax.JSONLD).negate()).isPresent()) {
063            throw new IllegalArgumentException("Unsupported contentType: " + contentType);
064        }
065        return c;
066    }
067
068    private static URL asURL(final IRI iri) throws IllegalStateException {
069        try {
070            return new URI(iri.getIRIString()).toURL();
071        } catch (MalformedURLException | URISyntaxException e) {
072            throw new IllegalStateException("Invalid URL: " + iri.getIRIString());
073        }
074    }
075
076    @Override
077    protected void checkSource() throws IOException {
078        super.checkSource();
079        // Might throw IllegalStateException if invalid
080        getSourceIri().map(JsonLdParser::asURL);
081    }
082
083    @Override
084    protected void parseSynchronusly() throws IOException {
085        final Object json = readSource();
086        final JsonLdOptions options = new JsonLdOptions();
087        getBase().map(IRI::getIRIString).ifPresent(options::setBase);
088        // TODO: base from readSource() (after redirection and Content-Location
089        // header)
090        // should be forwarded
091
092        // TODO: Modify JsonLdProcessor to accept the target RDFDataset
093        RDFDataset rdfDataset;
094        try {
095            rdfDataset = (RDFDataset) JsonLdProcessor.toRDF(json, options);
096        } catch (final JsonLdError e) {
097            throw new IOException("Could not parse Json-LD", e);
098        }
099        if (getTargetGraph().isPresent()) {
100            final Graph intoGraph = getTargetGraph().get();
101            if (intoGraph instanceof JsonLdGraph && !intoGraph.contains(null, null, null)) {
102                // Empty graph, we can just move over the map content directly:
103                final JsonLdGraph jsonLdGraph = (JsonLdGraph) intoGraph;
104                jsonLdGraph.getRdfDataSet().putAll(rdfDataset);
105                return;
106                // otherwise we have to merge as normal
107            }
108            // TODO: Modify JsonLdProcessor to have an actual triple callback
109            final Graph parsedGraph = getJsonLdFactory().asGraph(rdfDataset);
110            // sequential() as we don't know if destination is thread safe :-/
111            parsedGraph.stream().sequential().forEach(intoGraph::add);
112        } else if (getTargetDataset().isPresent()) {
113            final Dataset intoDataset = getTargetDataset().get();
114            if (intoDataset instanceof JsonLdDataset && !intoDataset.contains(null, null, null, null)) {
115                final JsonLdDataset jsonLdDataset = (JsonLdDataset) intoDataset;
116                // Empty - we can just do a brave replace!
117                jsonLdDataset.getRdfDataSet().putAll(rdfDataset);
118                return;
119                // otherwise we have to merge.. but also avoid duplicate
120                // triples,
121                // map blank nodes etc, so we'll fall back to normal Dataset
122                // appending.
123            }
124            final Dataset fromDataset = getJsonLdFactory().asDataset(rdfDataset);
125            // .sequential() as we don't know if destination is thread-safe :-/
126            fromDataset.stream().sequential().forEach(intoDataset::add);
127        } else {
128            final Dataset fromDataset = getJsonLdFactory().asDataset(rdfDataset);
129            // No need for .sequential() here
130            fromDataset.stream().forEach(getTarget());
131        }
132    }
133
134    private JsonLdRDF getJsonLdFactory() {
135        if (getRdfTermFactory().isPresent() && getRdfTermFactory().get() instanceof JsonLdRDF) {
136            return (JsonLdRDF) getRdfTermFactory().get();
137        }
138        return createRDFTermFactory();
139    }
140
141    private Object readSource() throws IOException {
142        // Due to checked IOException we can't easily
143        // do this with .map and .orElseGet()
144
145        if (getSourceInputStream().isPresent()) {
146            return JsonUtils.fromInputStream(getSourceInputStream().get());
147        }
148        if (getSourceIri().isPresent()) {
149            // TODO: propagate @base from content
150            return JsonUtils.fromURL(asURL(getSourceIri().get()), JsonUtils.getDefaultHttpClient());
151        }
152        if (getSourceFile().isPresent()) {
153            try (InputStream inputStream = Files.newInputStream(getSourceFile().get())) {
154                return JsonUtils.fromInputStream(inputStream);
155            }
156        }
157        throw new IllegalStateException("No known source found");
158    }
159
160}