| /* |
| * 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.model; |
| |
| import static java.util.Objects.requireNonNull; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| 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.RyaIRI; |
| import org.apache.rya.indexing.entity.storage.EntityStorage; |
| import org.apache.rya.indexing.smarturi.SmartUriAdapter; |
| import org.apache.rya.indexing.smarturi.SmartUriException; |
| import org.eclipse.rdf4j.model.IRI; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Maps; |
| |
| import edu.umd.cs.findbugs.annotations.DefaultAnnotation; |
| import edu.umd.cs.findbugs.annotations.NonNull; |
| import edu.umd.cs.findbugs.annotations.Nullable; |
| |
| /** |
| * An {@link Entity} is a named concept that has at least one defined structure |
| * and a bunch of values that fit within each of those structures. A structure is |
| * defined by a {@link Type}. A value that fits within that Type is a {@link Property}. |
| * </p> |
| * For example, suppose we want to represent a type of icecream as an Entity. |
| * First we must define what properties an icecream entity may have: |
| * <pre> |
| * Type ID: <urn:icecream> |
| * Properties: <urn:brand> |
| * <urn:flavor> |
| * <urn:ingredients> |
| * <urn:nutritionalInformation> |
| * </pre> |
| * Now we can represent our icecream whose brand is "Awesome Icecream" and whose |
| * flavor is "Chocolate", but has no ingredients or nutritional information, as |
| * an Entity by doing the following: |
| * <pre> |
| * final Entity entity = Entity.builder() |
| * .setSubject(new RyaIRI("urn:GTIN-14/00012345600012")) |
| * .setExplicitType(new RyaIRI("urn:icecream")) |
| * .setProperty(new RyaIRI("urn:icecream"), new Property(new RyaIRI("urn:brand"), new RyaType(XMLSchema.STRING, "Awesome Icecream"))) |
| * .setProperty(new RyaIRI("urn:icecream"), new Property(new RyaIRI("urn:flavor"), new RyaType(XMLSchema.STRING, "Chocolate"))) |
| * .build(); |
| * </pre> |
| * The two types of Entities that may be created are implicit and explicit. |
| * An implicit Entity is one who has at least one {@link Property} that matches |
| * the {@link Type}, but nothing has explicitly indicated it is of that Type. |
| * Once something has done so, it is an explicitly typed Entity. |
| */ |
| @Immutable |
| @DefaultAnnotation(NonNull.class) |
| public class Entity { |
| private static final Logger log = Logger.getLogger(Entity.class); |
| |
| private final RyaIRI subject; |
| private final ImmutableList<RyaIRI> explicitTypeIds; |
| |
| // First key is Type ID. |
| // Second key is Property Name. |
| // Value is the Property value for a specific type. |
| private final ImmutableMap<RyaIRI, ImmutableMap<RyaIRI, Property>> properties; |
| |
| private final int version; |
| |
| private IRI smartUri = null; |
| |
| /** |
| * 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 IRI} representation of this |
| * {@link Entity}. |
| */ |
| private Entity( |
| final RyaIRI subject, |
| final ImmutableList<RyaIRI> explicitTypeIds, |
| final ImmutableMap<RyaIRI, ImmutableMap<RyaIRI, Property>> typeProperties, |
| final int version, |
| final IRI 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 RyaIRI subject, |
| final ImmutableList<RyaIRI> explicitTypeIds, |
| final ImmutableMap<RyaIRI, ImmutableMap<RyaIRI, Property>> typeProperties, |
| final int version) { |
| this(subject, explicitTypeIds, typeProperties, version, null); |
| } |
| |
| /** |
| * @return Identifies the thing that is being represented as an Entity. |
| */ |
| public RyaIRI getSubject() { |
| return subject; |
| } |
| |
| /** |
| * @return {@link Type}s that have been explicitly applied to the {@link Entity}. |
| */ |
| public ImmutableList<RyaIRI> getExplicitTypeIds() { |
| return explicitTypeIds; |
| } |
| |
| /** |
| * @return All {@link Property}s that have been set for the Entity, grouped by Type ID. |
| */ |
| public ImmutableMap<RyaIRI, ImmutableMap<RyaIRI, Property>> getProperties() { |
| return properties; |
| } |
| |
| /** |
| * @return The version of this Entity. This value is used by the {@link EntityStorage} |
| * to prevent stale updates. |
| */ |
| public int getVersion() { |
| return version; |
| } |
| |
| /** |
| * @return the Smart {@link IRI} representation of this {@link Entity}. |
| */ |
| public IRI getSmartUri() { |
| return smartUri; |
| } |
| |
| /** |
| * Does a lookup to see if the {@link Entity} contains the specified |
| * property for the specified type. |
| * @param type the {@link Type}. (not {@code null}) |
| * @param propertyRyaIri the property {@link RyaIRI}. (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 RyaIRI propertyRyaIri) { |
| requireNonNull(type); |
| return lookupTypeProperty(type.getId(), propertyRyaIri); |
| } |
| |
| /** |
| * Does a lookup to see if the {@link Entity} contains the specified |
| * property for the specified type. |
| * @param typeRyaIri the type {@link RyaIRI}. (not {@code null}) |
| * @param propertyRyaIri the property {@link RyaIRI}. (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 RyaIRI typeRyaIri, final RyaIRI propertyRyaIri) { |
| requireNonNull(typeRyaIri); |
| requireNonNull(propertyRyaIri); |
| final ImmutableMap<RyaIRI, Property> typePropertyMap = properties.get(typeRyaIri); |
| Optional<Property> property = Optional.empty(); |
| if (typePropertyMap != null) { |
| property = Optional.ofNullable(typePropertyMap.get(propertyRyaIri)); |
| } |
| return property; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(subject, explicitTypeIds, properties, version); |
| } |
| |
| @Override |
| public boolean equals(final Object o) { |
| if(this == o) { |
| return true; |
| } |
| if(o instanceof Entity) { |
| final Entity entity = (Entity) o; |
| return Objects.equals(subject, entity.subject) && |
| Objects.equals(explicitTypeIds, entity.explicitTypeIds) && |
| Objects.equals(properties, entity.properties) && |
| version == entity.version; |
| } |
| return false; |
| } |
| |
| /** |
| * Builds an {@link TypedEntity} using this object's values for the specified {@link Type}. |
| * |
| * @param typeId - The ID of the Type the TypedEntity will be for. (not null) |
| * @return A TypedEntity using this object's values if any properties for the Type |
| * are present or if the Type was explicitly set. Otherwise an empty {@link Optional}. |
| */ |
| public Optional<TypedEntity> makeTypedEntity(final RyaIRI typeId) { |
| requireNonNull(typeId); |
| |
| final boolean explicitlyHasType = explicitTypeIds.contains(typeId); |
| final boolean hasTypesProperties = properties.containsKey(typeId); |
| |
| // The case where the MongoEntity can be represented as the typeId's Type. |
| if(explicitlyHasType || hasTypesProperties) { |
| // Set required fields. |
| final TypedEntity.Builder builder = TypedEntity.builder() |
| .setId( subject ) |
| .setTypeId( typeId ) |
| .setExplicitelyTyped( explicitTypeIds.contains(typeId) ); |
| |
| // Set Type's properties if present. |
| if(properties.containsKey(typeId)) { |
| properties.get(typeId).forEach( (propertyName, property) -> builder.setProperty(property)); |
| } |
| |
| return Optional.of( builder.build() ); |
| } |
| |
| // This MongoEntity can not be represented by the typeId's Type. |
| return Optional.empty(); |
| } |
| |
| /** |
| * @return An empty instance of {@link Builder}. |
| */ |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| /** |
| * Create a {@link Builder} initialized with an {@link Entity}'s values. |
| * |
| * @param entity - The Entity the builder will be based on. (not null) |
| * @return A {@link Builder} loaded with {@code entity}'s values. |
| */ |
| public static Builder builder(final Entity entity) { |
| return new Builder(entity); |
| } |
| |
| /** |
| * Builds instances of {@link Entity}. |
| */ |
| @DefaultAnnotation(NonNull.class) |
| public static class Builder { |
| |
| private RyaIRI subject = null; |
| private final List<RyaIRI> explicitTypes = new ArrayList<>(); |
| private final Map<RyaIRI, Map<RyaIRI, Property>> properties = new HashMap<>(); |
| private IRI smartUri = null; |
| |
| private int version = 0; |
| |
| /** |
| * Constructs an empty instance of {@link Builder}. |
| */ |
| public Builder() { } |
| |
| /** |
| * Constructs an instance of {@link Builder}. |
| * |
| * @param entity - The Entity the builder will be based on. (not null) |
| */ |
| public Builder(final Entity entity) { |
| requireNonNull(entity); |
| |
| subject = entity.getSubject(); |
| explicitTypes.addAll( entity.getExplicitTypeIds() ); |
| |
| for(final Entry<RyaIRI, ImmutableMap<RyaIRI, Property>> entry : entity.getProperties().entrySet()) { |
| properties.put(entry.getKey(), Maps.newHashMap(entry.getValue())); |
| } |
| |
| version = entity.getVersion(); |
| |
| smartUri = entity.getSmartUri(); |
| } |
| |
| /** |
| * @param subject - Identifies the {@link TypedEntity}. |
| * @return This {@link Builder} so that method invocations may be chained. |
| */ |
| public Builder setSubject(@Nullable final RyaIRI subject) { |
| this.subject = subject; |
| return this; |
| } |
| |
| /** |
| * @param typeId - A {@link Type} that has been explicity set for the {@link TypedEntity}. |
| * @return This {@link Builder} so that method invocations may be chained. |
| */ |
| public Builder setExplicitType(@Nullable final RyaIRI typeId) { |
| if(typeId != null) { |
| explicitTypes.add(typeId); |
| } |
| return this; |
| } |
| |
| /** |
| * Removed a Type ID from the set of explicit Type IDs. |
| * |
| * @param typeId - The Type ID to remove from the set of explicit types. |
| * @return This {@link Builder} so that method invocations may be chained. |
| */ |
| public Builder unsetExplicitType(@Nullable final RyaIRI typeId) { |
| if(typeId != null) { |
| explicitTypes.remove(typeId); |
| } |
| return this; |
| } |
| |
| /** |
| * Adds a {@link Property} for a specific {@link Type} of {@link TypedEntity}. |
| * |
| * @param typeId - The Type the Property is for. |
| * @param property - The Property values to add. |
| * @return This {@link Builder} so that method invocations may be chained. |
| */ |
| public Builder setProperty(@Nullable final RyaIRI typeId, @Nullable final Property property) { |
| if(typeId != null && property != null) { |
| if(!properties.containsKey(typeId)) { |
| properties.put(typeId, new HashMap<>()); |
| } |
| |
| properties.get(typeId).put(property.getName(), property); |
| } |
| return this; |
| } |
| |
| /** |
| * Removes a {@link Property} for a specific {@link Type} of {@link TypedEntity}. |
| * |
| * @param typeId - The Type the Property will be removed from. |
| * @param propertyName - The name of the Property to remove. |
| * @return This {@link Builder} so that method invocations may be chained. |
| */ |
| public Builder unsetProperty(@Nullable final RyaIRI typeId, @Nullable final RyaIRI propertyName) { |
| if(typeId != null && propertyName != null) { |
| if(properties.containsKey(typeId)) { |
| final Map<RyaIRI, Property> typedProperties = properties.get(typeId); |
| if(typedProperties.containsKey(propertyName)) { |
| typedProperties.remove(propertyName); |
| } |
| } |
| } |
| return this; |
| } |
| |
| /** |
| * @param smartUri - the Smart {@link IRI} representation of this |
| * {@link Entity}. |
| * @return This {@link Builder} so that method invocations may be chained. |
| */ |
| public Builder setSmartUri(final IRI 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. |
| */ |
| public Builder setVersion(final int version) { |
| this.version = version; |
| return this; |
| } |
| |
| /** |
| * @return Builds an instance of {@link Entity} using this builder's values. |
| */ |
| public Entity build() { |
| final ImmutableMap.Builder<RyaIRI, ImmutableMap<RyaIRI, Property>> propertiesBuilder = ImmutableMap.builder(); |
| for(final Entry<RyaIRI, Map<RyaIRI, Property>> entry : properties.entrySet()) { |
| propertiesBuilder.put(entry.getKey(), ImmutableMap.copyOf( entry.getValue() )); |
| } |
| |
| return new Entity(subject, |
| ImmutableList.copyOf( explicitTypes ), |
| propertiesBuilder.build(), |
| version, |
| smartUri); |
| } |
| } |
| } |