/*
* Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed 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.hawkular.inventory.api;
import java.io.InputStream;
import java.time.Instant;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.Set;
import java.util.stream.Collectors;
import org.hawkular.inventory.api.model.AbstractElement;
import org.hawkular.inventory.api.model.Blueprint;
import org.hawkular.inventory.api.model.DataEntity;
import org.hawkular.inventory.api.model.ElementVisitor;
import org.hawkular.inventory.api.model.Entity;
import org.hawkular.inventory.api.model.Environment;
import org.hawkular.inventory.api.model.Feed;
import org.hawkular.inventory.api.model.MetadataPack;
import org.hawkular.inventory.api.model.Metric;
import org.hawkular.inventory.api.model.MetricType;
import org.hawkular.inventory.api.model.OperationType;
import org.hawkular.inventory.api.model.Relationship;
import org.hawkular.inventory.api.model.Resource;
import org.hawkular.inventory.api.model.ResourceType;
import org.hawkular.inventory.api.model.Tenant;
import org.hawkular.inventory.api.paging.Order;
import org.hawkular.inventory.api.paging.Page;
import org.hawkular.inventory.api.paging.PageContext;
import org.hawkular.inventory.api.paging.Pager;
import org.hawkular.inventory.paths.CanonicalPath;
import org.hawkular.inventory.paths.DataRole;
import org.hawkular.inventory.paths.ElementTypeVisitor;
import org.hawkular.inventory.paths.Path;
import org.hawkular.inventory.paths.RelativePath;
import org.hawkular.inventory.paths.SegmentType;
/**
* Inventory stores "resources" which are groupings of measurements and other data. Inventory also stores metadata about
* the measurements and resources to give them meaning.
*
* <p>The resources are organized by tenant (your company) and environments (i.e. testing, development, staging,
* production, ...).
*
* <p>Despite their name, tenants are not completely separated and one can easily create relationships between them or
* between entities underneath different tenants. This is because there are situations where such relationships might
* make sense but more importantly because at the API level, inventory does not mandate any security model. It is
* assumed that the true multi-tenancy in the common sense of the word is implemented by a layer on top of the inventory
* API that also understands some security model to separate the tenants.
*
* <p>Resources are hierarchical - meaning that one can be a parent of others, recursively. One can also say that a
* resource can contain other resources. Resources can have other kinds of relationships that are not necessarily
* tree-like.
*
* <p>Resources can have a "resource type" (but they don't have to) which prescribes what kind of data a resource
* contains. Most prominently a resource can have a list of metrics and a resource type can define what those metrics
* should be by specifying the set of "metric types".
*
* <p>This interface offers a fluent API to compose a "traversal" over the graph of entities stored in the inventory in
* a strongly typed fashion.
*
* <p>The inventory implementations are not required to be thread-safe. Instances should therefore be accessed only by a
* single thread or serially.
*
* <p>Note to implementers:
*
* <p>It is highly recommended to extend the {@link org.hawkular.inventory.base.BaseInventory} and its SPI instead of
* this interface directly. The base is considered the "reference implementation" and any implementation is required to
* behave the same.
*
* <p>If you for any reason need to implement the full inventory interface, please consider the following:
*
* <p>The interfaces composing the inventory API are of 2 kinds:
* <ol>
* <li>CRUD interfaces that provide manipulation of the entities as well as the retrieval of the actual entity
* instances (various {@code Read}, {@code ReadWrite} or {@code ReadRelate} interfaces, e.g.
* {@link org.hawkular.inventory.api.Environments.ReadWrite}),
* <li>browse interfaces that offer further navigation methods to "hop" onto other entities somehow related to the
* one(s) in the current position in the inventory traversal. These interfaces are further divided into 2 groups:
* <ul>
* <li><b>{@code Single}</b> interfaces that provide methods for navigating from a single entity.
* These interfaces generally contain methods that enable modification of the entity or its relationships.
* See {@link org.hawkular.inventory.api.Environments.Single} for an example.
* <li><b>{@code Multiple}</b> interfaces that provide methods for navigating from multiple entities at once.
* These interfaces strictly offer only read-only access to the entities, because the semantics of what should
* be done when modifying or relating multiple entities at once is not uniformly defined on all types of
* entities and therefore would make the API more confusing than necessary. See
* {@link org.hawkular.inventory.api.Environments.Multiple} for example.
* </ul>
* </ol>
*
* @author Lukas Krejci
* @since 0.0.1
*/
public interface Inventory extends AutoCloseable, Tenants.Container<Tenants.ReadWrite> {
/**
* Defines the "view" on the inventory as it existed at given point in time.
*
* @param time the time the inventory existed at
* @return a new inventory instance set up to query data as it existed at the given time
*/
Inventory at(Instant time);
/**
* Initializes the inventory from the provided configuration object.
*
* @param configuration the configuration to use.
*/
void initialize(Configuration configuration);
/**
* This creates a new transaction frame that will be responsible for its own transaction handling.
* <p>
* Note that operations done directly on this inventory instance will be done in their own transactions outside
* of the returned frame. To do operations on inventory within that transaction frame, use {@link
* TransactionFrame#boundInventory()} method to obtain an inventory instance that will obey the transaction
* boundaries set by the frame.
* <p>
* Note also that identity-hashable entities created within the frame will not have their identity hashes computed
* (and other entities their identity hashes updated) until the transaction frame commits the transaction. Therefore
* if within the transaction frame you obtain an entity you created beforehand in the same frame, its identity hash
* will be null. If you depend on the identity hashes having their full value, you can either use
* {@link org.hawkular.inventory.api.model.IdentityHash#of(Entity, Inventory)} to obtain the new value (at an
* expense of additional backend queries and computations) or actually commit the transaction.
* <p>
* Note that it is not recommended to operate with more than 1 transaction frame in a single thread of execution.
* The behavior might differ depending on the storage backend for the inventory. Concurrent usage of more
* transaction frames is fine though (this is because of the weird "transaction-per-thread" policy in Tinkerpop
* and yes that means that one of the backend is leaking its requirements into the API, but I can't see a way
* around it).
*
* @return a new transaction frame
*/
TransactionFrame newTransactionFrame();
/**
* Entry point into the inventory. Select one ({@link org.hawkular.inventory.api.Tenants.ReadWrite#get(Object)}) or
* more ({@link org.hawkular.inventory.api.Tenants.ReadWrite#getAll(org.hawkular.inventory.api.filters.Filter...)})
* tenants and navigate further to the entities of interest.
*
* @return full CRUD and navigation interface to tenants
*/
Tenants.ReadWrite tenants();
/**
* Global access to all relationships. Use this with caution as it may result in scanning across the potentially
* very large number of relationships present in the inventory.
*
* <p>To create relationships, first navigate to one of its endpoint entities and create it from there using the
* API calls.
*
* @return the read access to relationships.
*/
Relationships.Read relationships();
/**
* Provides an access interface for inspecting given tenant.
*
* @param tenant the tenant to steer to.
* @return the access interface to the tenant
*/
default Tenants.Single inspect(Tenant tenant) throws EntityNotFoundException {
return tenants().get(tenant.getId());
}
/**
* Provides an access interface for inspecting given environment.
*
* @param environment the environment to steer to.
* @return the access interface to the environment
*/
default Environments.Single inspect(Environment environment) throws EntityNotFoundException {
return inspect(environment.getPath(), Environments.Single.class);
}
/**
* Provides an access interface for inspecting given feed.
*
* @param feed the feed to steer to.
* @return the access interface to the feed
*/
default Feeds.Single inspect(Feed feed) throws EntityNotFoundException {
return inspect(feed.getPath(), Feeds.Single.class);
}
/**
* Provides an access interface for inspecting given metric.
*
* @param metric the metric to steer to.
* @return the access interface to the metric
*/
default Metrics.Single inspect(Metric metric) throws EntityNotFoundException {
return inspect(metric.getPath(), Metrics.Single.class);
}
/**
* Provides an access interface for inspecting given metric type.
*
* @param metricType the metric type to steer to.
* @return the access interface to the metric type
*/
default MetricTypes.Single inspect(MetricType metricType) throws EntityNotFoundException {
return inspect(metricType.getPath(), MetricTypes.Single.class);
}
/**
* Provides an access interface for inspecting given resource.
*
* @param resource the resource to steer to.
* @return the access interface to the resource
*/
default Resources.Single inspect(Resource resource) throws EntityNotFoundException {
return inspect(resource.getPath(), Resources.Single.class);
}
/**
* Provides an access interface for inspecting given resource type.
*
* @param resourceType the resource type to steer to.
* @return the access interface to the resource type
*/
default ResourceTypes.Single inspect(ResourceType resourceType) throws EntityNotFoundException {
return inspect(resourceType.getPath(), ResourceTypes.Single.class);
}
default Data.Single inspect(DataEntity data) throws EntityNotFoundException {
return inspect(data.getPath(), Data.Single.class);
}
default OperationTypes.Single inspect(OperationType operationType) throws EntityNotFoundException {
return inspect(operationType.getPath(), OperationTypes.Single.class);
}
default MetadataPacks.Single inspect(MetadataPack metadataPack) throws EntityNotFoundException {
return inspect(metadataPack.getPath(), MetadataPacks.Single.class);
}
default Relationships.Single inspect(Relationship relationship) {
return relationships().get(relationship.getId());
}
/**
* A generic version of the {@code inspect} methods that accepts an element and returns the access interface to it.
*
* <p>If you don't know the type of element (and therefore cannot deduce access interface type) you can always use
* {@link org.hawkular.inventory.api.ResolvableToSingle} type which is guaranteed to be the super interface of
* all access interfaces returned from this method (which makes it almost useless but at least you can get the
* instance of it).
*
* @param entity the element to inspect
* @param accessInterface the expected access interface
* @param <E> the type of the element
* @param <Single> the type of the access interface
* @return the access interface instance
* @throws java.lang.ClassCastException if the provided access interface doesn't match the element
*/
default <E extends AbstractElement<?, U>, U extends AbstractElement.Update, Single extends ResolvableToSingle<E, U>>
Single inspect(E entity, Class<Single> accessInterface) {
return entity.accept(new ElementVisitor<Single, Void>() {
@Override
public Single visitTenant(Tenant tenant, Void ignored) {
return accessInterface.cast(inspect(tenant));
}
@Override
public Single visitEnvironment(Environment environment, Void ignored) {
return accessInterface.cast(inspect(environment));
}
@Override
public Single visitFeed(Feed feed, Void ignored) {
return accessInterface.cast(inspect(feed));
}
@Override
public Single visitMetric(Metric metric, Void ignored) {
return accessInterface.cast(inspect(metric));
}
@Override
public Single visitMetricType(MetricType definition, Void ignored) {
return accessInterface.cast(inspect(definition));
}
@Override
public Single visitResource(Resource resource, Void ignored) {
return accessInterface.cast(inspect(resource));
}
@Override
public Single visitResourceType(ResourceType type, Void ignored) {
return accessInterface.cast(inspect(type));
}
@Override
public Single visitData(DataEntity data, Void parameter) {
return accessInterface.cast(inspect(data));
}
@Override
public Single visitOperationType(OperationType operationType, Void parameter) {
return accessInterface.cast(inspect(operationType));
}
@Override
public Single visitRelationship(Relationship relationship, Void parameter) {
return accessInterface.cast(relationships().get(relationship.getId()));
}
@Override public Single visitMetadataPack(MetadataPack metadataPack, Void parameter) {
return accessInterface.cast(inspect(metadataPack));
}
@Override public Single visitUnknown(Object entity, Void parameter) {
return null;
}
}, null);
}
/**
* Another generic version of the inspect method, this time using the {@link CanonicalPath} to an element.
*
* <p>If you don't know the type of element (and therefore cannot deduce access interface type) you can always use
* {@link org.hawkular.inventory.api.ResolvableToSingle} type which is guaranteed to be the super interface of
* all access interfaces returned from this method (which makes it almost useless but at least you can get the
* instance of it).
*
* <p>Note that this does NOT support paths that end inside a structured data, because structured data is not a
* standalone inventory entity and does not have a separate access interface. For such paths, you need to extract
* the parent path to the data entity, pass it to this method and then use the
* {@link Data.Single#data(RelativePath)} or {@link Data.Single#flatData(RelativePath)}
* methods to get at the target portion of the structured data specified by the path.
*
* @param path the path to the element (entity or relationship)
* @param accessInterface the expected access interface
* @param <Single> the type of the access interface
* @return the access interface instance
* @throws java.lang.ClassCastException if the provided access interface doesn't match the element
*/
default <E extends AbstractElement<?, U>, U extends AbstractElement.Update, Single extends ResolvableToSingle<E, U>>
Single inspect(CanonicalPath path, Class<Single> accessInterface) {
CanonicalPath.IdExtractor ids = path.ids();
return path.accept(new ElementTypeVisitor<Single, Void>() {
@Override
public Single visitTenant(Void parameter) {
return accessInterface.cast(tenants().get(ids.getTenantId()));
}
@Override
public Single visitEnvironment(Void parameter) {
return accessInterface.cast(tenants().get(ids.getTenantId()).environments()
.get(ids.getEnvironmentId()));
}
@Override
public Single visitFeed(Void parameter) {
return accessInterface.cast(tenants().get(ids.getTenantId()).feeds()
.get(ids.getFeedId()));
}
@Override
public Single visitMetric(Void parameter) {
Tenants.Single tenant = tenants().get(ids.getTenantId());
RelativePath resourcePath = ids.getResourcePath();
String feedId = ids.getFeedId();
String environmentId = ids.getEnvironmentId();
String metricId = ids.getMetricId();
ResolvableToSingle<?, ?> iface;
if (resourcePath == null) {
if (feedId == null) {
iface = tenant.environments().get(environmentId).metrics().get(metricId);
} else {
iface = tenant.feeds().get(feedId).metrics().get(metricId);
}
} else {
if (feedId == null) {
iface = tenant.environments().get(environmentId).resources()
.descendContained(ids.getResourcePath()).metrics().get(metricId);
} else {
iface = tenant.feeds().get(feedId).resources().descendContained(ids.getResourcePath())
.metrics().get(metricId);
}
}
return accessInterface.cast(iface);
}
@Override
public Single visitMetricType(Void parameter) {
Tenants.Single ten = tenants().get(ids.getTenantId());
return accessInterface.cast(ids.getFeedId() == null
? ten.metricTypes().get(ids.getMetricTypeId())
: ten.feeds().get(ids.getFeedId()).metricTypes()
.get(ids.getMetricTypeId()));
}
@Override
public Single visitResource(Void parameter) {
Tenants.Single tenant = tenants().get(ids.getTenantId());
Resources.Single access;
if (ids.getFeedId() == null) {
access = tenant.environments().get(ids.getEnvironmentId()).resources()
.descendContained(ids.getResourcePath());
} else {
access = tenant.feeds().get(ids.getFeedId()).resources().descendContained(ids.getResourcePath());
}
return accessInterface.cast(access);
}
@Override
public Single visitResourceType(Void parameter) {
Tenants.Single ten = tenants().get(ids.getTenantId());
return accessInterface.cast(ids.getFeedId() == null
? ten.resourceTypes().get(ids.getResourceTypeId())
: ten.feeds().get(ids.getFeedId()).resourceTypes()
.get(ids.getResourceTypeId()));
}
@Override
public Single visitRelationship(Void parameter) {
return accessInterface.cast(relationships().get(ids.getRelationshipId()));
}
@Override
public Single visitData(Void parameter) {
String rt = ids.getResourceTypeId();
String ot = ids.getOperationTypeId();
if (rt != null && ot == null) {
ResourceTypes.Single rts = inspect(path.up(), ResourceTypes.Single.class);
DataRole.ResourceType role = DataRole.ResourceType.valueOf(ids.getDataRole());
return accessInterface.cast(rts.data().get(role));
} else if (ot != null) {
OperationTypes.Single ots = inspect(path.up(), OperationTypes.Single.class);
DataRole.OperationType role = DataRole.OperationType.valueOf(ids.getDataRole());
return accessInterface.cast(ots.data().get(role));
} else {
Resources.Single res = inspect(path.up(), Resources.Single.class);
DataRole.Resource role = DataRole.Resource.valueOf(ids.getDataRole());
return accessInterface.cast(res.data().get(role));
}
}
@Override
public Single visitOperationType(Void parameter) {
ResourceTypes.Single rt = inspect(path.up(), ResourceTypes.Single.class);
return accessInterface.cast(rt.operationTypes().get(path.getSegment().getElementId()));
}
@Override
public Single visitMetadataPack(Void parameter) {
return accessInterface.cast(tenants().get(path.up().getSegment().getElementId())
.metadataPacks().get(path.getSegment().getElementId()));
}
@Override
public Single visitUnknown(Void parameter) {
return null;
}
}, null);
}
/**
* This method is mainly useful for testing.
*
* @param interest the interest in changes of some inventory entity type
* @return true if the interest has some observers or not
*/
boolean hasObservers(Interest<?, ?> interest);
/**
* <b>NOTE</b>: The subscribers will receive the notifications even after they failed. I.e. it is the
* subscribers responsibility to unsubscribe on error
*
* @param interest the interest in changes of some inventory entity type
* @param <C> the type of object that will be passed to the subscribers of the returned observable
* @param <E> the type of the entity the interest is expressed on
* @return an observable to which the caller can subscribe to receive notifications about inventory
* mutation
*/
<C, E> rx.Observable<C> observable(Interest<C, E> interest);
/**
* This method returns the {@link java.io.InputStream} with the GraphSON representation of the whole sub-graph
* of given tenantId. It's basically the graph dump.
*
* @param tenantId the tenantId for which we want the GraphSON
* @return the InputStream with the GraphSON representation
*/
InputStream getGraphSON(String tenantId);
<T extends AbstractElement<?, ?>> T getElement(CanonicalPath path);
<T extends Entity<?, ?>> Iterator<T> getTransitiveClosureOver(CanonicalPath startingPoint,
Relationships.Direction direction, Class<T> clazz,
String... relationshipNames);
Configuration getConfiguration();
default <T extends AbstractElement> Page<T> execute(Query query, Class<T> requestedEntity, Pager pager) {
return new Page<>(Collections.emptyIterator(), new PageContext(0, 0, Order.unspecified()), 0);
}
/**
* Converts the provided entity to a blueprint that would create the same entity.
*
* @param <B> the type of the blueprint
* @param entity the entity to convert to blueprint
* @return the blueprint of the entity
*/
@SuppressWarnings({"unchecked", "rawtypes"})
static <B extends Blueprint> B asBlueprint(Entity<B, ?> entity) {
if (entity == null) {
return null;
}
return entity.accept(new ElementVisitor<B, Void>() {
@Override public B visitData(DataEntity data, Void parameter) {
return (B) fillCommon(data, new DataEntity.Blueprint.Builder<>()).withRole(data.getRole())
.withValue(data.getValue()).build();
}
@Override public B visitTenant(Tenant tenant, Void parameter) {
return (B) fillCommon(tenant, new Tenant.Blueprint.Builder()).build();
}
@Override public B visitEnvironment(Environment environment, Void parameter) {
return (B) fillCommon(environment, new Environment.Blueprint.Builder()).build();
}
@Override public B visitFeed(Feed feed, Void parameter) {
return (B) fillCommon(feed, Feed.Blueprint.builder()).build();
}
@Override public B visitMetric(Metric metric, Void parameter) {
//we don't want to have tenant ID and all that jazz influencing the hash, so always use
//a relative path
RelativePath metricTypePath = metric.getType().getPath().relativeTo(metric.getPath());
return (B) fillCommon(metric, Metric.Blueprint.builder())
.withInterval(metric.getCollectionInterval())
.withMetricTypePath(metricTypePath.toString()).build();
}
@Override public B visitMetricType(MetricType type, Void parameter) {
return (B) fillCommon(type, MetricType.Blueprint.builder(type.getMetricDataType()))
.withInterval(type.getCollectionInterval()).withUnit(type.getUnit()).build();
}
@Override public B visitOperationType(OperationType operationType, Void parameter) {
return (B) fillCommon(operationType, OperationType.Blueprint.builder()).build();
}
@Override public B visitMetadataPack(MetadataPack metadataPack, Void parameter) {
throw new IllegalStateException("Computing a blueprint of a metadatapack is not supported.");
}
@Override public B visitUnknown(Object entity1, Void parameter) {
throw new IllegalStateException("Unhandled entity type during conversion to blueprint: " +
entity1.getClass());
}
@Override public B visitResource(Resource resource, Void parameter) {
//we don't want to have tenant ID and all that jazz influencing the hash, so always use
//a relative path
RelativePath resourceTypePath = resource.getType().getPath().relativeTo(resource.getPath());
return (B) fillCommon(resource, Resource.Blueprint.builder())
.withResourceTypePath(resourceTypePath.toString()).build();
}
@Override public B visitResourceType(ResourceType type, Void parameter) {
return (B) fillCommon(type, ResourceType.Blueprint.builder()).build();
}
@Override public B visitRelationship(Relationship relationship,
Void parameter) {
throw new IllegalArgumentException("Inventory structure blueprint conversion does not handle " +
"relationships.");
}
private <X extends Entity<? extends XB, ?>, XB extends Entity.Blueprint,
XBB extends Entity.Blueprint.Builder<XB, XBB>>
XBB fillCommon(X e, XBB bld) {
return bld.withId(e.getId()).withName(e.getName())
.withProperties(e.getProperties());
}
}, null);
}
/**
* @return a registry of various types associated with entities
*/
static Types types() {
return Types.INSTANCE;
}
/**
* A registry of various types used with entities. You can look up an by things like segment type, entity type,
* blueprint type, etc. and then obtain the rest of the types for the corresponding entity type.
*/
@SuppressWarnings("unchecked")
final class Types {
private static final Types INSTANCE = new Types();
private static final EnumMap<SegmentType, ElementTypes<?, ?, ?>> elementTypes;
static {
elementTypes = new EnumMap<>(SegmentType.class);
elementTypes.put(SegmentType.d,
new ElementTypes<>(Data.Single.class, Data.Multiple.class, (Class) DataEntity.Blueprint.class,
DataEntity.Update.class, DataEntity.class, SegmentType.d));
elementTypes.put(SegmentType.e,
new ElementTypes<>(Environments.Single.class, Environments.Multiple.class,
Environment.Blueprint.class, Environment.Update.class, Environment.class, SegmentType.e));
elementTypes.put(SegmentType.f,
new ElementTypes<>(Feeds.Single.class, Feeds.Multiple.class, Feed.Blueprint.class, Feed.Update.class,
Feed.class, SegmentType.f));
elementTypes.put(SegmentType.m,
new ElementTypes<>(Metrics.Single.class, Metrics.Multiple.class, Metric.Blueprint.class,
Metric.Update.class, Metric.class, SegmentType.m));
elementTypes.put(SegmentType.mp,
new ElementTypes<>(MetadataPacks.Single.class, MetadataPacks.Multiple.class,
MetadataPack.Blueprint.class, MetadataPack.Update.class, MetadataPack.class,
SegmentType.mp));
elementTypes.put(SegmentType.mt,
new ElementTypes<>(MetricTypes.Single.class, MetricTypes.Multiple.class, MetricType.Blueprint.class,
MetricType.Update.class, MetricType.class, SegmentType.mt));
elementTypes.put(SegmentType.ot,
new ElementTypes<>(OperationTypes.Single.class, OperationTypes.Multiple.class,
OperationType.Blueprint.class, OperationType.Update.class, OperationType.class,
SegmentType.ot));
elementTypes.put(SegmentType.r,
new ElementTypes<>(Resources.Single.class, Resources.Multiple.class, Resource.Blueprint.class,
Resource.Update.class, Resource.class, SegmentType.r));
elementTypes.put(SegmentType.rl,
new ElementTypes<>(Relationships.Single.class, Relationships.Multiple.class,
Relationship.Blueprint.class, Relationship.Update.class, Relationship.class,
SegmentType.rl));
elementTypes.put(SegmentType.rt,
new ElementTypes<>(ResourceTypes.Single.class, ResourceTypes.Multiple.class,
ResourceType.Blueprint.class, ResourceType.Update.class, ResourceType.class,
SegmentType.rt));
elementTypes.put(SegmentType.t,
new ElementTypes<>(Tenants.Single.class, Tenants.Multiple. class, Tenant.Blueprint.class,
Tenant.Update.class, Tenant.class, SegmentType.t));
}
private Types() {
}
/**
* @return element types that represent entities (i.e. everything but a relationship)
*/
public Set<ElementTypes<? extends Entity<?, ?>, ?, ?>> entityTypes() {
return elementTypes.entrySet().stream()
.filter(e -> {
SegmentType st = e.getKey();
return st != SegmentType.rl;
})
.map(e -> (ElementTypes<? extends Entity<?, ?>, ?, ?>) e.getValue())
.collect(Collectors.toSet());
}
public ElementTypes<?, ?, ?> byPath(Path path) {
return bySegment(path.getSegment().getElementType());
}
public ElementTypes<?, ?, ?> bySegment(SegmentType segmentType) {
ElementTypes ret = elementTypes.get(segmentType);
if (ret == null) {
throw new IllegalArgumentException(
"Unsupported segment type: " + segmentType);
}
return ret;
}
public <B extends Blueprint> ElementTypes<? extends AbstractElement<B, ?>, B, ?>
byBlueprint(Class<B> blueprintType) {
for(SegmentType st : SegmentType.values()) {
ElementTypes ret = elementTypes.get(st);
if (ret.getBlueprintType().equals(blueprintType)) {
return ret;
}
}
throw new IllegalArgumentException("Unknown blueprint type: " + blueprintType);
}
public <U extends AbstractElement.Update> ElementTypes<?, ?, U> byUpdate(Class<U> updateType) {
for(SegmentType st : SegmentType.values()) {
ElementTypes ret = elementTypes.get(st);
if (ret.getUpdateType().equals(updateType)) {
return ret;
}
}
throw new IllegalArgumentException("Unknown update type: " + updateType);
}
public <E extends AbstractElement<B, U>, B extends Blueprint, U extends AbstractElement.Update>
ElementTypes<E, B, U> byElement(Class<E> elementType) {
for(SegmentType st : SegmentType.values()) {
ElementTypes ret = elementTypes.get(st);
if (ret.getElementType().equals(elementType)) {
return ret;
}
}
throw new IllegalArgumentException("Unknown element type: " + elementType);
}
public <E extends AbstractElement<B, U>, B extends Blueprint, U extends AbstractElement.Update>
ElementTypes<E, B, U> bySingle(Class<? extends ResolvableToSingle<E, U>> singleAccessorType) {
for(SegmentType st : SegmentType.values()) {
ElementTypes ret = elementTypes.get(st);
if (ret.getSingleAccessorType().equals(singleAccessorType)) {
return ret;
}
}
throw new IllegalArgumentException("Unknown single accessor type: " + singleAccessorType);
}
public <E extends AbstractElement<B, U>, B extends Blueprint, U extends AbstractElement.Update>
ElementTypes<E, B, U> byMultiple(Class<? extends ResolvableToMany<E>> multipleAccessorType) {
for(SegmentType st : SegmentType.values()) {
ElementTypes ret = elementTypes.get(st);
if (ret.getMultipleAccessorType().equals(multipleAccessorType)) {
return ret;
}
}
throw new IllegalArgumentException("Unknown multiple accessor type: " + multipleAccessorType);
}
}
final class ElementTypes<E extends AbstractElement<B, U>, B extends Blueprint, U extends AbstractElement.Update> {
private final Class<? extends ResolvableToSingle<E, U>> singleAccessorType;
private final Class<? extends ResolvableToMany<E>> manyAccessorType;
private final Class<B> blueprintType;
private final Class<U> updateType;
private final Class<E> elementType;
private final SegmentType segmentType;
private ElementTypes(Class<? extends ResolvableToSingle<E, U>> singleAccessorType,
Class<? extends ResolvableToMany<E>> manyAccessorType,
Class<B> blueprintType,
Class<U> updateType,
Class<E> elementType, SegmentType segmentType) {
this.singleAccessorType = singleAccessorType;
this.manyAccessorType = manyAccessorType;
this.blueprintType = blueprintType;
this.updateType = updateType;
this.elementType = elementType;
this.segmentType = segmentType;
}
public Class<B> getBlueprintType() {
return blueprintType;
}
public Class<E> getElementType() {
return elementType;
}
public SegmentType getSegmentType() {
return segmentType;
}
public Class<U> getUpdateType() {
return updateType;
}
public Class<? extends ResolvableToMany<E>> getMultipleAccessorType() {
return manyAccessorType;
}
public Class<? extends ResolvableToSingle<E, U>> getSingleAccessorType() {
return singleAccessorType;
}
}
}