org.eclipse.lyo.testsuite.oslcv2.AbstractCreationAndUpdateRdfTests.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.lyo.testsuite.oslcv2.AbstractCreationAndUpdateRdfTests.java

Source

/*******************************************************************************
 * Copyright (c) 2013, 2014 IBM Corporation.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution. 
 *
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *
 *    Samuel Padgett - create and update resources using shapes
 *    Samuel Padgett - don't cache query shapes for creation when the URIs are the same
 *    Samuel Padgett - handle oslc:AllowedValues blank nodes
 *******************************************************************************/
package org.eclipse.lyo.testsuite.oslcv2;

import static org.junit.Assert.*;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.eclipse.lyo.testsuite.server.util.OSLCConstants;
import org.eclipse.lyo.testsuite.server.util.OSLCUtils;
import org.eclipse.lyo.testsuite.server.util.RDFUtils;

import com.hp.hpl.jena.datatypes.xsd.impl.XMLLiteralType;
import com.hp.hpl.jena.rdf.model.Literal;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.vocabulary.RDF;

/**
 * Common class for testing creation and update using RDF media types.
 */
public abstract class AbstractCreationAndUpdateRdfTests extends CreationAndUpdateBaseTests {

    private static Logger logger = Logger.getLogger(AbstractCreationAndUpdateRdfTests.class);

    public AbstractCreationAndUpdateRdfTests(String url) {
        super(url);
    }

    public String createResourceFromShape(String shapeUri) throws IOException {
        logger.debug(String.format("Creating resource from shape <%s>", shapeUri));
        Model m = ModelFactory.createDefaultModel();
        createResourceFromShape(m, shapeUri, 1);

        return toString(m);
    }

    // For debugging.
    private String asTurtle(Model m) {
        StringWriter writer = new StringWriter();
        m.write(writer, "TURTLE");

        return writer.toString();
    }

    // Max depth is used to detect cycles.
    private final static int MAX_DEPTH = Integer
            .parseInt(System.getProperty("org.eclipse.lyo.testsuite.oslcv2.createResource.maxDepth", "10"));

    protected Resource createResourceFromShape(Model requestModel, String shapeUri, int depth) throws IOException {
        assertTrue("Detected possible circular reference in shape while creating resource.", depth < MAX_DEPTH);

        // Get the shape.
        Model shapeModel = getModel(shapeUri);
        if (logger.isDebugEnabled()) {
            logger.debug(asTurtle(shapeModel));
        }

        Resource toCreate = requestModel.createResource();

        Resource shapeResource = shapeModel.getResource(shapeUri);
        StmtIterator typeIter = shapeResource.listProperties(shapeModel.createProperty(OSLCConstants.DESCRIBES));

        // Use the first rdf:type if defined.
        if (typeIter.hasNext()) {
            Resource type = typeIter.next().getResource();
            toCreate.addProperty(RDF.type, type);
        }

        final Property propertyProp = shapeModel.createProperty(OSLCConstants.PROPERTY);

        // Try to create a resource based on the properties in the shape.
        StmtIterator propIter = shapeResource.listProperties(propertyProp);
        while (propIter.hasNext()) {
            Resource nextProperty = propIter.next().getResource();

            // Only try to fill in required properties to minimize the chance of errors.
            if (isPropertyRequired(nextProperty)) {
                fillInProperty(shapeModel, toCreate, nextProperty, depth);
            }
        }

        return toCreate;
    }

    private void fillInProperty(Model shapeModel, Resource toCreate, Resource propertyToFill, int depth)
            throws IOException {
        final Property propertyDefinitionProp = shapeModel.createProperty(OSLCConstants.PROPERTY_DEFINITION);
        final Property allowedValueProp = shapeModel.createProperty(OSLCConstants.ALLOWED_VALUE);
        final Property allowedValuesProp = shapeModel.createProperty(OSLCConstants.ALLOWED_VALUES);

        String propertyDefinition = propertyToFill.getRequiredProperty(propertyDefinitionProp).getResource()
                .getURI();
        Property requestProp = toCreate.getModel().createProperty(propertyDefinition);

        /*
         * Don't attempt to use the default value. Some providers make the
         * default value something that is not allowed (for instance, Filed
         * Against: Unassigned in RTC).
         */

        //      final Property defaultValueProp = shapeModel.createProperty(OSLCConstants.DEFAULT_VALUE);
        //      if (propertyToFill.hasProperty(defaultValueProp)) {
        //         RDFNode defaultValue = propertyToFill.getProperty(defaultValueProp).getObject();
        //         // Make sure it's not just the empty string.
        //         if (defaultValue.isResource() || !"".equals(defaultValue.asLiteral().getLexicalForm())) {
        //            toCreate.addProperty(requestProp, defaultValue);
        //            return;
        //         }
        //      }

        // Check for a list of allowed values that we can use.
        if (propertyToFill.hasProperty(allowedValueProp)) {
            RDFNode randomAllowedValue = getAllowedValue(propertyToFill);
            toCreate.addProperty(requestProp, randomAllowedValue);
            return;
        }

        if (propertyToFill.hasProperty(allowedValuesProp)) {
            Resource allowedValuesResource = propertyToFill.getProperty(allowedValuesProp).getResource();
            String allowedValuesUri = allowedValuesResource.getURI();
            RDFNode randomAllowedValue;
            if (allowedValuesUri == null) {
                // Blank node. Maybe the allowed values are inline?
                // It's not normal since in that case you can use oslc:allowedValue directly on Property.
                randomAllowedValue = getAllowedValue(allowedValuesResource);
            } else {
                // Make another request to get the list.
                Model allowedValuesModel = getModel(allowedValuesUri);
                allowedValuesResource = allowedValuesModel.getResource(allowedValuesUri);
                randomAllowedValue = getAllowedValue(allowedValuesResource);
            }
            toCreate.addProperty(requestProp, randomAllowedValue);
            return;
        }

        // No allowed values. Fill some some data appropriate to the type.
        fillInPropertyFromValueType(toCreate, propertyToFill, requestProp, depth);
    }

    /*
     * Try to find an acceptable allowed value. Really we should be able to
     * select any, but some providers give an empty or unassigned value as the
     * first item in the list. Technically, order is not guaranteed from
     * listProperties(), but in practice it seems to be preserved. Thus let's
     * try to avoid the first item if possible to minimize errors.
     */
    private RDFNode getAllowedValue(Resource r) {
        final Property allowedValueProp = r.getModel().createProperty(OSLCConstants.ALLOWED_VALUE);
        List<Statement> allowedValues = r.listProperties(allowedValueProp).toList();
        if (allowedValues.isEmpty()) {
            return null;
        }

        if (allowedValues.size() == 1) {
            return allowedValues.get(0).getObject();
        }

        if (allowedValues.size() == 2) {
            return allowedValues.get(1).getObject();
        }

        // Even the second item causes problems sometimes.
        // Use the second to last... Maybe it's safe?
        return allowedValues.get(allowedValues.size() - 2).getObject();
    }

    /*
     * Attempt to add a value for this property using its value type.
     */
    private void fillInPropertyFromValueType(Resource toCreate, Resource propertyResource, Property requestProp,
            int depth) throws IOException {
        Model requestModel = toCreate.getModel();
        Model shapeModel = propertyResource.getModel();
        final Property valueTypeProp = shapeModel.createProperty(OSLCConstants.VALUE_TYPE);

        if (propertyResource.hasProperty(valueTypeProp)) {
            final Property rangeProp = shapeModel.createProperty(OSLCConstants.RANGE);
            final Property valueShapeProp = shapeModel.createProperty(OSLCConstants.VALUE_SHAPE_PROP);
            HashSet<String> valueTypes = new HashSet<String>();
            StmtIterator valueTypeIter = propertyResource.listProperties(valueTypeProp);
            while (valueTypeIter.hasNext()) {
                String typeUri = valueTypeIter.next().getResource().getURI();
                valueTypes.add(typeUri);
            }

            /*
             * Look at each type. Try to fill in something reasonable.
             */
            if (valueTypes.contains(OSLCConstants.STRING_TYPE)) {
                String string = generateStringValue(getMaxSize(propertyResource));
                toCreate.addProperty(requestProp, string);
            } else if (valueTypes.contains(OSLCConstants.XML_LITERAL_TYPE)) {
                String string = generateStringValue(getMaxSize(propertyResource));
                Literal literal = requestModel.createTypedLiteral(string, XMLLiteralType.theXMLLiteralType);
                toCreate.addLiteral(requestProp, literal);
            } else if (valueTypes.contains(OSLCConstants.BOOLEAN_TYPE)) {
                toCreate.addLiteral(requestProp, true);
            } else if (valueTypes.contains(OSLCConstants.INTEGER_TYPE)) {
                toCreate.addLiteral(requestProp, 1);
            } else if (valueTypes.contains(OSLCConstants.DOUBLE_TYPE)) {
                toCreate.addLiteral(requestProp, 1.0d);
            } else if (valueTypes.contains(OSLCConstants.FLOAT_TYPE)) {
                toCreate.addLiteral(requestProp, 1.0f);
            } else if (valueTypes.contains(OSLCConstants.DECIMAL_TYPE)) {
                Literal literal = requestModel.createTypedLiteral(1, OSLCConstants.DECIMAL_TYPE);
                toCreate.addLiteral(requestProp, literal);
            } else if (valueTypes.contains(OSLCConstants.DATE_TIME_TYPE)) {
                toCreate.addLiteral(requestProp, requestModel.createTypedLiteral(Calendar.getInstance()));
            } else {
                // It appears to be a resource.
                Statement valueShapeStatement = propertyResource.getProperty(valueShapeProp);
                if (valueShapeStatement == null) {
                    // We have no shape, so this will likely fail. We can try, though.
                    // Create an empty resource. Add an rdf:type if the property has a range.
                    Resource valueResource = requestModel.createResource();
                    StmtIterator rangeIter = propertyResource.listProperties(rangeProp);
                    if (rangeIter.hasNext()) {
                        valueResource.addProperty(RDF.type, rangeIter.next().getResource());
                    }
                    toCreate.addProperty(requestProp, valueResource);
                } else {
                    Resource nested = createResourceFromShape(requestModel,
                            valueShapeStatement.getResource().getURI(), depth + 1);
                    toCreate.addProperty(requestProp, nested);
                }
            }
        } else {
            // We have no hints. Try to set a string value. This may fail.
            String string = generateStringValue(getMaxSize(propertyResource));
            toCreate.addProperty(requestProp, string);
        }
    }

    protected String toString(Model model) {
        String lang = (OSLCConstants.CT_XML.equals(getContentType())) ? "RDF/XML-ABBREV" : "RDF/XML";
        StringWriter writer = new StringWriter();
        model.write(writer, lang, "");

        return writer.toString();
    }

    /*
     * Is this property from a resource shape required?
     */
    private boolean isPropertyRequired(Resource property) {
        Statement statement = property
                .getRequiredProperty(property.getModel().createProperty(OSLCConstants.OCCURS));
        String occursValue = statement.getResource().getURI();

        return isPropertyRequired(occursValue);
    }

    /*
     * Is this property from a resource shape read only?
     */
    private boolean isPropertyReadOnly(Resource property) {
        Statement statement = property.getProperty(property.getModel().createProperty(OSLCConstants.READ_ONLY));
        if (statement == null) {
            return false;
        }

        return statement.getBoolean();
    }

    /*
     * Is this property from a resource shape a string?
     */
    private boolean isStringType(Resource property) {
        Property valueTypeProp = property.getModel().getProperty(OSLCConstants.VALUE_TYPE);
        if (!property.hasProperty(valueTypeProp)) {
            // We don't know, but assume it's not.
            return false;
        }

        StmtIterator iter = property.listProperties(valueTypeProp);
        while (iter.hasNext()) {
            String valueType = iter.next().getResource().getURI();
            if (OSLCConstants.STRING_TYPE.equals(valueType)) {
                return true;
            }
        }

        return false;
    }

    /*
     * Get the max size for this property if defined. Return null otherwise.
     */
    private Integer getMaxSize(Resource property) {
        Property maxSizeProp = property.getModel().createProperty(OSLCConstants.MAX_SIZE_PROP);
        Statement maxSize = property.getProperty(maxSizeProp);
        if (maxSize == null) {
            return null;
        }

        return maxSize.getInt();
    }

    private Model getModel(String uri) throws IOException {
        HttpResponse resp = OSLCUtils.getResponseFromUrl(uri, null, creds, OSLCConstants.CT_RDF, headers);
        try {
            assertEquals("Failed to get resource at " + uri, 200, resp.getStatusLine().getStatusCode());
            Model model = ModelFactory.createDefaultModel();
            model.read(resp.getEntity().getContent(), uri, OSLCConstants.JENA_RDF_XML);
            RDFUtils.validateModel(model);

            return model;
        } finally {
            EntityUtils.consume(resp.getEntity());
        }
    }

    /**
     * Change a resource using its instance shape. Will only attempt to change
     * one property to limit the chance of errors.
     * 
     * @param resource
     *            the resource to modify
     * 
     * @throws IOException
     *             on errors requesting the instance shape
     */
    protected String updateResourceFromShape(String uri, String contentType) throws IOException {
        Model resourceModel = getModel(uri);
        Resource resource = resourceModel.getResource(uri);
        Property instanceShapeProp = resourceModel.createProperty(OSLCConstants.INSTANCE_SHAPE);
        Statement instanceShapeStatement = resource.getProperty(instanceShapeProp);
        assertNotNull("The resource does not have an instance shape.", instanceShapeStatement);

        String shapeUri = instanceShapeStatement.getResource().getURI();
        Model shapeModel = getModel(shapeUri);

        Property propertyProp = shapeModel.createProperty(OSLCConstants.PROPERTY);
        Property propertyDefinitionProp = shapeModel.createProperty(OSLCConstants.PROPERTY_DEFINITION);
        Resource shape = shapeModel.getResource(shapeUri);
        StmtIterator propertyIterator = shape.listProperties(propertyProp);

        // Get the list of properties.
        //List<Statement> propertyStatements = shape.listProperties(propertyProp).toList();
        while (propertyIterator.hasNext()) {
            Resource property = propertyIterator.next().getResource();

            // Skip read-only properties that we can't change.
            if (isPropertyReadOnly(property)) {
                continue;
            }

            /*
             * For now, let's keep things simple and try to find a string value
             * we can update. This will fail (incorrectly) if the resource does
             * not have any modifiable strings. We could make this more
             * sophisticated and look for any type that might be modifiable.
             */
            if (isStringType(property)) {
                String propertyDefinition = property.getRequiredProperty(propertyDefinitionProp).getResource()
                        .getURI();
                Property propertyToChange = resourceModel.createProperty(propertyDefinition);
                resource.removeAll(propertyToChange);
                resource.addLiteral(propertyToChange, generateStringValue(getMaxSize(propertyToChange)));

                // Updating one field should be good enough.
                break;
            }
        }

        return toString(resourceModel);
    }

    protected String getCreateContent(String template) throws IOException {
        String content;
        if (template == null) {
            String shapeUri = getShapeUriForCreation(currentUrl);
            assertNotNull("No shape for creation factory: " + currentUrl, shapeUri);
            content = createResourceFromShape(shapeUri);
        } else {
            content = template;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("POST content:");
            logger.debug(content);
        }

        return content;
    }

    protected String getUpdateContent(String resourceUri, String template) throws IOException {
        if (template == null) {
            return updateResourceFromShape(resourceUri, getContentType());
        }

        if (template.contains("rdf:about=\"\"")) {
            // We need to replace the rdf:about in the template with the real url
            String replacement = "rdf:about=\"" + resourceUri + "\"";
            template = template.replace("rdf:about=\"\"", replacement);
        }

        return template;
    }
}