RYA-250 Smart URI; Closes #148
Project: http://git-wip-us.apache.org/repos/asf/incubator-rya/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-rya/commit/2e71ff2a Tree: http://git-wip-us.apache.org/repos/asf/incubator-rya/tree/2e71ff2a Diff: http://git-wip-us.apache.org/repos/asf/incubator-rya/diff/2e71ff2a Branch: refs/heads/master Commit: 2e71ff2a711fc857533f87b9af5c7258ca897823 Parents: 4b755c6 Author: eric.white <[email protected]> Authored: Mon Feb 27 11:12:05 2017 -0500 Committer: pujav65 <[email protected]> Committed: Tue May 9 10:09:03 2017 -0400 ---------------------------------------------------------------------- .../org/apache/rya/api/domain/RyaTypeUtils.java | 157 +++++ extras/indexing/pom.xml | 5 + .../rya/indexing/entity/model/Entity.java | 117 +++- .../storage/mongo/EntityDocumentConverter.java | 18 +- .../storage/mongo/MongoEntityStorage.java | 6 +- .../storage/mongo/key/MongoDbSafeKey.java | 81 +++ .../rya/indexing/mongodb/MongoDbSmartUri.java | 172 +++++ .../rya/indexing/smarturi/SmartUriAdapter.java | 688 +++++++++++++++++++ .../indexing/smarturi/SmartUriException.java | 36 + .../rya/indexing/smarturi/SmartUriStorage.java | 78 +++ .../rya/indexing/mongo/MongoDbSmartUriTest.java | 333 +++++++++ extras/indexingExample/pom.xml | 6 + extras/rya.merger/pom.xml | 5 - pom.xml | 21 +- 14 files changed, 1705 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/common/rya.api/src/main/java/org/apache/rya/api/domain/RyaTypeUtils.java ---------------------------------------------------------------------- diff --git a/common/rya.api/src/main/java/org/apache/rya/api/domain/RyaTypeUtils.java b/common/rya.api/src/main/java/org/apache/rya/api/domain/RyaTypeUtils.java new file mode 100644 index 0000000..8ca65fc --- /dev/null +++ b/common/rya.api/src/main/java/org/apache/rya/api/domain/RyaTypeUtils.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.rya.api.domain; + +import java.util.Date; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.format.ISODateTimeFormat; +import org.openrdf.model.URI; +import org.openrdf.model.vocabulary.XMLSchema; + +/** + * Utility methods for using {@link RyaType}. + */ +public final class RyaTypeUtils { + /** + * Private constructor to prevent instantiation. + */ + private RyaTypeUtils() { + } + + /** + * Creates a boolean {@link RyaType} object. + * @param value the {@link Boolean} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#BOOLEAN} and the data set to the specified + * {@code value}. + */ + public static RyaType booleanRyaType(final Boolean value) { + return new RyaType(XMLSchema.BOOLEAN, Boolean.toString(value)); + } + + /** + * Creates a byte {@link RyaType} object. + * @param value the {@link Byte} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#BYTE} and the data set to the specified {@code value}. + */ + public static RyaType byteRyaType(final Byte value) { + return new RyaType(XMLSchema.BYTE, Byte.toString(value)); + } + + /** + * Creates a date {@link RyaType} object. + * @param value the {@link Date} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#DATETIME} and the data set to the specified + * {@code value}. + */ + public static RyaType dateRyaType(final Date value) { + final DateTime dateTime = new DateTime(value.getTime()); + return dateRyaType(dateTime); + } + + /** + * Creates a date/time {@link RyaType} object. + * @param value the {@link DateTime} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#DATETIME} and the data set to the specified + * {@code value}. + */ + public static RyaType dateRyaType(final DateTime value) { + final StringBuffer sb = new StringBuffer(); + ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC).printTo(sb, value.getMillis()); + final String formattedDate = sb.toString(); + return new RyaType(XMLSchema.DATETIME, formattedDate); + } + + /** + * Creates a double {@link RyaType} object. + * @param value the {@link Double} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#DOUBLE} and the data set to the specified {@code value}. + */ + public static RyaType doubleRyaType(final Double value) { + return new RyaType(XMLSchema.DOUBLE, Double.toString(value)); + } + + /** + * Creates a float {@link RyaType} object. + * @param value the {@link Float} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#FLOAT} and the data set to the specified {@code value}. + */ + public static RyaType floatRyaType(final Float value) { + return new RyaType(XMLSchema.FLOAT, Float.toString(value)); + } + + /** + * Creates an integer {@link RyaType} object. + * @param value the {@link Integer} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#INTEGER} and the data set to the specified + * {@code value}. + */ + public static RyaType intRyaType(final Integer value) { + return new RyaType(XMLSchema.INTEGER, Integer.toString(value)); + } + + /** + * Creates a long {@link RyaType} object. + * @param value the {@link Long} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#LONG} and the data set to the specified {@code value}. + */ + public static RyaType longRyaType(final Long value) { + return new RyaType(XMLSchema.LONG, Long.toString(value)); + } + + /** + * Creates a short {@link RyaType} object. + * @param value the {@link Short} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#SHORT} and the data set to the specified {@code value}. + */ + public static RyaType shortRyaType(final Short value) { + return new RyaType(XMLSchema.SHORT, Short.toString(value)); + } + + /** + * Creates a string {@link RyaType} object. + * @param value the {@link String} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#STRING} and the data set to the specified {@code value}. + */ + public static RyaType stringRyaType(final String value) { + return new RyaType(XMLSchema.STRING, value); + } + + /** + * + * Creates a URI {@link RyaType} object. + * @param value the {@link URI} object. + * @return the {@link RyaType} with the data type set to + * {@link XMLSchema#ANYURI} and the data set to the specified {@code value}. + */ + public static RyaType uriRyaType(final URI value) { + return new RyaType(XMLSchema.ANYURI, value.stringValue()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexing/pom.xml ---------------------------------------------------------------------- diff --git a/extras/indexing/pom.xml b/extras/indexing/pom.xml index e38e75d..755646c 100644 --- a/extras/indexing/pom.xml +++ b/extras/indexing/pom.xml @@ -72,6 +72,11 @@ <artifactId>commons-codec</artifactId> </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + </dependency> + <!-- PCJ Indexing --> <dependency> http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/model/Entity.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/model/Entity.java b/extras/indexing/src/main/java/org/apache/rya/indexing/entity/model/Entity.java index 3eb4c39..a90e469 100644 --- a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/model/Entity.java +++ b/extras/indexing/src/main/java/org/apache/rya/indexing/entity/model/Entity.java @@ -29,8 +29,12 @@ import java.util.Objects; import java.util.Optional; import org.apache.http.annotation.Immutable; +import org.apache.log4j.Logger; import org.apache.rya.api.domain.RyaURI; import org.apache.rya.indexing.entity.storage.EntityStorage; +import org.apache.rya.indexing.smarturi.SmartUriAdapter; +import org.apache.rya.indexing.smarturi.SmartUriException; +import org.openrdf.model.URI; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -73,6 +77,7 @@ import edu.umd.cs.findbugs.annotations.Nullable; @Immutable @DefaultAnnotation(NonNull.class) public class Entity { + private static final Logger log = Logger.getLogger(Entity.class); private final RyaURI subject; private final ImmutableList<RyaURI> explicitTypeIds; @@ -84,18 +89,60 @@ public class Entity { private final int version; + private URI smartUri = null; + /** - * To construct an instances of this class, use {@link Builder}. + * To construct an instance of this class, use {@link Builder}. + * @param subject Identifies the thing that is being represented as an + * Entity. + * @param explicitTypeIds {@link Type}s that have been explicitly applied to + * the {@link Entity}. + * @param typeProperties All {@link Property}s that have been set for the + * Entity, grouped by Type ID. + * @param version The version of this Entity. This value is used by the + * {@link EntityStorage} to prevent stale updates. + * @param smartUri the Smart {@link URI} representation of this + * {@link Entity}. */ private Entity( final RyaURI subject, final ImmutableList<RyaURI> explicitTypeIds, final ImmutableMap<RyaURI, ImmutableMap<RyaURI, Property>> typeProperties, - final int version) { + final int version, + final URI smartUri) { this.subject = requireNonNull(subject); this.explicitTypeIds = requireNonNull(explicitTypeIds); properties = requireNonNull(typeProperties); this.version = version; + if (smartUri != null) { + this.smartUri = smartUri; + } else { + // if Smart URI isn't provided create it from the given properties + try { + this.smartUri = SmartUriAdapter.serializeUriEntity(this); + } catch (final SmartUriException e) { + log.error("Unable to create a Smart URI for the entity", e); + } + } + } + + /** + * To construct an instance of this class, use {@link Builder}. + * @param subject Identifies the thing that is being represented as an + * Entity. + * @param explicitTypeIds {@link Type}s that have been explicitly applied to + * the {@link Entity}. + * @param typeProperties All {@link Property}s that have been set for the + * Entity, grouped by Type ID. + * @param version The version of this Entity. This value is used by the + * {@link EntityStorage} to prevent stale updates. + */ + private Entity( + final RyaURI subject, + final ImmutableList<RyaURI> explicitTypeIds, + final ImmutableMap<RyaURI, ImmutableMap<RyaURI, Property>> typeProperties, + final int version) { + this(subject, explicitTypeIds, typeProperties, version, null); } /** @@ -127,6 +174,45 @@ public class Entity { return version; } + /** + * @return the Smart {@link URI} representation of this {@link Entity}. + */ + public URI getSmartUri() { + return smartUri; + } + + /** + * Does a lookup to see if the {@link Entity} contains the specified + * property for the specified type. + * @param typeRyaUri the type {@link RyaURI}. (not {@code null}) + * @param propertyRyaUri the property {@link RyaURI}. (not {@code null}) + * @return the {@link Property} or an empty {@link Optional} if it could not + * be found in the {@link Entity}. + */ + public Optional<Property> lookupTypeProperty(final Type type, final RyaURI propertyRyaUri) { + requireNonNull(type); + return lookupTypeProperty(type.getId(), propertyRyaUri); + } + + /** + * Does a lookup to see if the {@link Entity} contains the specified + * property for the specified type. + * @param typeRyaUri the type {@link RyaURI}. (not {@code null}) + * @param propertyRyaUri the property {@link RyaURI}. (not {@code null}) + * @return the {@link Property} or an empty {@link Optional} if it could not + * be found in the {@link Entity}. + */ + public Optional<Property> lookupTypeProperty(final RyaURI typeRyaUri, final RyaURI propertyRyaUri) { + requireNonNull(typeRyaUri); + requireNonNull(propertyRyaUri); + final ImmutableMap<RyaURI, Property> typePropertyMap = properties.get(typeRyaUri); + Optional<Property> property = Optional.empty(); + if (typePropertyMap != null) { + property = Optional.of(typePropertyMap.get(propertyRyaUri)); + } + return property; + } + @Override public int hashCode() { return Objects.hash(subject, explicitTypeIds, properties, version); @@ -206,6 +292,7 @@ public class Entity { private RyaURI subject = null; private final List<RyaURI> explicitTypes = new ArrayList<>(); private final Map<RyaURI, Map<RyaURI, Property>> properties = new HashMap<>(); + private URI smartUri = null; private int version = 0; @@ -230,6 +317,8 @@ public class Entity { } version = entity.getVersion(); + + smartUri = entity.getSmartUri(); } /** @@ -303,6 +392,27 @@ public class Entity { } /** + * @param smartUri - the Smart {@link URI} representation of this + * {@link Entity}. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setSmartUri(final URI smartUri) { + this.smartUri = smartUri; + return this; + } + + /** + * Indicates that the builder should rebuild the Smart URI. This should + * be used when properties or anything else in the {@link Entity} has + * changed. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder rebuildSmartUri() { + setSmartUri(null); + return this; + } + + /** * @param version - The version of this Entity. This value is used by the * {@link EntityStorage} to prevent stale updates. * @return This {@link Builder} so that method invocations may be chained. @@ -324,7 +434,8 @@ public class Entity { return new Entity(subject, ImmutableList.copyOf( explicitTypes ), propertiesBuilder.build(), - version); + version, + smartUri); } } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/EntityDocumentConverter.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/EntityDocumentConverter.java b/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/EntityDocumentConverter.java index a46fd5a..f2647ef 100644 --- a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/EntityDocumentConverter.java +++ b/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/EntityDocumentConverter.java @@ -27,7 +27,9 @@ import org.apache.rya.api.domain.RyaType; import org.apache.rya.api.domain.RyaURI; import org.apache.rya.indexing.entity.model.Entity; import org.apache.rya.indexing.entity.model.Property; +import org.apache.rya.indexing.entity.storage.mongo.key.MongoDbSafeKey; import org.bson.Document; +import org.openrdf.model.impl.URIImpl; import edu.umd.cs.findbugs.annotations.DefaultAnnotation; import edu.umd.cs.findbugs.annotations.NonNull; @@ -42,6 +44,7 @@ public class EntityDocumentConverter implements DocumentConverter<Entity> { public static final String EXPLICIT_TYPE_IDS = "explicitTypeIds"; public static final String PROPERTIES = "properties"; public static final String VERSION = "version"; + public static final String SMART_URI = "smartUri"; private final RyaTypeDocumentConverter ryaTypeConverter = new RyaTypeDocumentConverter(); @@ -62,8 +65,9 @@ public class EntityDocumentConverter implements DocumentConverter<Entity> { entity.getProperties().get(typeId) .forEach((propertyNameUri, property) -> { final String propertyName = property.getName().getData(); + final String encodedPropertyName = MongoDbSafeKey.encodeKey(propertyName); final RyaType value = property.getValue(); - typePropertiesDoc.append(propertyName, ryaTypeConverter.toDocument(value)); + typePropertiesDoc.append(encodedPropertyName, ryaTypeConverter.toDocument(value)); }); propertiesDoc.append(typeId.getData(), typePropertiesDoc); } @@ -71,6 +75,8 @@ public class EntityDocumentConverter implements DocumentConverter<Entity> { doc.append(VERSION, entity.getVersion()); + doc.append(SMART_URI, entity.getSmartUri().stringValue()); + return doc; } @@ -99,6 +105,11 @@ public class EntityDocumentConverter implements DocumentConverter<Entity> { "' because its '" + VERSION + "' field is missing."); } + if(!document.containsKey(SMART_URI)) { + throw new DocumentConverterException("Could not convert document '" + document + + "' because its '" + SMART_URI + "' field is missing."); + } + // Perform the conversion. final Entity.Builder builder = Entity.builder() .setSubject( new RyaURI(document.getString(SUBJECT)) ); @@ -110,14 +121,17 @@ public class EntityDocumentConverter implements DocumentConverter<Entity> { for(final String typeId : propertiesDoc.keySet()) { final Document typePropertiesDoc = (Document) propertiesDoc.get(typeId); for(final String propertyName : typePropertiesDoc.keySet()) { + final String decodedPropertyName = MongoDbSafeKey.decodeKey(propertyName); final Document value = (Document) typePropertiesDoc.get(propertyName); final RyaType propertyValue = ryaTypeConverter.fromDocument( value ); - builder.setProperty(new RyaURI(typeId), new Property(new RyaURI(propertyName), propertyValue)); + builder.setProperty(new RyaURI(typeId), new Property(new RyaURI(decodedPropertyName), propertyValue)); } } builder.setVersion( document.getInteger(VERSION) ); + builder.setSmartUri( new URIImpl(document.getString(SMART_URI)) ); + return builder.build(); } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/MongoEntityStorage.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/MongoEntityStorage.java b/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/MongoEntityStorage.java index 3fc817b..1b4681d 100644 --- a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/MongoEntityStorage.java +++ b/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/MongoEntityStorage.java @@ -34,6 +34,7 @@ import org.apache.rya.indexing.entity.model.TypedEntity; import org.apache.rya.indexing.entity.storage.EntityStorage; import org.apache.rya.indexing.entity.storage.mongo.ConvertingCursor.Converter; import org.apache.rya.indexing.entity.storage.mongo.DocumentConverter.DocumentConverterException; +import org.apache.rya.indexing.entity.storage.mongo.key.MongoDbSafeKey; import org.bson.Document; import org.bson.conversions.Bson; @@ -225,16 +226,17 @@ public class MongoEntityStorage implements EntityStorage { private static Stream<Bson> makePropertyFilters(final RyaURI typeId, final Property property) { final String propertyName = property.getName().getData(); + final String encodedPropertyName = MongoDbSafeKey.encodeKey(propertyName); // Must match the property's data type. final String dataTypePath = Joiner.on(".").join( - new String[]{EntityDocumentConverter.PROPERTIES, typeId.getData(), propertyName, RyaTypeDocumentConverter.DATA_TYPE}); + new String[]{EntityDocumentConverter.PROPERTIES, typeId.getData(), encodedPropertyName, RyaTypeDocumentConverter.DATA_TYPE}); final String propertyDataType = property.getValue().getDataType().stringValue(); final Bson dataTypeFilter = Filters.eq(dataTypePath, propertyDataType); // Must match the property's value. final String valuePath = Joiner.on(".").join( - new String[]{EntityDocumentConverter.PROPERTIES, typeId.getData(), propertyName, RyaTypeDocumentConverter.VALUE}); + new String[]{EntityDocumentConverter.PROPERTIES, typeId.getData(), encodedPropertyName, RyaTypeDocumentConverter.VALUE}); final String propertyValue = property.getValue().getData(); final Bson valueFilter = Filters.eq(valuePath, propertyValue); http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/key/MongoDbSafeKey.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/key/MongoDbSafeKey.java b/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/key/MongoDbSafeKey.java new file mode 100644 index 0000000..3d569f9 --- /dev/null +++ b/extras/indexing/src/main/java/org/apache/rya/indexing/entity/storage/mongo/key/MongoDbSafeKey.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.rya.indexing.entity.storage.mongo.key; + +import org.javatuples.Pair; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * Utilities to escape keys to be safe for MongoDB use. + */ +public final class MongoDbSafeKey { + /** + * Encode characters in this order. + */ + private static final ImmutableList<Pair<String, String>> ESCAPE_CHARACTERS = ImmutableList.of( + new Pair<>("\\", "\\\\"), + new Pair<>("\\$", "\\u0024"), + new Pair<>(".", "\\u002e") + ); + + /** + * Decode characters in this order (reverse order of encoding). + */ + private static final ImmutableList<Pair<String, String>> UNESCAPE_CHARACTERS = ImmutableList.copyOf(Lists.reverse(ESCAPE_CHARACTERS)); + + /** + * Private constructor to prevent instantiation. + */ + private MongoDbSafeKey() { + } + + /** + * Encodes a string to be safe for use as a MongoDB field name. + * @param key the unencoded key. + * @return the encoded key. + */ + public static String encodeKey(final String key) { + String encodedKey = key; + for (final Pair<String, String> pair : ESCAPE_CHARACTERS) { + final String unescapedCharacter = pair.getValue0(); + final String escapedCharacter = pair.getValue1(); + encodedKey = encodedKey.replace(unescapedCharacter, escapedCharacter); + } + + return encodedKey; + } + + /** + * Decodes a MongoDB safe field name to an unencoded string. + * @param key the encoded key. + * @return the unencoded key. + */ + public static String decodeKey(final String key) { + String decodedKey = key; + for (final Pair<String, String> pair : UNESCAPE_CHARACTERS) { + final String unescapedCharacter = pair.getValue0(); + final String escapedCharacter = pair.getValue1(); + decodedKey = decodedKey.replace(escapedCharacter, unescapedCharacter); + } + + return decodedKey; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexing/src/main/java/org/apache/rya/indexing/mongodb/MongoDbSmartUri.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/org/apache/rya/indexing/mongodb/MongoDbSmartUri.java b/extras/indexing/src/main/java/org/apache/rya/indexing/mongodb/MongoDbSmartUri.java new file mode 100644 index 0000000..9fdfad6 --- /dev/null +++ b/extras/indexing/src/main/java/org/apache/rya/indexing/mongodb/MongoDbSmartUri.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.rya.indexing.mongodb; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.net.UnknownHostException; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.io.IOUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.rya.api.domain.RyaURI; +import org.apache.rya.indexing.entity.model.Entity; +import org.apache.rya.indexing.entity.model.Property; +import org.apache.rya.indexing.entity.model.Type; +import org.apache.rya.indexing.entity.model.TypedEntity; +import org.apache.rya.indexing.entity.storage.EntityStorage; +import org.apache.rya.indexing.entity.storage.EntityStorage.EntityStorageException; +import org.apache.rya.indexing.entity.storage.mongo.ConvertingCursor; +import org.apache.rya.indexing.entity.storage.mongo.MongoEntityStorage; +import org.apache.rya.indexing.smarturi.SmartUriAdapter; +import org.apache.rya.indexing.smarturi.SmartUriException; +import org.apache.rya.indexing.smarturi.SmartUriStorage; +import org.apache.rya.mongodb.MongoConnectorFactory; +import org.apache.rya.mongodb.MongoDBRdfConfiguration; +import org.openrdf.model.URI; +import org.openrdf.model.Value; + +import com.mongodb.MongoClient; +import com.mongodb.MongoException; + +/** + * MongoDB implementation of the Smart URI. + */ +public class MongoDbSmartUri implements SmartUriStorage { + private boolean isInit = false; + private final MongoDBRdfConfiguration conf; + private MongoClient mongoClient = null; + private EntityStorage entityStorage; + + /** + * Creates a new instance of {@link MongoDbSmartUri}. + * @param conf the {@link MongoDBRdfConfiguration}. (not {@code null}) + */ + public MongoDbSmartUri(final MongoDBRdfConfiguration conf) { + this.conf = checkNotNull(conf); + } + + @Override + public void storeEntity(final RyaURI subject, final Map<URI, Value> map) throws SmartUriException { + checkInit(); + + final URI uri = SmartUriAdapter.serializeUri(subject, map); + final Entity entity = SmartUriAdapter.deserializeUriEntity(uri); + + // Create it. + try { + entityStorage.create(entity); + } catch (final EntityStorageException e) { + throw new SmartUriException("Failed to create entity storage", e); + } + } + + @Override + public void storeEntity(final Entity entity) throws SmartUriException { + checkInit(); + + // Create it. + try { + entityStorage.create(entity); + } catch (final EntityStorageException e) { + throw new SmartUriException("Failed to create entity storage", e); + } + } + + @Override + public void updateEntity(final Entity oldEntity, final Entity updatedEntity) throws SmartUriException { + checkInit(); + + // Update it. + try { + entityStorage.update(oldEntity, updatedEntity); + } catch (final EntityStorageException e) { + throw new SmartUriException("Failed to update entity", e); + } + } + + @Override + public Entity queryEntity(final RyaURI subject) throws SmartUriException { + checkInit(); + + // Query it. + try { + final Optional<Entity> resultEntity = entityStorage.get(subject); + return resultEntity.get(); + } catch (final EntityStorageException e) { + throw new SmartUriException("Failed to query entity storage", e); + } + } + + @Override + public ConvertingCursor<TypedEntity> queryEntity(final Type type, final Map<URI, Value> map) throws SmartUriException { + checkInit(); + + // Query it. + try { + final Set<Property> properties = SmartUriAdapter.mapToProperties(map); + final ConvertingCursor<TypedEntity> cursor = entityStorage.search(Optional.empty(), type, properties); + return cursor; + } catch (final EntityStorageException e) { + throw new SmartUriException("Failed to query entity storage", e); + } + } + + private void checkInit() throws SmartUriException { + if (!isInit) { + try { + setupClient(conf); + } catch (final UnknownHostException | MongoException e) { + throw new SmartUriException("Failed to setup MongoDB client", e); + } + } + } + + /** + * Setup the MongoDB client. + * @param conf the {@link Configuration}. + * @throws UnknownHostException + * @throws MongoException + */ + private void setupClient(final Configuration conf) throws UnknownHostException, MongoException { + final MongoDBRdfConfiguration mongoConf = (MongoDBRdfConfiguration) conf; + mongoClient = mongoConf.getMongoClient(); + if (mongoClient == null) { + mongoClient = MongoConnectorFactory.getMongoClient(conf); + } + entityStorage = new MongoEntityStorage(mongoClient, mongoConf.getMongoInstance()); + isInit = true; + } + + /** + * Shutdown the MongoDB client. + */ + public void shutdown() { + IOUtils.closeQuietly(mongoClient); + } + + /** + * @return the {@link EntityStorage}. + */ + public EntityStorage getEntityStorage() { + return entityStorage; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriAdapter.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriAdapter.java b/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriAdapter.java new file mode 100644 index 0000000..d6a5e8a --- /dev/null +++ b/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriAdapter.java @@ -0,0 +1,688 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.rya.indexing.smarturi; + +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.message.BasicNameValuePair; +import org.apache.rya.api.domain.RyaType; +import org.apache.rya.api.domain.RyaURI; +import org.apache.rya.api.resolver.RdfToRyaConversions; +import org.apache.rya.api.resolver.RyaToRdfConversions; +import org.apache.rya.indexing.entity.model.Entity; +import org.apache.rya.indexing.entity.model.Property; +import org.joda.time.DateTime; +import org.joda.time.format.ISODateTimeFormat; +import org.openrdf.model.URI; +import org.openrdf.model.Value; +import org.openrdf.model.impl.URIImpl; +import org.openrdf.model.vocabulary.XMLSchema; + +import com.google.common.base.Charsets; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableMap; +import com.google.common.primitives.Doubles; +import com.google.common.primitives.Floats; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; + +/** + * Interface for serializing and deserializing Smart URIs. + */ +public class SmartUriAdapter { + private static final String ENTITY_TYPE_MAP_URN = "urn://entityTypeMap"; + private static final URI RYA_TYPES_URI = new URIImpl("urn://ryaTypes"); + + /** + * Private constructor to prevent instantiation. + */ + private SmartUriAdapter() { + } + + private static URI createTypePropertiesUri(final ImmutableMap<RyaURI, ImmutableMap<RyaURI, Property>> typeProperties) throws SmartUriException { + final List<NameValuePair> nameValuePairs = new ArrayList<>(); + for (final Entry<RyaURI, ImmutableMap<RyaURI, Property>> typeProperty : typeProperties.entrySet()) { + final RyaURI type = typeProperty.getKey(); + final Map<RyaURI, Property> propertyMap = typeProperty.getValue(); + final URI typeUri = createIndividualTypeWithPropertiesUri(type, propertyMap); + final String keyString = type.getDataType().getLocalName(); + final String valueString = typeUri.getLocalName(); + nameValuePairs.add(new BasicNameValuePair(keyString, valueString)); + } + + final URIBuilder uriBuilder = new URIBuilder(); + uriBuilder.addParameters(nameValuePairs); + + String uriString; + try { + final java.net.URI uri = uriBuilder.build(); + final String queryString = uri.getRawSchemeSpecificPart(); + uriString = "urn:test" + queryString; + } catch (final URISyntaxException e) { + throw new SmartUriException("Unable to create type properties for the Smart URI", e); + } + + return new URIImpl(uriString); + } + + private static String getShortNameForType(final RyaURI type) throws SmartUriException { + String typeUriString; + try { + typeUriString = new java.net.URI(type.getData()).getRawSchemeSpecificPart(); + } catch (final URISyntaxException e) { + throw new SmartUriException("Unable to get create URI for type", e); + } + final Path path = Paths.get(typeUriString); + final String shortName = path.getFileName().toString(); + return shortName; + } + + + private static String addTypePrefixToUri(final String uriString, final String typePrefix) { + int location = StringUtils.lastIndexOf(uriString, "#"); + if (location == - 1) { + location = StringUtils.lastIndexOf(uriString, "/"); + } + + final String lastSegment = uriString.substring(location + 1); + + final String formattedUriString = uriString.substring(0, location + 1) + typePrefix + lastSegment; + return formattedUriString; + } + + private static String removeTypePrefixFromUri(final String uriString, final String typePrefix) { + int location = StringUtils.lastIndexOf(uriString, "#"); + if (location == - 1) { + location = StringUtils.lastIndexOf(uriString, "/"); + } + + final String lastSegment = uriString.substring(location + 1); + final String replacement = lastSegment.replaceFirst(typePrefix + ".", ""); + + final String formattedUriString = uriString.substring(0, location + 1) + replacement; + return formattedUriString; + } + + private static Map<RyaURI, String> createTypeMap(final List<RyaURI> types) throws SmartUriException { + final Map<RyaURI, String> map = new LinkedHashMap<>(); + for (final RyaURI type : types) { + final String shortName = getShortNameForType(type); + map.put(type, shortName); + } + return map; + } + + private static URI createTypeMapUri(final List<RyaURI> types) throws SmartUriException { + final List<NameValuePair> nameValuePairs = new ArrayList<>(); + for (final RyaURI type : types) { + final String shortName = getShortNameForType(type); + nameValuePairs.add(new BasicNameValuePair(type.getData(), shortName)); + } + + final URIBuilder uriBuilder = new URIBuilder(); + uriBuilder.addParameters(nameValuePairs); + + String uriString; + try { + final java.net.URI uri = uriBuilder.build(); + final String queryString = uri.getRawSchemeSpecificPart(); + uriString = ENTITY_TYPE_MAP_URN + queryString; + } catch (final URISyntaxException e) { + throw new SmartUriException("Unable to create type properties for the Smart URI", e); + } + + return new URIImpl(uriString); + } + + private static Map<RyaURI, String> convertUriToTypeMap(final URI typeMapUri) throws SmartUriException { + final Map<RyaURI, String> map = new HashMap<>(); + java.net.URI uri; + try { + final URIBuilder uriBuilder = new URIBuilder(typeMapUri.stringValue()); + uri = uriBuilder.build(); + } catch (final URISyntaxException e) { + throw new SmartUriException("Unable to parse Rya type map in Smart URI", e); + } + + final List<NameValuePair> params = URLEncodedUtils.parse(uri, Charsets.UTF_8.name()); + + for (final NameValuePair param : params) { + final String name = param.getName(); + final String value = param.getValue(); + final RyaURI type = new RyaURI(name); + map.put(type, value); + } + return map; + } + + private static URI createIndividualTypeWithPropertiesUri(final RyaURI type, final Map<RyaURI, Property> map) throws SmartUriException { + final List<NameValuePair> nameValuePairs = new ArrayList<>(); + for (final Entry<RyaURI, Property> entry : map.entrySet()) { + final RyaURI key = entry.getKey(); + final Property property = entry.getValue(); + + final RyaType ryaType = property.getValue(); + final String keyString = (new URIImpl(key.getData())).getLocalName(); + final Value value = RyaToRdfConversions.convertValue(ryaType); + final String valueString = value.stringValue(); + nameValuePairs.add(new BasicNameValuePair(keyString, valueString)); + } + + final URIBuilder uriBuilder = new URIBuilder(); + uriBuilder.addParameters(nameValuePairs); + + String uriString; + try { + final java.net.URI uri = uriBuilder.build(); + final String queryString = uri.getRawSchemeSpecificPart(); + uriString = type.getData()/*new URIImpl(type.getData()).getLocalName()*/ + queryString; + } catch (final URISyntaxException e) { + throw new SmartUriException("Unable to create type URI with all its properties for the Smart URI", e); + } + + return new URIImpl(uriString); + } + + private static Entity convertMapToEntity(final RyaURI subject, final Map<RyaURI, Map<URI, Value>> map) { + final Entity.Builder entityBuilder = Entity.builder(); + entityBuilder.setSubject(subject); + + for (final Entry<RyaURI, Map<URI, Value>> typeEntry : map.entrySet()) { + final RyaURI type = typeEntry.getKey(); + final Map<URI, Value> subMap = typeEntry.getValue(); + entityBuilder.setExplicitType(type); + for (final Entry<URI, Value> entry : subMap.entrySet()) { + final URI uri = entry.getKey(); + final Value value = entry.getValue(); + final RyaURI ryaUri = new RyaURI(uri.stringValue()); + final RyaURI ryaName = new RyaURI(uri.stringValue()); + final RyaType ryaType = new RyaType(value.stringValue()); + final Property property = new Property(ryaName, ryaType); + entityBuilder.setProperty(ryaUri, property); + } + } + final Entity entity = entityBuilder.build(); + return entity; + } + + public static RyaURI findSubject(final URI uri) throws SmartUriException { + final String uriString = uri.stringValue(); + return findSubject(uriString); + } + + public static RyaURI findSubject(final String uriString) throws SmartUriException { + java.net.URI uri; + try { + uri = new java.net.URI(uriString); + } catch (final URISyntaxException e) { + throw new SmartUriException("Could not find subject in Smart URI", e); + } + final RyaURI subject; + final String fullFragment = uri.getFragment(); + if (fullFragment != null) { + final int queryPosition = fullFragment.indexOf("?"); + String partialFragment = null; + if (queryPosition != - 1) { + partialFragment = fullFragment.substring(0, queryPosition); + } + final String subjectString = uri.getScheme() + ":" + uri.getSchemeSpecificPart() + "#" + partialFragment; + subject = new RyaURI(subjectString); + } else { + final int queryPosition = uriString.indexOf("?"); + String subjectString = null; + if (queryPosition != - 1) { + subjectString = uriString.substring(0, queryPosition); + } else { + subjectString = uriString; + } + subject = new RyaURI(subjectString); + } + + return subject; + } + + + /** + * Serializes an {@link Entity} into a Smart {@link URI}. + * @param entity the {@link Entity} to serialize into a Smart URI. + * @return the Smart {@link URI}. + * @throws SmartUriException + */ + public static URI serializeUriEntity(final Entity entity) throws SmartUriException { + final Map<URI, Value> objectMap = new LinkedHashMap<>(); + + // Adds the entity's types to the Smart URI + final List<RyaURI> typeIds = entity.getExplicitTypeIds(); + final Map<RyaURI, String> ryaTypeMap = createTypeMap(typeIds); + final URI ryaTypeMapUri = createTypeMapUri(typeIds); + final RyaType valueRyaType = new RyaType(XMLSchema.ANYURI, ryaTypeMapUri.stringValue()); + final Value typeValue = RyaToRdfConversions.convertValue(valueRyaType); + objectMap.put(RYA_TYPES_URI, typeValue); + + final RyaURI subject = entity.getSubject(); + final Map<RyaURI, ImmutableMap<RyaURI, Property>> typeMap = entity.getProperties(); + for (final Entry<RyaURI, ImmutableMap<RyaURI, Property>> typeEntry : typeMap.entrySet()) { + final RyaURI type = typeEntry.getKey(); + String typeShortName = ryaTypeMap.get(type); + typeShortName = typeShortName != null ? typeShortName + "." : ""; + final ImmutableMap<RyaURI, Property> typeProperties = typeEntry.getValue(); + for (final Entry<RyaURI, Property> properties : typeProperties.entrySet()) { + final RyaURI key = properties.getKey(); + final Property property = properties.getValue(); + final String valueString = property.getValue().getData(); + final RyaType ryaType = property.getValue(); + + //final RyaType ryaType = new RyaType(new URIImpl(key.getData()), valueString); + + final Value value = RyaToRdfConversions.convertValue(ryaType); + + String formattedKey = key.getData(); + if (StringUtils.isNotBlank(typeShortName)) { + formattedKey = addTypePrefixToUri(key.getData(), typeShortName); + } + final URI uri = new URIImpl(formattedKey); + objectMap.put(uri, value); + } + } + + return serializeUri(subject, objectMap); + } + + /** + * Serializes a map into a URI. + * @param subject the {@link RyaURI} subject of the Entity. Identifies the + * thing that is being represented as an Entity. + * @param map the {@link Map} of {@link URI}s to {@link Value}s. + * @return the Smart {@link URI}. + * @throws SmartUriException + */ + public static URI serializeUri(final RyaURI subject, final Map<URI, Value> map) throws SmartUriException { + final String subjectData = subject.getData(); + final int fragmentPosition = subjectData.indexOf("#"); + String prefix = subjectData; + String fragment = null; + if (fragmentPosition > -1) { + prefix = subjectData.substring(0, fragmentPosition); + fragment = subjectData.substring(fragmentPosition + 1, subjectData.length()); + } + + URIBuilder uriBuilder = null; + try { + if (fragmentPosition > -1) { + uriBuilder = new URIBuilder(new java.net.URI("urn://" + fragment)); + } else { + uriBuilder = new URIBuilder(new java.net.URI(subjectData)); + } + } catch (final URISyntaxException e) { + throw new SmartUriException("Unable to serialize a Smart URI from the provided properties", e); + } + final List<NameValuePair> nameValuePairs = new ArrayList<>(); + + for (final Entry<URI, Value> entry : map.entrySet()) { + final URI key = entry.getKey(); + final Value value = entry.getValue(); + nameValuePairs.add(new BasicNameValuePair(key.getLocalName(), value.stringValue())); + } + + uriBuilder.setParameters(nameValuePairs); + + URI uri = null; + try { + if (fragmentPosition > -1) { + final java.net.URI partialUri = uriBuilder.build(); + final String uriString = StringUtils.removeStart(partialUri.getRawSchemeSpecificPart(), "//"); + final URIBuilder fragmentUriBuilder = new URIBuilder(new java.net.URI(prefix)); + fragmentUriBuilder.setFragment(uriString); + final String fragmentUriString = fragmentUriBuilder.build().toString(); + uri = new URIImpl(fragmentUriString); + } else { + final String uriString = uriBuilder.build().toString(); + uri = new URIImpl(uriString); + } + } catch (final URISyntaxException e) { + throw new SmartUriException("Smart URI could not serialize the property map.", e); + } + + return uri; + } + + /** + * Deserializes a URI into a map of URI's to values. + * @param uri the {@link URI}. + * @return the {@link Map} of {@link URI}s to {@link Value}s. + * @throws SmartUriException + */ + public static Map<URI, Value> deserializeUri(final URI uri) throws SmartUriException { + final String uriString = uri.stringValue(); + final int fragmentPosition = uriString.indexOf("#"); + String prefix = uriString.substring(0, fragmentPosition + 1); + if (fragmentPosition == -1) { + prefix = uriString.split("\\?", 2)[0]; + } + final String fragment = uriString.substring(fragmentPosition + 1, uriString.length()); + java.net.URI queryUri; + + URIBuilder uriBuilder = null; + try { + if (fragmentPosition > -1) { + queryUri = new java.net.URI("urn://" + fragment); + } else { + queryUri = new java.net.URI(uriString); + } + uriBuilder = new URIBuilder(queryUri); + } catch (final URISyntaxException e) { + throw new SmartUriException("Unable to deserialize Smart URI", e); + } + final Map<URI, Value> map = new HashMap<>(); + final RyaURI subject = findSubject(uri.stringValue()); + + final List<NameValuePair> parameters = uriBuilder.getQueryParams(); + Map<RyaURI, String> entityTypeMap = new LinkedHashMap<>(); + Map<String, RyaURI> invertedEntityTypeMap = new LinkedHashMap<>(); + final Map<RyaURI, Map<URI, Value>> fullMap = new LinkedHashMap<>(); + for (final NameValuePair pair : parameters) { + final String keyString = pair.getName(); + final String valueString = pair.getValue(); + + final URI keyUri = new URIImpl(prefix + keyString); + final String decoded; + try { + decoded = URLDecoder.decode(valueString, Charsets.UTF_8.name()); + } catch (final UnsupportedEncodingException e) { + throw new SmartUriException("", e); + } + final URI type = TypeDeterminer.determineType(decoded); + if (type == XMLSchema.ANYURI) { + if (keyString.equals(RYA_TYPES_URI.getLocalName())) { + entityTypeMap = convertUriToTypeMap(new URIImpl(decoded)); + invertedEntityTypeMap = HashBiMap.create(entityTypeMap).inverse(); + } + } else { + final int keyPrefixLocation = keyString.indexOf("."); + final String keyPrefix = keyString.substring(0, keyPrefixLocation); + final RyaURI keyCorrespondingType = invertedEntityTypeMap.get(keyPrefix); + final String keyName = keyString.substring(keyPrefixLocation + 1, keyString.length()); + final RyaType ryaType = new RyaType(type, valueString); + + final Value value = RyaToRdfConversions.convertValue(ryaType); + + final String formattedKeyUriString = removeTypePrefixFromUri(keyUri.stringValue(), keyPrefix); + final URI formattedKeyUri = new URIImpl(formattedKeyUriString); + + map.put(formattedKeyUri, value); + } + } + return map; + } + +// public static Map<URI, Value> deserializeUri(final URI uri) throws SmartUriException { +// final String uriString = uri.stringValue(); +// final int fragmentPosition = uriString.indexOf("#"); +// String prefix = uriString.substring(0, fragmentPosition + 1); +// if (fragmentPosition == -1) { +// prefix = uriString.split("\\?", 2)[0]; +// } +// final String fragment = uriString.substring(fragmentPosition + 1, uriString.length()); +// java.net.URI queryUri; +// +// URIBuilder uriBuilder = null; +// try { +// if (fragmentPosition > -1) { +// queryUri = new java.net.URI("urn://" + fragment); +// } else { +// queryUri = new java.net.URI(uriString); +// } +// uriBuilder = new URIBuilder(queryUri); +// } catch (final URISyntaxException e) { +// throw new SmartUriException("Unable to deserialize Smart URI", e); +// } +// final Map<URI, Value> map = new HashMap<>(); +// final List<NameValuePair> parameters = uriBuilder.getQueryParams(); +// Map<RyaURI, String> entityTypeMap = new LinkedHashMap<>(); +// for (final NameValuePair pair : parameters) { +// final String keyString = pair.getName(); +// final String valueString = pair.getValue(); +// +// final URI keyUri = new URIImpl(prefix + keyString); +// final URI type = TypeDeterminer.determineType(valueString); +// if (type == XMLSchema.ANYURI) { +// final String decoded; +// try { +// decoded = URLDecoder.decode(valueString, Charsets.UTF_8.name()); +// } catch (final UnsupportedEncodingException e) { +// throw new SmartUriException("", e); +// } +// entityTypeMap = convertUriToTypeMap(new URIImpl(decoded)); +// } else { +// final RyaType ryaType = new RyaType(type, valueString); +// +// final Value value = RyaToRdfConversions.convertValue(ryaType); +// +// map.put(keyUri, value); +// } +// } +// return map; +// } + + public static Entity deserializeUriEntity(final URI uri) throws SmartUriException { + final String uriString = uri.stringValue(); + final int fragmentPosition = uriString.indexOf("#"); + String prefix = uriString.substring(0, fragmentPosition + 1); + if (fragmentPosition == -1) { + prefix = uriString.split("\\?", 2)[0]; + } + final String fragment = uriString.substring(fragmentPosition + 1, uriString.length()); + java.net.URI queryUri; + + URIBuilder uriBuilder = null; + try { + if (fragmentPosition > -1) { + queryUri = new java.net.URI("urn://" + fragment); + } else { + queryUri = new java.net.URI(uriString); + } + uriBuilder = new URIBuilder(queryUri); + } catch (final URISyntaxException e) { + throw new SmartUriException("Unable to deserialize Smart URI", e); + } + + final RyaURI subject = findSubject(uri.stringValue()); + + final List<NameValuePair> parameters = uriBuilder.getQueryParams(); + Map<RyaURI, String> entityTypeMap = new LinkedHashMap<>(); + Map<String, RyaURI> invertedEntityTypeMap = new LinkedHashMap<>(); + final Map<RyaURI, Map<URI, Value>> fullMap = new LinkedHashMap<>(); + for (final NameValuePair pair : parameters) { + final String keyString = pair.getName(); + final String valueString = pair.getValue(); + + final URI keyUri = new URIImpl(prefix + keyString); + final String decoded; + try { + decoded = URLDecoder.decode(valueString, Charsets.UTF_8.name()); + } catch (final UnsupportedEncodingException e) { + throw new SmartUriException("", e); + } + final URI type = TypeDeterminer.determineType(decoded); + if (type == XMLSchema.ANYURI) { + if (keyString.equals(RYA_TYPES_URI.getLocalName())) { + entityTypeMap = convertUriToTypeMap(new URIImpl(decoded)); + invertedEntityTypeMap = HashBiMap.create(entityTypeMap).inverse(); + } + } else { + final int keyPrefixLocation = keyString.indexOf("."); + final String keyPrefix = keyString.substring(0, keyPrefixLocation); + final RyaURI keyCorrespondingType = invertedEntityTypeMap.get(keyPrefix); + final String keyName = keyString.substring(keyPrefixLocation + 1, keyString.length()); + final RyaType ryaType = new RyaType(type, valueString); + + final Value value = RyaToRdfConversions.convertValue(ryaType); + + final String formattedKeyUriString = removeTypePrefixFromUri(keyUri.stringValue(), keyPrefix); + final URI formattedKeyUri = new URIImpl(formattedKeyUriString); + final Map<URI, Value> map = fullMap.get(keyCorrespondingType); + + if (map == null) { + final Map<URI, Value> subMap = new HashMap<>(); + subMap.put(formattedKeyUri, value); + fullMap.put(keyCorrespondingType, subMap); + } else { + map.put(formattedKeyUri, value); + fullMap.put(keyCorrespondingType, map); + } + } + } + final Entity entity = convertMapToEntity(subject, fullMap); + return entity; + } + + private static final class TypeDeterminer { + /** + * Private constructor to prevent instantiation. + */ + private TypeDeterminer() { + } + + private static URI determineType(final String data) { + if (Ints.tryParse(data) != null) { + return XMLSchema.INTEGER; + } else if (Doubles.tryParse(data) != null) { + return XMLSchema.DOUBLE; + } else if (Floats.tryParse(data) != null) { + return XMLSchema.FLOAT; + } else if (isShort(data)) { + return XMLSchema.SHORT; + } else if (Longs.tryParse(data) != null) { + return XMLSchema.LONG; + } if (Boolean.parseBoolean(data)) { + return XMLSchema.BOOLEAN; + } else if (isByte(data)) { + return XMLSchema.BYTE; + } else if (isDate(data)) { + return XMLSchema.DATETIME; + } else if (isUri(data)) { + return XMLSchema.ANYURI; + } + + return XMLSchema.STRING; + } + + private static boolean isDate(final String data) { + try { + DateTime.parse(data, ISODateTimeFormat.dateTimeParser()); + return true; + } catch (final IllegalArgumentException e) { + // not a date + return false; + } + } + + private static boolean isShort(final String data) { + try { + Short.parseShort(data); + return true; + } catch (final NumberFormatException e) { + // not a short + return false; + } + } + + private static boolean isByte(final String data) { + try { + Byte.parseByte(data); + return true; + } catch (final NumberFormatException e) { + // not a byte + return false; + } + } + + private static boolean isUri(final String data) { + try { + final String decoded = URLDecoder.decode(data, Charsets.UTF_8.name()); + new URIImpl(decoded); + return true; + } catch (final IllegalArgumentException | UnsupportedEncodingException e) { + // not a URI + return false; + } + } + } + + public static Map<URI, Value> entityToValueMap(final Entity entity) { + final Map<URI, Value> map = new LinkedHashMap<>(); + for (final Entry<RyaURI, ImmutableMap<RyaURI, Property>> entry : entity.getProperties().entrySet()) { + for (final Entry<RyaURI, Property> property : entry.getValue().entrySet()) { + final RyaURI propertyKey = property.getKey(); + final URI uri = new URIImpl(propertyKey.getData()); + final Property propertyValue = property.getValue(); + final Value value = RyaToRdfConversions.convertValue(propertyValue.getValue()); + map.put(uri, value); + } + } + return map; + } + + /** + * Converts a {@link Map} of {@link URI}/{@link Value}s to a {@link Set} of + * {@link Property}s. + * @param map the {@link Map} of {@link URI}/{@link Value}. + * @return the {@link Set} of {@link Property}s. + */ + public static Set<Property> mapToProperties(final Map<URI, Value> map) { + final Set<Property> properties = new LinkedHashSet<>(); + for (final Entry<URI, Value> entry : map.entrySet()) { + final URI uri = entry.getKey(); + final Value value = entry.getValue(); + + final RyaURI ryaUri = new RyaURI(uri.stringValue()); + final RyaType ryaType = RdfToRyaConversions.convertValue(value); + + final Property property = new Property(ryaUri, ryaType); + properties.add(property); + } + return properties; + } + + public static Map<URI, Value> propertiesToMap(final Set<Property> properties) { + final Map<URI, Value> map = new LinkedHashMap<>(); + for (final Property property : properties) { + final URI uri = new URIImpl(property.getName().getData()); + final Value value = RyaToRdfConversions.convertValue(property.getValue()); + map.put(uri, value); + } + return map; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriException.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriException.java b/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriException.java new file mode 100644 index 0000000..979b6b9 --- /dev/null +++ b/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriException.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.rya.indexing.smarturi; + +/** + * Exception thrown when a SmartURI is malformed when attempting to + * serialize/deserialize it. + */ +public class SmartUriException extends Exception { + private static final long serialVersionUID = 1L; + + /** + * Creates a new instance of {@link SmartUriException}. + * @param message the message to be displayed by the exception. + * @param throwable the source {#link Throwable} cause of the exception. + */ + public SmartUriException(final String message, final Throwable throwable) { + super(message, throwable); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriStorage.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriStorage.java b/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriStorage.java new file mode 100644 index 0000000..1043d42 --- /dev/null +++ b/extras/indexing/src/main/java/org/apache/rya/indexing/smarturi/SmartUriStorage.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.rya.indexing.smarturi; + +import java.util.Map; + +import org.apache.rya.api.domain.RyaURI; +import org.apache.rya.indexing.entity.model.Entity; +import org.apache.rya.indexing.entity.model.Type; +import org.apache.rya.indexing.entity.model.TypedEntity; +import org.apache.rya.indexing.entity.storage.mongo.ConvertingCursor; +import org.calrissian.mango.collect.CloseableIterator; +import org.openrdf.model.URI; +import org.openrdf.model.Value; + +/** + * Interface for interacting with a Smart URI's datastore. + */ +public interface SmartUriStorage { + /** + * Stores the map into the datastore. + * @param subject the {@link RyaURI} subject of the Entity. Identifies the + * thing that is being represented as an Entity. + * @param map the {@link Map} of {@link URI}s to {@link Value}s. + * @throws SmartUriException + */ + public void storeEntity(final RyaURI subject, final Map<URI, Value> map) throws SmartUriException; + + /** + * Stores the entity into the datastore. + * @param entity the {@link Entity}. + * @throws SmartUriException + */ + public void storeEntity(final Entity entity) throws SmartUriException; + + /** + * Updates the entity. + * @param oldEntity the old {@link Entity} to update + * @param updatedEntity the new {@link Entity} to replace the old one with. + * @throws SmartUriException + */ + public void updateEntity(final Entity oldEntity, final Entity updatedEntity) throws SmartUriException; + + /** + * Queries for the entity based on the subject + * @param subject the {@link RyaURI} subject of the Entity. Identifies the + * thing that is being represented as an Entity. + * @return the {@link Entity} matching the subject. + * @throws SmartUriException + */ + public Entity queryEntity(final RyaURI subject) throws SmartUriException; + + /** + * Queries the datastore for the map. + * @param type the type associated with the entity values. + * @param map the {@link Map} of {@link URI}s to {@link Value}s. + * @return a {@link CloseableIterator} over the {@link TypedEntity}s that + * match the search parameters. + * @throws SmartUriException + */ + public ConvertingCursor<TypedEntity> queryEntity(final Type type, final Map<URI, Value> map) throws SmartUriException; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexing/src/test/java/org/apache/rya/indexing/mongo/MongoDbSmartUriTest.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/test/java/org/apache/rya/indexing/mongo/MongoDbSmartUriTest.java b/extras/indexing/src/test/java/org/apache/rya/indexing/mongo/MongoDbSmartUriTest.java new file mode 100644 index 0000000..60efbed --- /dev/null +++ b/extras/indexing/src/test/java/org/apache/rya/indexing/mongo/MongoDbSmartUriTest.java @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.rya.indexing.mongo; + +import static org.apache.rya.api.domain.RyaTypeUtils.booleanRyaType; +import static org.apache.rya.api.domain.RyaTypeUtils.byteRyaType; +import static org.apache.rya.api.domain.RyaTypeUtils.dateRyaType; +import static org.apache.rya.api.domain.RyaTypeUtils.doubleRyaType; +import static org.apache.rya.api.domain.RyaTypeUtils.floatRyaType; +import static org.apache.rya.api.domain.RyaTypeUtils.intRyaType; +import static org.apache.rya.api.domain.RyaTypeUtils.longRyaType; +import static org.apache.rya.api.domain.RyaTypeUtils.shortRyaType; +import static org.apache.rya.api.domain.RyaTypeUtils.stringRyaType; +import static org.apache.rya.api.domain.RyaTypeUtils.uriRyaType; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.net.URISyntaxException; +import java.util.Date; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.rya.api.domain.RyaSchema; +import org.apache.rya.api.domain.RyaURI; +import org.apache.rya.api.resolver.RdfToRyaConversions; +import org.apache.rya.api.resolver.RyaToRdfConversions; +import org.apache.rya.indexing.accumulo.ConfigUtils; +import org.apache.rya.indexing.entity.model.Entity; +import org.apache.rya.indexing.entity.model.Property; +import org.apache.rya.indexing.entity.model.Type; +import org.apache.rya.indexing.entity.model.TypedEntity; +import org.apache.rya.indexing.entity.query.EntityQueryNode; +import org.apache.rya.indexing.entity.storage.mongo.ConvertingCursor; +import org.apache.rya.indexing.mongodb.MongoDbSmartUri; +import org.apache.rya.indexing.smarturi.SmartUriAdapter; +import org.apache.rya.indexing.smarturi.SmartUriException; +import org.apache.rya.mongodb.MockMongoFactory; +import org.apache.rya.mongodb.MongoDBRdfConfiguration; +import org.joda.time.DateTime; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.openrdf.model.URI; +import org.openrdf.model.Value; +import org.openrdf.model.ValueFactory; +import org.openrdf.model.impl.URIImpl; +import org.openrdf.model.impl.ValueFactoryImpl; +import org.openrdf.model.vocabulary.RDF; +import org.openrdf.query.BindingSet; +import org.openrdf.query.MalformedQueryException; +import org.openrdf.query.QueryEvaluationException; +import org.openrdf.query.algebra.StatementPattern; +import org.openrdf.query.algebra.evaluation.QueryBindingSet; +import org.openrdf.query.algebra.helpers.StatementPatternCollector; +import org.openrdf.query.parser.sparql.SPARQLParser; + +import com.beust.jcommander.internal.Lists; +import com.google.common.collect.ImmutableSet; +import com.mongodb.MongoClient; + +import info.aduna.iteration.CloseableIteration; + +/** + * Tests for MongoDB based Smart URI. + */ +public class MongoDbSmartUriTest { + private static final String NAMESPACE = RyaSchema.NAMESPACE; + private static final ValueFactory VALUE_FACTORY = ValueFactoryImpl.getInstance(); + + // People + private static final RyaURI BOB = createRyaUri("Bob"); + + // Attributes + private static final RyaURI HAS_WEIGHT = createRyaUri("hasWeight"); + private static final RyaURI HAS_HEIGHT = createRyaUri("hasHeight"); + private static final RyaURI HAS_SSN = createRyaUri("hasSSN"); + private static final RyaURI HAS_AGE = createRyaUri("hasAge"); + private static final RyaURI HAS_INCOME = createRyaUri("hasIncome"); + private static final RyaURI HAS_NUMBER_OF_CHILDREN = createRyaUri("hasNumberOfChildren"); + private static final RyaURI HAS_LICENSE_NUMBER = createRyaUri("hasLicenseNumber"); + private static final RyaURI HAS_EYE_COLOR = createRyaUri("hasEyeColor"); + private static final RyaURI HAS_HAIR_COLOR = createRyaUri("hasHairColor"); + private static final RyaURI HAS_DATE_OF_BIRTH = createRyaUri("hasDateOfBirth"); + private static final RyaURI HAS_EXPIRATION_DATE = createRyaUri("hasExpirationDate"); + private static final RyaURI HAS_GLASSES = createRyaUri("hasGlasses"); + private static final RyaURI HAS_EMAIL_ADDRESS = createRyaUri("hasEmailAddress"); + private static final RyaURI HAS_ATTRIBUTE_SPACE = createRyaUri("has Attribute Space"); + private static final RyaURI HAS_MOTTO = createRyaUri("hasMotto"); + private static final RyaURI HAS_BLOOD_TYPE = createRyaUri("hasBloodType"); + private static final RyaURI HAS_SEX = createRyaUri("hasSex"); + private static final RyaURI HAS_ADDRESS = createRyaUri("hasAddress"); + private static final RyaURI HAS_POSITION_TITLE = createRyaUri("hasPositionTitle"); + private static final RyaURI HAS_WORK_ADDRESS = createRyaUri("hasWorkAddress"); + private static final RyaURI HAS_EXTENSION = createRyaUri("hasExtension"); + private static final RyaURI HAS_OFFICE_ROOM_NUMBER = createRyaUri("hasOfficeRoomNumber"); + + // Type URIs + private static final RyaURI PERSON_TYPE_URI = new RyaURI("urn:example/person"); + private static final RyaURI EMPLOYEE_TYPE_URI = new RyaURI("urn:example/employee"); + + // Entities + private static final Entity BOB_ENTITY = createBobEntity(); + + // Types + private static final Type PERSON_TYPE = createPersonType(); + private static final Type EMPLOYEE_TYPE = createEmployeeType(); + + private static MongoDBRdfConfiguration conf; + private static MongoDbSmartUri smartUriConverter; + + @BeforeClass + public static void setupClass() throws Exception { + conf = new MongoDBRdfConfiguration(); + conf.set(ConfigUtils.USE_MONGO, "true"); + conf.set(MongoDBRdfConfiguration.MONGO_DB_NAME, "test"); + conf.set(MongoDBRdfConfiguration.MONGO_COLLECTION_PREFIX, "rya_"); + conf.setTablePrefix("another_"); + } + + @Before + public void setupTest() throws Exception { + final MongoClient client = MockMongoFactory.newFactory().newMongoClient(); + conf.setMongoClient(client); + + if (smartUriConverter != null) { + smartUriConverter.shutdown(); + } + smartUriConverter = new MongoDbSmartUri(conf); + } + + /** + * Creates a {@link RyaURI} for the specified local name. + * @param localName the URI's local name. + * @return the {@link RyraURI}. + */ + private static RyaURI createRyaUri(final String localName) { + return createRyaUri(NAMESPACE, localName); + //return new RyaURI("http://" + localName); + } + + /** + * Creates a {@link RyaURI} for the specified local name. + * @param namespace the namespace. + * @param localName the URI's local name. + * @return the {@link RyraURI}. + */ + private static RyaURI createRyaUri(final String namespace, final String localName) { + return RdfToRyaConversions.convertURI(VALUE_FACTORY.createURI(namespace, localName)); + } + + private static Entity createBobEntity() { + final Entity bobEntity = Entity.builder() + .setSubject(BOB) + .setExplicitType(PERSON_TYPE_URI) + .setExplicitType(EMPLOYEE_TYPE_URI) + .setProperty(PERSON_TYPE_URI, new Property(HAS_WEIGHT, floatRyaType(250.75f))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_HEIGHT, doubleRyaType(72.5))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_SSN, stringRyaType("123-45-6789"))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_AGE, shortRyaType((short) 40))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_INCOME, intRyaType(50000))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_NUMBER_OF_CHILDREN, byteRyaType((byte) 2))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_LICENSE_NUMBER, longRyaType(123456789012L))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_EYE_COLOR, stringRyaType("blue"))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_HAIR_COLOR, stringRyaType("brown"))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_DATE_OF_BIRTH, dateRyaType(new DateTime().minusYears(40)))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_EXPIRATION_DATE, dateRyaType(new Date()))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_GLASSES, booleanRyaType(true))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_EMAIL_ADDRESS, uriRyaType(new URIImpl("mailto:[email protected]")))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_ATTRIBUTE_SPACE, stringRyaType("attribute space"))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_MOTTO, stringRyaType("!@#*\\&%20^ smörgåsbord"))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_BLOOD_TYPE, stringRyaType("A+ blood type"))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_SEX, stringRyaType("M"))) + .setProperty(PERSON_TYPE_URI, new Property(HAS_ADDRESS, stringRyaType("123 Fake St. Washington, DC 20024"))) + .setProperty(EMPLOYEE_TYPE_URI, new Property(HAS_POSITION_TITLE, stringRyaType("Assistant to the Regional Manager"))) + .setProperty(EMPLOYEE_TYPE_URI, new Property(HAS_WORK_ADDRESS, stringRyaType("987 Fake Office Rd. Washington, DC 20024"))) + .setProperty(EMPLOYEE_TYPE_URI, new Property(HAS_EXTENSION, shortRyaType((short) 555))) + .setProperty(EMPLOYEE_TYPE_URI, new Property(HAS_OFFICE_ROOM_NUMBER, shortRyaType((short) 9999))) + .build(); + + return bobEntity; + } + + private static Type createPersonType() { + final Type personType = new Type(PERSON_TYPE_URI, + ImmutableSet.<RyaURI>builder() + .add(HAS_WEIGHT) + .add(HAS_HEIGHT) + .add(HAS_SSN) + .add(HAS_AGE) + .add(HAS_INCOME) + .add(HAS_NUMBER_OF_CHILDREN) + .add(HAS_LICENSE_NUMBER) + .add(HAS_EYE_COLOR) + .add(HAS_HAIR_COLOR) + .add(HAS_DATE_OF_BIRTH) + .add(HAS_EXPIRATION_DATE) + .add(HAS_GLASSES) + .add(HAS_EMAIL_ADDRESS) + .add(HAS_ATTRIBUTE_SPACE) + .add(HAS_MOTTO) + .add(HAS_BLOOD_TYPE) + .add(HAS_SEX) + .add(HAS_ADDRESS) + .build()); + return personType; + } + + private static Type createEmployeeType() { + final Type employeeType = new Type(EMPLOYEE_TYPE_URI, + ImmutableSet.<RyaURI>builder() + .add(HAS_POSITION_TITLE) + .add(HAS_WORK_ADDRESS) + .add(HAS_EXTENSION) + .add(HAS_OFFICE_ROOM_NUMBER) + .build()); + return employeeType; + } + + private static String getRyaUriLocalName(final RyaURI ryaUri) { + return new URIImpl(ryaUri.getData()).getLocalName(); + } + + @Test + public void testSerializeDeserialize() throws SmartUriException, URISyntaxException { + final URI smartUri = SmartUriAdapter.serializeUriEntity(BOB_ENTITY); + System.out.println(smartUri); + final Entity resultEntity = SmartUriAdapter.deserializeUriEntity(smartUri); + System.out.println(resultEntity); + assertEquals(BOB_ENTITY.getSubject(), resultEntity.getSubject()); + //assertTrue(Paths.get(BOB_ENTITY.getSubject().getData()).equals(Paths.get(resultEntity.getSubject().getData()))); + } + + @Test + public void testStorage() throws SmartUriException, MalformedQueryException, RuntimeException, QueryEvaluationException { + smartUriConverter.storeEntity(BOB_ENTITY); + + final String sparql = "SELECT * WHERE { " + + "<" + BOB.getData() + "> <" + RDF.TYPE + "> <" + PERSON_TYPE.getId().getData() + "> . " + + "<" + BOB.getData() + "> <" + HAS_SSN.getData() + "> ?ssn . " + + "<" + BOB.getData() + "> <" + HAS_AGE.getData() + "> ?age . " + + "<" + BOB.getData() + "> <" + HAS_WEIGHT.getData() + "> ?weight . " + + "<" + BOB.getData() + "> <" + HAS_ADDRESS.getData() + "> ?address . " + + "}"; + + final StatementPatternCollector spCollector = new StatementPatternCollector(); + new SPARQLParser().parseQuery(sparql, null).getTupleExpr().visit(spCollector); + final List<StatementPattern> patterns = spCollector.getStatementPatterns(); + final EntityQueryNode entityQueryNode = new EntityQueryNode(PERSON_TYPE, patterns, smartUriConverter.getEntityStorage()); + final QueryBindingSet queryBindingSet = new QueryBindingSet(); + final Property ssnProperty = BOB_ENTITY.lookupTypeProperty(PERSON_TYPE, HAS_SSN).get(); + queryBindingSet.addBinding(HAS_SSN.getData(), RyaToRdfConversions.convertValue(ssnProperty.getValue())); + + final CloseableIteration<BindingSet, QueryEvaluationException> iter = entityQueryNode.evaluate(queryBindingSet); + int count = 0; + // These should match what was used in the SPARQL query. + final List<String> queryParamNames = Lists.newArrayList("ssn", "age", "weight", "address"); + while (iter.hasNext()) { + final BindingSet bs = iter.next(); + assertTrue(bs.getBindingNames().containsAll(queryParamNames)); + count++; + } + assertEquals(count, 1); + } + + @Test + public void testUpdate() throws SmartUriException { + smartUriConverter.storeEntity(BOB_ENTITY); + + // New properties to add + final RyaURI hasNickName = createRyaUri("hasNickName"); + final RyaURI hasWindowOffice = createRyaUri("hasWindowOffice"); + + final Entity.Builder builder = Entity.builder(BOB_ENTITY); + builder.setProperty(PERSON_TYPE_URI, new Property(HAS_AGE, shortRyaType((short) 41))); + builder.setProperty(PERSON_TYPE_URI, new Property(hasNickName, stringRyaType("Bobby"))); + builder.setProperty(EMPLOYEE_TYPE_URI, new Property(HAS_POSITION_TITLE, stringRyaType("Assistant Regional Manager"))); + builder.setProperty(EMPLOYEE_TYPE_URI, new Property(hasWindowOffice, booleanRyaType(true))); + builder.setVersion(BOB_ENTITY.getVersion() + 1); + builder.rebuildSmartUri(); + + final Entity newBobEntity = builder.build(); + + smartUriConverter.updateEntity(BOB_ENTITY, newBobEntity); + + final Entity resultEntity = smartUriConverter.queryEntity(BOB_ENTITY.getSubject()); + assertEquals(newBobEntity.getVersion(), resultEntity.getVersion()); + assertEquals(newBobEntity.lookupTypeProperty(PERSON_TYPE, HAS_AGE), resultEntity.lookupTypeProperty(PERSON_TYPE, HAS_AGE)); + assertEquals(newBobEntity.lookupTypeProperty(PERSON_TYPE, hasNickName), resultEntity.lookupTypeProperty(PERSON_TYPE, hasNickName)); + assertEquals(newBobEntity.lookupTypeProperty(EMPLOYEE_TYPE, HAS_POSITION_TITLE), resultEntity.lookupTypeProperty(EMPLOYEE_TYPE, HAS_POSITION_TITLE)); + assertEquals(newBobEntity.lookupTypeProperty(EMPLOYEE_TYPE, hasWindowOffice), resultEntity.lookupTypeProperty(EMPLOYEE_TYPE, hasWindowOffice)); + assertEquals(newBobEntity.getSmartUri(), resultEntity.getSmartUri()); + final String resultUriString = resultEntity.getSmartUri().stringValue(); + assertTrue(resultUriString.contains(getRyaUriLocalName(hasWindowOffice))); + assertTrue(resultUriString.contains(getRyaUriLocalName(hasNickName))); + } + + @Test + public void testQuery() throws SmartUriException { + smartUriConverter.storeEntity(BOB_ENTITY); + + // Look up Person Type Entities that match Bob's SSN property + final Set<Property> properties = new LinkedHashSet<>(); + properties.add(BOB_ENTITY.lookupTypeProperty(PERSON_TYPE, HAS_SSN).get()); + final Map<URI, Value> map = SmartUriAdapter.propertiesToMap(properties); + + final ConvertingCursor<TypedEntity> cursor = smartUriConverter.queryEntity(PERSON_TYPE, map); + int count = 0; + while (cursor.hasNext()) { + final TypedEntity typedEntity = cursor.next(); + System.out.println(typedEntity); + count++; + } + assertEquals(count, 1); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/indexingExample/pom.xml ---------------------------------------------------------------------- diff --git a/extras/indexingExample/pom.xml b/extras/indexingExample/pom.xml index 5304884..50c9a29 100644 --- a/extras/indexingExample/pom.xml +++ b/extras/indexingExample/pom.xml @@ -78,6 +78,12 @@ under the License. <artifactId>sesame-queryrender</artifactId> <version>${openrdf.sesame.version}</version> </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> </dependencies> <build> http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/2e71ff2a/extras/rya.merger/pom.xml ---------------------------------------------------------------------- diff --git a/extras/rya.merger/pom.xml b/extras/rya.merger/pom.xml index 62b799e..44f65ef 100644 --- a/extras/rya.merger/pom.xml +++ b/extras/rya.merger/pom.xml @@ -133,11 +133,6 @@ under the License. </dependency> <dependency> - <groupId>commons-httpclient</groupId> - <artifactId>commons-httpclient</artifactId> - </dependency> - - <dependency> <groupId>com.beust</groupId> <artifactId>jcommander</artifactId> </dependency>
