Java tutorial
/** * 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.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; 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 RyaURI("urn:GTIN-14/00012345600012")) * .setExplicitType(new RyaURI("urn:icecream")) * .setProperty(new RyaURI("urn:icecream"), new Property(new RyaURI("urn:brand"), new RyaType(XMLSchema.STRING, "Awesome Icecream"))) * .setProperty(new RyaURI("urn:icecream"), new Property(new RyaURI("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 RyaURI subject; private final ImmutableList<RyaURI> explicitTypeIds; // First key is Type ID. // Second key is Property Name. // Value is the Property value for a specific type. private final ImmutableMap<RyaURI, ImmutableMap<RyaURI, Property>> properties; private final int version; private URI 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 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 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); } /** * @return Identifies the thing that is being represented as an Entity. */ public RyaURI getSubject() { return subject; } /** * @return {@link Type}s that have been explicitly applied to the {@link Entity}. */ public ImmutableList<RyaURI> getExplicitTypeIds() { return explicitTypeIds; } /** * @return All {@link Property}s that have been set for the Entity, grouped by Type ID. */ public ImmutableMap<RyaURI, ImmutableMap<RyaURI, 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 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.ofNullable(typePropertyMap.get(propertyRyaUri)); } 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 RyaURI 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 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; /** * 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<RyaURI, ImmutableMap<RyaURI, 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 RyaURI 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 RyaURI 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 RyaURI 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 RyaURI 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 RyaURI typeId, @Nullable final RyaURI propertyName) { if (typeId != null && propertyName != null) { if (properties.containsKey(typeId)) { final Map<RyaURI, Property> typedProperties = properties.get(typeId); if (typedProperties.containsKey(propertyName)) { typedProperties.remove(propertyName); } } } return this; } /** * @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. */ 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<RyaURI, ImmutableMap<RyaURI, Property>> propertiesBuilder = ImmutableMap .builder(); for (final Entry<RyaURI, Map<RyaURI, Property>> entry : properties.entrySet()) { propertiesBuilder.put(entry.getKey(), ImmutableMap.copyOf(entry.getValue())); } return new Entity(subject, ImmutableList.copyOf(explicitTypes), propertiesBuilder.build(), version, smartUri); } } }