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

io.telicent.jena.abac.fuseki.LabelledDataLoader Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright (c) Telicent Ltd.
 *
 *  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 io.telicent.jena.abac.fuseki;

import static java.lang.String.format;

import java.io.InputStream;
import java.util.List;
import java.util.function.BiConsumer;

import io.telicent.jena.abac.AE;
import io.telicent.jena.abac.SysABAC;
import io.telicent.jena.abac.attributes.AttributeExpr;
import io.telicent.jena.abac.core.DatasetGraphABAC;
import io.telicent.jena.abac.core.StreamSplitter;
import io.telicent.jena.abac.labels.LabelsStore;
import org.apache.jena.atlas.logging.FmtLog;
import org.apache.jena.fuseki.servlets.ActionErrorException;
import org.apache.jena.fuseki.servlets.ActionLib;
import org.apache.jena.fuseki.servlets.HttpAction;
import org.apache.jena.fuseki.servlets.ServletOps;
import org.apache.jena.fuseki.system.DataUploader;
import org.apache.jena.fuseki.system.UploadDetails;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Triple;
import org.apache.jena.query.TxnType;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFLanguages;
import org.apache.jena.riot.RiotException;
import org.apache.jena.riot.lang.StreamRDFCounting;
import org.apache.jena.riot.system.StreamRDF;
import org.apache.jena.riot.system.StreamRDFLib;
import org.apache.jena.riot.system.StreamRDFWrapper;
import org.apache.jena.shared.JenaException;
import org.apache.jena.sparql.core.DatasetGraph;
import org.apache.jena.sparql.core.Quad;
import org.apache.jena.sparql.graph.GraphFactory;
import org.apache.jena.web.HttpSC;
import org.slf4j.Logger;

/**
 * The process of loading data with labels.
 */
class LabelledDataLoader {

    // Restructure by splitting up the code
    //   Use with FKProcessorSCG

    private record LoaderRequest(String id, Logger log, DatasetGraphABAC dsgz, InputStream data, String contentTypeStr, String headerLabels) {}

    private record LoaderResponse(long tripleCount, long quadCount, long count, long contentLength, String contentType, Lang lang, String base) {
        public String str() {
            return String.format("Content-Length=%d, Content-Type=%s => %s : Count=%d Triples=%d Quads=%d",
                                 contentLength, contentType,
                                 lang.getName(), count, tripleCount, quadCount);
        }
    }

    /*package*/ static void validate(HttpAction action) {
        DatasetGraph dsg = action.getDataset();
        if ( !(dsg instanceof DatasetGraphABAC) ) {
            // Should have been caught in validate.
            FmtLog.error(action.log, "[%d] This dataset does not support ABAC security labelling.", action.id);
            ServletOps.error(HttpSC.BAD_REQUEST_400, "This dataset does not support ABAC security labelling.");
            return;
        }
    }

    // Current HTTP/ABAC_DataLoader codepath.
    // Called by ABAC_DataLoader.execute()
    /*package*/ static void execute(HttpAction action) {
        DatasetGraph dsg = action.getDataset();
        DatasetGraphABAC dsgz = null;
        if ( dsg instanceof DatasetGraphABAC x )
            dsgz = x;
        if ( dsgz == null ) {
            // Should have been caught in validate.
            FmtLog.error(action.log, "[%d] This dataset does not support ABAC security labelling.", action.id);
            ServletOps.error(HttpSC.BAD_REQUEST_400, "This dataset does not support ABAC security labelling.");
            return;
        }
        action.begin(TxnType.WRITE);
        try {
            // long len = action.getRequestContentLengthLong();

            String hSecurityLabel = action.getRequestHeader(SysABAC.hSecurityLabel);
            List headerSecurityLabels = parseAttributeList(hSecurityLabel);
            String dsgDftLabels = dsgz.getDefaultLabel();

            if ( headerSecurityLabels != null )
                FmtLog.info(action.log, "[%d] Security-Label %s", action.id, headerSecurityLabels);
            else
                // Dataset default will apply at use time.
                FmtLog.info(action.log, "[%d] Dataset default label: %s", action.id, dsgDftLabels);

            UploadInfo x = ingestData(action, dsgz, headerSecurityLabels, dsgDftLabels);
            action.log.info(format("[%d] Body: %s", action.id, x.str()));
            action.commit();
            ServletOps.success(action);
            // ServletOps.uploadResponse(action, details);
        } catch (ActionErrorException ex) {
            action.abortSilent();
            throw ex;
        } catch (Throwable ex) {
            action.abortSilent();
            ServletOps.errorOccurred(ex);
            return;
        }
    }

    private static List parseAttributeList(String securityLabelsList) {
        if ( securityLabelsList == null )
            return null;
        List x = AE.parseExprList(securityLabelsList);
        return AE.asStrings(x);
    }

    private static void applyLabels(DatasetGraphABAC dsgz, Graph labelsGraph) {
        if ( labelsGraph != null && !labelsGraph.isEmpty() )
            dsgz.labelsStore().addGraph(labelsGraph);
    }

    record UploadInfo(long tripleCount, long quadCount, long count, String contentType, long contentLength, Lang lang, String base) {
        public String str() {
            return String.format("Content-Length=%d, Content-Type=%s => %s : Count=%d Triples=%d Quads=%d",
                                 contentLength, contentType, lang,
                                 count, tripleCount, quadCount);
        }
    }

    // ---- Ingestion processing.

    // ==> where?
    /**
     * Ingest labelled data.
     * 

* If it is triples the labels are the ones in the header and can't be in the * data body. *

* If it is quads, the data is the default graph and the labels from the header * apply but are overridden by the {@code } * graph. */ /*package*/ static UploadInfo ingestData(HttpAction action, DatasetGraphABAC dsgz, List headerLabels, String dsgDftLabel) { String base = ActionLib.wholeRequestURL(action.getRequest()); return ingestData(action, base, dsgz, headerLabels, dsgDftLabel); } /*package*/ static UploadInfo ingestData(HttpAction action, String base, DatasetGraphABAC dsgz, List headerLabels, String dsgDftLabel) { try { // Decide the label to apply when the data does not explicitly set the // labels on a triple. List labelsForData = headerLabels; // Decide default labelling. // No storing of labels if the dataset default is enough. if ( headerLabels != null && dsgDftLabel != null ) { if ( headerLabels.equals(List.of(dsgDftLabel)) ) { // Save space in the label store. // Don't store when the dataset default will apply. // This is advantageous when there is one label for // most of the data, and maybe some exceptions explicit // labelled differently. It relies on the default not // changing though. labelsForData = List.of(); } } Lang lang = RDFLanguages.contentTypeToLang(action.getRequestContentType()); if ( RDFLanguages.isTriples(lang) ) { // Triples. We can stream process the data because we know the label // to apply ahead of parsing. return ingestTriples(action, lang, base, dsgz, labelsForData); } else { // Quads. (Currently assumed to be the labels graph). This has to be // buffered. return ingestQuads(action, lang, base, dsgz, labelsForData); } } catch (RiotException ex) { ex.printStackTrace(); } catch (JenaException ex) { ex.printStackTrace(); } catch (Throwable ex) { ex.printStackTrace(); } return null; } private static UploadInfo ingestTriples(HttpAction action, Lang lang, String base, DatasetGraphABAC dsgz, List headerLabels) { StreamRDF baseDest = StreamRDFLib.dataset(dsgz.getData()); LabelsStore labelsStore = dsgz.labelsStore(); BiConsumer> labelledTriplesCollector = (triple, listLabels) -> labelsStore.add(triple, listLabels); StreamRDF dest = baseDest; if ( headerLabels != null ) { // If there are no header labels, nothing to do - send to base dest = new StreamLabeller(baseDest, headerLabels, labelledTriplesCollector); } StreamRDFCounting countingDest = StreamRDFLib.count(dest); ActionLib.parse(action, countingDest, lang, base); return new UploadInfo(countingDest.countTriples(), countingDest.countQuads(), countingDest.count(), action.getRequestContentType(), action.getRequestContentLengthLong(), lang, base); } private static class StreamLabeller extends StreamRDFWrapper { private final List labels; private BiConsumer> labelsHandler; StreamLabeller(StreamRDF destination, List labels, BiConsumer> labelsHandler) { super(destination); this.labels = labels; this.labelsHandler = labelsHandler; } @Override public void triple(Triple triple) { super.triple(triple); if ( labels != null ) labelsHandler.accept(triple, labels); } @Override public void quad(Quad quad) { throw new UnsupportedOperationException("StreamLabeller.quad"); } } private static UploadInfo ingestQuads(HttpAction action, Lang lang, String base, DatasetGraphABAC dsgz, List labelsForData) { // We could split the bulk data from the modifications using the fact we are // inside a transaction on the dataset. The transaction means we are // proceeding optimistically adding to the dataset by streaming to data // store, then add labels to the labels store, then commit, making the new // triples available in the dataset. StreamRDF rdfData = StreamRDFLib.dataset(dsgz.getData()); // Get all the labels - as they may come before or after the data, // we need to collect them together, then process them before the txn commit. Graph labelsGraph = GraphFactory.createDefaultGraph(); StreamRDF stream = new StreamSplitter(rdfData, labelsGraph, labelsForData); // Contains: String base = ActionLib.wholeRequestURL(action.getRequest()); UploadDetails details = DataUploader.incomingData(action, stream); applyLabels(dsgz, labelsGraph); // UploadDetails is a Fuseki class and has limited accessibility. Convert. return new UploadInfo(details.getTripleCount(), details.getQuadCount(), details.getCount(), action.getRequestContentType(), action.getRequestContentLengthLong(), lang, base); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy