/*
* 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.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.hawkular.inventory.api.Inventory;
import org.hawkular.inventory.paths.Path;
import org.hawkular.inventory.paths.RelativePath;
import io.swagger.annotations.ApiModel;
/**
* Produces an identity hash of entities. Identity hash is a hash that uniquely identifies an entity
* and is produced using its user-defined id and structure. This hash is used to match a client-side state of an
* entity severside state of it.
* <p>
* The identity hash is defined only for the following types of entities:
* {@link Feed}, {@link ResourceType}, {@link MetricType}, {@link OperationType}, {@link Metric}, {@link Resource} and
* {@link DataEntity}.
* <p>
* The identity hash is an SHA1 hash of a string representation of the entity (in UTF-8 encoding). The string
* representation is produced as follows:
* <ol>
* <li>DataEntity: role + minimizedDataJSON
* <li>Metric: id
* <li>Resource: hashOf(configuration) + hashOf(connectionConfiguration) + hashOf(childResource)*
* + hashOf(childMetric)* + id
* <li>MetricType: id + type + unit
* <li>OperationType: hashOf(returnType) + hashOf(parameterTypes) + id
* <li>ResourceType: hashOf(configurationSchema) + hashOf(connectionConfigurationSchema) + hashOf(childOperationType)*
* + id
* <li>Feed: hashOf(childResourceType)* + hashOf(childMetricType)* + hashOf(childResource)* + hashOf(childMetric)* + id
* </ol>
* where {@code hashOf()} means the identity hash of the child entity
*
* @author Lukas Krejci
* @since 0.7.0
*/
public final class IdentityHash {
private IdentityHash() {
}
public static String of(MetadataPack.Members metadata) {
ComputeHash.HashConstructor ctor = new ComputeHash.HashConstructor(new ComputeHash.DigestComputingWriter(
ComputeHash.newDigest()));
ComputeHash.HashableView metadataView = ComputeHash.HashableView.of(metadata);
SortedSet<Entity.Blueprint> all = new TreeSet<>(ComputeHash.BLUEPRINT_COMPARATOR);
all.addAll(metadata.getMetricTypes());
all.addAll(metadata.getResourceTypes());
StringBuilder result = new StringBuilder();
for (Entity.Blueprint bl : all) {
//identity hash not dependent on relative paths (if any) in the data, so null for entityPath is ok.
ComputeHash.IntermediateHashResult res = ComputeHash.computeHash(null, bl, metadataView, ctor, true, false,
false, (rp) -> null);
result.append(res.identityHash);
}
ComputeHash.DigestComputingWriter digestor = ctor.getDigestor();
digestor.reset();
digestor.append(result);
digestor.close();
return digestor.digest();
}
public static String of(InventoryStructure<?> inventory) {
//identity hash is not dependent on the root path - I.e. no relative paths in the inventory structure are used
//to compute the identity hash...
return ComputeHash.of(inventory, null, true, false, false).getIdentityHash();
}
public static String of(Entity<? extends Entity.Blueprint, ?> entity, Inventory inventory) {
return of(InventoryStructure.of(entity, inventory));
}
public static String of(MetadataPack mp, Inventory inventory) {
List<Entity<? extends Entity.Blueprint, ?>> all = new ArrayList<>();
all.addAll(inventory.inspect(mp).resourceTypes().getAll().entities());
all.addAll(inventory.inspect(mp).metricTypes().getAll().entities());
return of(all, inventory);
}
public static Tree treeOf(InventoryStructure<?> inventory) {
@SuppressWarnings("unchecked")
Tree.AbstractBuilder<?>[] tbld =
new Tree.AbstractBuilder[1];
Consumer<ComputeHash.IntermediateHashContext> startChild = context -> {
if (tbld[0] == null) {
tbld[0] = Tree.builder();
} else {
tbld[0] = tbld[0].startChild();
}
};
BiConsumer<ComputeHash.IntermediateHashContext, ComputeHash.IntermediateHashResult> endChild = (ctx, result) -> {
if (tbld[0] instanceof Tree.ChildBuilder) {
tbld[0].withHash(result.identityHash).withPath(result.path);
@SuppressWarnings("unchecked")
Tree.AbstractBuilder<?> parent =
((Tree.ChildBuilder<?>) tbld[0]).getParent();
parent.addChild(((Tree.ChildBuilder<?>) tbld[0]).build());
tbld[0] = parent;
}
};
//identity hash is not computed using any relative paths, so we can pass null to the root path of the
//computation.
ComputeHash.IntermediateHashResult res = ComputeHash.treeOf(inventory, null, true, false, false, startChild,
endChild);
tbld[0].withPath(res.path).withHash(res.identityHash);
return ((Tree.Builder)tbld[0]).build();
}
public static String of(Iterable<? extends Entity<? extends Entity.Blueprint, ?>> entities, Inventory inventory) {
return of(entities.iterator(), inventory);
}
public static String of(Iterator<? extends Entity<? extends Entity.Blueprint, ?>> entities, Inventory inventory) {
SortedSet<Entity<? extends Entity.Blueprint, ?>> sortedEntities = new TreeSet<>(ComputeHash.ENTITY_COMPARATOR);
entities.forEachRemaining(sortedEntities::add);
ComputeHash.HashConstructor ctor = new ComputeHash.HashConstructor(new ComputeHash.DigestComputingWriter(
ComputeHash.newDigest()));
StringBuilder resultHash = new StringBuilder();
sortedEntities.forEach((e) -> {
InventoryStructure<?> structure = InventoryStructure.of(e, inventory);
ComputeHash.HashableView v = ComputeHash.HashableView.of(structure);
ComputeHash.IntermediateHashResult res = ComputeHash
.computeHash(e.getPath(), structure.getRoot(), v, ctor, true, false,
false, (rp) -> null);
resultHash.append(res.identityHash);
});
ctor.getDigestor().reset();
ctor.getDigestor().append(resultHash);
ctor.getDigestor().close();
return ctor.getDigestor().digest();
}
@ApiModel("IdentityHashTree")
public static final class Tree extends AbstractHashTree<Tree, String> implements Serializable {
//jackson support
private Tree() {
this(null, null, null);
}
private Tree(RelativePath path, String hash, Map<Path.Segment, Tree> children) {
super(path, hash, children);
}
public static Builder builder() {
return new Builder();
}
public interface AbstractBuilder<This extends AbstractHashTree.Builder<This, ChildBuilder<This>, Tree, String>
& AbstractBuilder<This>>
extends AbstractHashTree.Builder<This, ChildBuilder<This>, Tree, String> {
}
public static final class Builder
extends AbstractHashTree.AbstractBuilder<Builder, ChildBuilder<Builder>, Tree, String>
implements AbstractBuilder<Builder>, TopBuilder<Builder, ChildBuilder<Builder>, Tree, String> {
private Builder() {
super(Tree::new, ChildBuilder<Builder>::new);
}
@Override
public Tree build() {
return super.build();
}
}
public static final class ChildBuilder<
Parent extends AbstractHashTree.Builder<Parent, ChildBuilder<Parent>, Tree, String>
& AbstractBuilder<Parent>>
extends AbstractChildBuilder<ChildBuilder<Parent>, Parent, ChildBuilder<ChildBuilder<Parent>>,
Tree, String>
implements AbstractBuilder<ChildBuilder<Parent>>,
AbstractHashTree.ChildBuilder<ChildBuilder<Parent>, Parent, ChildBuilder<ChildBuilder<Parent>>, Tree,
String> {
ChildBuilder(TreeConstructor<Tree, String> tctor, Parent p) {
super(tctor, p, ChildBuilder<ChildBuilder<Parent>>::new);
}
}
}
}