/*
* 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.model;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hawkular.inventory.paths.CanonicalPath;
import org.hawkular.inventory.paths.DataRole;
import org.hawkular.inventory.paths.SegmentType;
import io.swagger.annotations.ApiModel;
/**
* A metadata pack incorporates a bunch of resource types and metric types. It computes a hash of its "contents" so that
* merely by examining the hash, one can make sure that certain set of resource types and metric types is present in
* the form one expects.
*
* @author Lukas Krejci
* @since 0.7.0
*/
@ApiModel(description = "A metadata pack can incorporate global resource and metric types making them read-only.",
parent = Entity.class)
public final class MetadataPack extends Entity<MetadataPack.Blueprint, MetadataPack.Update> {
public static final SegmentType SEGMENT_TYPE = SegmentType.mp;
public static boolean canIncorporate(CanonicalPath entityPath) {
SegmentType entityType = entityPath.getSegment().getElementType();
SegmentType parentType = entityPath.up().getSegment().getElementType();
return SegmentType.t.equals(parentType)
&& (SegmentType.rt.equals(entityType) || SegmentType.mt.equals(entityType));
}
private MetadataPack() {
}
public MetadataPack(CanonicalPath path) {
this(path, null);
}
public MetadataPack(CanonicalPath path, Map<String, Object> properties) {
this(null, path, properties);
}
public MetadataPack(String name, CanonicalPath path) {
this(name, path, null);
}
public MetadataPack(String name, CanonicalPath path, Map<String, Object> properties) {
super(name, path, properties);
}
@Override
public <R, P> R accept(ElementVisitor<R, P> visitor, P parameter) {
return visitor.visitMetadataPack(this, parameter);
}
@Override
public Updater<Update, MetadataPack> update() {
return new Updater<>((u) -> new MetadataPack(getPath(), u.getProperties()), this, Update.builder());
}
/**
* This class can be used to completely describe the structure of the metadata pack offline, without needing
* to "touch" inventory. This is particularly useful with
* {@link IdentityHash#of(Members)} method that can be used to compute a hash of
* a metadata pack that might not yet exist without needing to consult inventory.
* <p>
* Note that this structure uses entities but the content hash computation only needs the ID of the entities
* from the paths. As such, the IDs of parent entities of resource types and metric types are irrelevant.
*/
public static final class Members {
private final List<ResourceType.Blueprint> resourceTypes;
private final List<MetricType.Blueprint> metricTypes;
private final IdentityHashMap<ResourceType.Blueprint, List<OperationType.Blueprint>> operations;
private final IdentityHashMap<OperationType.Blueprint, DataEntity.Blueprint<?>> returnTypes;
private final IdentityHashMap<OperationType.Blueprint, DataEntity.Blueprint<?>> parameterTypes;
private final IdentityHashMap<ResourceType.Blueprint, DataEntity.Blueprint<?>> configurationSchemas;
private final IdentityHashMap<ResourceType.Blueprint, DataEntity.Blueprint<?>> connectionConfigurationSchemas;
public static Builder builder() {
return new Builder();
}
private Members(Set<ResourceType.Blueprint> resourceTypes, Set<MetricType.Blueprint> metricTypes,
IdentityHashMap<ResourceType.Blueprint, Set<OperationType.Blueprint>> operations,
IdentityHashMap<OperationType.Blueprint, DataEntity.Blueprint<?>> returnTypes,
IdentityHashMap<OperationType.Blueprint, DataEntity.Blueprint<?>> parameterTypes,
IdentityHashMap<ResourceType.Blueprint, DataEntity.Blueprint<?>> configurationSchemas,
IdentityHashMap<ResourceType.Blueprint, DataEntity.Blueprint<?>>
connectionConfigurationSchemas) {
this.resourceTypes = new ArrayList<>(resourceTypes);
Comparator<Entity.Blueprint> idComp = (a, b) -> a.getId().compareTo(b.getId());
Collections.sort(this.resourceTypes, idComp);
this.metricTypes = new ArrayList<>(metricTypes);
Collections.sort(this.metricTypes, idComp);
this.operations = new IdentityHashMap<>();
operations.forEach((k, v) -> {
List<OperationType.Blueprint> ops = new ArrayList<>(v);
Collections.sort(ops, idComp);
this.operations.put(k, ops);
});
this.returnTypes = returnTypes;
this.parameterTypes = parameterTypes;
this.configurationSchemas = configurationSchemas;
this.connectionConfigurationSchemas = connectionConfigurationSchemas;
}
public List<ResourceType.Blueprint> getResourceTypes() {
return resourceTypes;
}
public List<MetricType.Blueprint> getMetricTypes() {
return metricTypes;
}
public List<OperationType.Blueprint> getOperationTypes(ResourceType.Blueprint resourceType) {
return operations.getOrDefault(resourceType, Collections.emptyList());
}
public DataEntity.Blueprint<?> getReturnType(OperationType.Blueprint operationType) {
return thatOrEmpty(returnTypes.get(operationType), DataRole.OperationType.returnType);
}
public DataEntity.Blueprint<?> getParameterTypes(OperationType.Blueprint operationType) {
return thatOrEmpty(parameterTypes.get(operationType), DataRole.OperationType.parameterTypes);
}
public DataEntity.Blueprint<?> getConfigurationSchema(ResourceType.Blueprint rt) {
return thatOrEmpty(configurationSchemas.get(rt), DataRole.ResourceType.configurationSchema);
}
public DataEntity.Blueprint<?> getConnectionConfigurationSchema(ResourceType.Blueprint rt) {
return thatOrEmpty(connectionConfigurationSchemas.get(rt),
DataRole.ResourceType.connectionConfigurationSchema);
}
private DataEntity.Blueprint<?> thatOrEmpty(DataEntity.Blueprint<?> b, DataRole role) {
if (b == null) {
b = DataEntity.Blueprint.builder().withRole(role).build();
}
return b;
}
public static final class Builder {
private final Set<ResourceType.Blueprint> resourceTypes = new HashSet<>();
private final Set<MetricType.Blueprint> metricTypes = new HashSet<>();
private final IdentityHashMap<ResourceType.Blueprint, Set<OperationType.Blueprint>>
resourceTypeOperationTypes = new IdentityHashMap<>();
private final IdentityHashMap<OperationType.Blueprint, DataEntity.Blueprint<?>> operationTypeReturnType =
new IdentityHashMap<>();
private final IdentityHashMap<OperationType.Blueprint, DataEntity.Blueprint<?>>
operationTypeParameterTypes = new IdentityHashMap<>();
private final IdentityHashMap<ResourceType.Blueprint, DataEntity.Blueprint<?>>
resourceTypeConfigurationSchemas = new IdentityHashMap<>();
private final IdentityHashMap<ResourceType.Blueprint, DataEntity.Blueprint<?>>
resourceTypeConnectionConfigurationSchemas = new IdentityHashMap<>();
public ResourceTypeBuilder with(ResourceType.Blueprint rt) {
resourceTypes.add(rt);
return new ResourceTypeBuilder(rt);
}
public Builder with(MetricType.Blueprint mt) {
metricTypes.add(mt);
return this;
}
public Members build() {
return new Members(resourceTypes, metricTypes, resourceTypeOperationTypes, operationTypeReturnType,
operationTypeParameterTypes, resourceTypeConfigurationSchemas,
resourceTypeConnectionConfigurationSchemas);
}
public final class ResourceTypeBuilder {
private final ResourceType.Blueprint rt;
private ResourceTypeBuilder(ResourceType.Blueprint rt) {
this.rt = rt;
}
public OperationTypeBuilder with(OperationType.Blueprint ot) {
Set<OperationType.Blueprint> ots = resourceTypeOperationTypes.get(rt);
if (ots == null) {
ots = new HashSet<>();
resourceTypeOperationTypes.put(rt, ots);
}
ots.add(ot);
return new OperationTypeBuilder(ot);
}
public ResourceTypeBuilder with(DataEntity.Blueprint<DataRole.ResourceType> data) {
switch (data.getRole()) {
case configurationSchema:
resourceTypeConfigurationSchemas.put(rt, data);
break;
case connectionConfigurationSchema:
resourceTypeConnectionConfigurationSchemas.put(rt, data);
break;
}
return this;
}
public Builder done() {
return Builder.this;
}
public final class OperationTypeBuilder {
private final OperationType.Blueprint ot;
private OperationTypeBuilder(OperationType.Blueprint ot) {
this.ot = ot;
}
public OperationTypeBuilder with(DataEntity.Blueprint<DataRole.OperationType> data) {
switch (data.getRole()) {
case returnType:
operationTypeReturnType.put(ot, data);
break;
case parameterTypes:
operationTypeParameterTypes.put(ot, data);
break;
}
return this;
}
public ResourceTypeBuilder done() {
return ResourceTypeBuilder.this;
}
}
}
}
}
@ApiModel("MetadataPackBlueprint")
public static final class Blueprint extends AbstractElement.Blueprint {
private final Set<CanonicalPath> members;
private final String name;
public static Builder builder() {
return new Builder();
}
private Blueprint() {
super(null);
members = Collections.emptySet();
name = null;
}
public Blueprint(String name, Set<CanonicalPath> members, Map<String, Object> properties) {
super(properties);
this.name = name;
members.forEach((p) -> {
if (!canIncorporate(p)) {
throw new IllegalArgumentException("Entity on path '" + p + "' cannot be part of a metadata pack.");
}
});
this.members = Collections.unmodifiableSet(new HashSet<>(members));
}
public String getName() {
return name;
}
public Set<CanonicalPath> getMembers() {
return members;
}
@Override
public <R, P> R accept(ElementBlueprintVisitor<R, P> visitor, P parameter) {
return visitor.visitMetadataPack(this, parameter);
}
public static final class Builder extends Entity.Blueprint.Builder<Blueprint, Builder> {
private final Set<CanonicalPath> members = new HashSet<>();
private String name;
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withMember(CanonicalPath path) {
if (!canIncorporate(path)) {
throw new IllegalArgumentException(
"A metadata pack cannot incorporate entity on the path: " + path);
}
members.add(path);
return this;
}
public Builder withMembers(Iterable<CanonicalPath> paths) {
paths.forEach(this::withMember);
return this;
}
public Builder withMembers(CanonicalPath... paths) {
return withMembers(Arrays.asList(paths));
}
@Override
public Blueprint build() {
return new Blueprint(name, members, properties);
}
}
}
@ApiModel("MetadataPackUpdate")
public static final class Update extends Entity.Update {
public static Builder builder() {
return new Builder();
}
public Update(Map<String, Object> properties) {
super(null, properties);
}
public Update(String name, Map<String, Object> properties) {
super(name, properties);
}
@Override
public <R, P> R accept(ElementUpdateVisitor<R, P> visitor, P parameter) {
return visitor.visitMetadataPack(this, parameter);
}
public static final class Builder extends Entity.Update.Builder<MetadataPack, Update, Builder> {
@Override
public Update build() {
return new Update(properties);
}
}
}
}