001/*
002 * Licensed to DuraSpace under one or more contributor license agreements.
003 * See the NOTICE file distributed with this work for additional information
004 * regarding copyright ownership.
005 *
006 * DuraSpace licenses this file to you under the Apache License,
007 * Version 2.0 (the "License"); you may not use this file except in
008 * compliance 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.fcrepo.kernel.api.utils;
019
020import static org.apache.jena.rdf.model.ResourceFactory.createProperty;
021import static org.apache.jena.vocabulary.RDF.Init.type;
022import static org.fcrepo.kernel.api.RdfLexicon.CREATED_BY;
023import static org.fcrepo.kernel.api.RdfLexicon.CREATED_DATE;
024import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_BY;
025import static org.fcrepo.kernel.api.RdfLexicon.LAST_MODIFIED_DATE;
026import static org.fcrepo.kernel.api.RdfLexicon.isManagedPredicate;
027import static org.fcrepo.kernel.api.RdfLexicon.isRelaxablePredicate;
028import static org.fcrepo.kernel.api.RdfLexicon.restrictedType;
029
030import java.util.Calendar;
031
032import org.fcrepo.kernel.api.exception.MalformedRdfException;
033import org.fcrepo.kernel.api.exception.RelaxableServerManagedPropertyException;
034import org.fcrepo.kernel.api.exception.ServerManagedPropertyException;
035import org.fcrepo.kernel.api.exception.ServerManagedTypeException;
036
037import org.apache.jena.datatypes.xsd.XSDDateTime;
038import org.apache.jena.graph.Triple;
039import org.apache.jena.rdf.model.Property;
040import org.apache.jena.rdf.model.RDFNode;
041import org.apache.jena.rdf.model.Resource;
042import org.apache.jena.rdf.model.Statement;
043
044/**
045 * Some server managed triples can have the prohibition on user-management overridden.  While
046 * the server still updates them implicitly, it may be possible in some cases for a user
047 * request to override them.
048 *
049 * @author Mike Durbin
050 * @author whikloj
051 */
052public class RelaxedPropertiesHelper {
053
054    /**
055     * Gets the created date (if any) that was included in the statements.
056     * @param resource the resource we are looking for properties of
057     * @return the date that should be set for the CREATED_DATE or null if it should be
058     *         untouched
059     */
060    public static Calendar getCreatedDate(final Resource resource) {
061        final Iterable<Statement> iter = getIterable(resource, CREATED_DATE);
062        return extractSingleCalendarValue(iter, CREATED_DATE);
063    }
064
065    /**
066     * Gets the created by user (if any) that is included within the statements.
067     * @param resource the resource we are looking for properties of
068     * @return the string that should be set for the CREATED_BY or null if it should be
069     *         untouched
070     */
071    public static String getCreatedBy(final Resource resource) {
072        final Iterable<Statement> iter = getIterable(resource, CREATED_BY);
073        return extractSingleStringValue(iter, CREATED_BY);
074    }
075
076    /**
077     * Gets the modified date (if any) that was included within the statements.
078     * @param resource the resource we are looking for properties of
079     * @return the date that should be set for the LAST_MODIFIED_DATE or null if it should be
080     *         untouched
081     */
082    public static Calendar getModifiedDate(final Resource resource) {
083        final Iterable<Statement> iter = getIterable(resource, LAST_MODIFIED_DATE);
084        return extractSingleCalendarValue(iter, LAST_MODIFIED_DATE);
085    }
086
087    /**
088     * Gets the modified by user (if any) that was included within the statements.
089     * @param resource the resource we are looking for properties of
090     * @return the string that should be set for the MODIFIED_BY or null if it should be
091     *         untouched
092     */
093    public static String getModifiedBy(final Resource resource) {
094        final Iterable<Statement> iter = getIterable(resource, LAST_MODIFIED_BY);
095        return extractSingleStringValue(iter, LAST_MODIFIED_BY);
096    }
097
098    private static String extractSingleStringValue(final Iterable<Statement> statements,
099                                                   final Property predicate) {
100        String textValue = null;
101        for (final Statement added : statements) {
102            if (textValue == null) {
103                textValue = added.getObject().asLiteral().getString();
104            } else {
105                throw new MalformedRdfException(predicate + " may only appear once!");
106            }
107        }
108        return textValue;
109    }
110
111    private static Calendar extractSingleCalendarValue(final Iterable<Statement> statements, final Property predicate) {
112        Calendar cal = null;
113        for (final Statement added : statements) {
114            if (cal == null) {
115                try {
116                    cal = RelaxedPropertiesHelper.parseExpectedXsdDateTimeValue(added.getObject());
117                } catch (final IllegalArgumentException e) {
118                    throw new MalformedRdfException("Expected a xsd:datetime for " + predicate, e);
119                }
120            } else {
121                throw new MalformedRdfException(predicate + " may only appear once!");
122            }
123        }
124        return cal;
125    }
126
127    /**
128     * Parses an RDFNode that is expected to be a literal of type xsd:dateTime into a Java Calendar
129     * object.
130     * @param node a node representing an xsd:dateTime literal
131     * @return a Calendar representation of the expressed dateTime
132     */
133    private static Calendar parseExpectedXsdDateTimeValue(final RDFNode node) {
134        final Object value = node.asLiteral().getValue();
135        if (value instanceof XSDDateTime) {
136            return ((XSDDateTime) value).asCalendar();
137        } else {
138            throw new IllegalArgumentException("Expected an xsd:dateTime!");
139        }
140    }
141
142    private static Iterable<Statement> getIterable(final Resource resource, final Property predicate) {
143        final var iterator = resource.listProperties(predicate);
144        return () -> iterator;
145    }
146
147    /**
148     * Several tests for invalid or disallowed RDF statements.
149     * @param triple the triple to check.
150     */
151    public static void checkTripleForDisallowed(final Triple triple) {
152        if (triple.getPredicate().equals(type().asNode()) && !triple.getObject().isVariable() &&
153                !triple.getObject().isURI()) {
154            // The object of a rdf:type triple is not a variable and not a URI.
155            throw new MalformedRdfException(
156                    String.format("Invalid rdf:type: %s", triple.getObject()));
157        } else if (restrictedType.test(triple)) {
158            // The object of a rdf:type triple has a restricted namespace.
159            throw new ServerManagedTypeException(
160                    String.format("The server managed type (%s) cannot be modified by the client.",
161                            triple.getObject()));
162        } else if (isManagedPredicate.test(createProperty(triple.getPredicate().getURI()))) {
163            // The predicate is server managed.
164            final var message = String.format("The server managed predicate (%s) cannot be modified by the client.",
165                    triple.getPredicate());
166            if (isRelaxablePredicate.test(createProperty(triple.getPredicate().getURI()))) {
167                // It is a relaxable predicate so throw the appropriate exception.
168                throw new RelaxableServerManagedPropertyException(message);
169            }
170            throw new ServerManagedPropertyException(message);
171        }
172    }
173
174    // Prevent instantiation
175    private RelaxedPropertiesHelper() {
176
177    }
178}