net.lshift.diffa.participant.scanning.DigestBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of participant-support Show documentation
Show all versions of participant-support Show documentation
Diffa Participant Support provides tools for implementing Diffa participants in Java (and other JVM languages)
/**
* Copyright (C) 2010-2011 LShift Ltd.
*
* 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 net.lshift.diffa.participant.scanning;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
/**
* Utility for building digests based on a series of entities.
*/
public class DigestBuilder {
private final static Logger log = LoggerFactory.getLogger(DigestBuilder.class);
private final Map digestBuckets;
private final List aggregations;
public DigestBuilder(List aggregations) {
this.aggregations = aggregations;
this.digestBuckets = new HashMap();
}
/**
* Adds a scan result entry. Note that this entry is expected to be for an entity - ie, it should have it's ID
* property present.
* @param entry the entry to add.
*/
public void add(ScanResultEntry entry) {
add(entry.getId(), entry.getAttributes(), entry.getVersion());
}
/**
* Adds a new version into the builder.
* @param id the id of the entity being added
* @param attributes the attributes of the entity being added. The builder expects that an attribute will be present
* for each of the aggregations specified.
* @param vsn the version of the entity
*/
public void add(String id, Map attributes, String vsn) {
log.trace("Adding to bucket: " + id + ", " + attributes + ", " + vsn);
Map partitions = new HashMap();
partitions.putAll(attributes); // Default partitions to the initial attribute set
for (ScanAggregation aggregation : aggregations) {
String attrVal = attributes.get(aggregation.getAttributeName());
if (attrVal == null) {
throw new MissingAttributeException(id, aggregation.getAttributeName());
}
String bucket = aggregation.bucket(attrVal);
partitions.put(aggregation.getAttributeName(), bucket);
}
BucketKey key = new BucketKey(partitions);
Bucket bucket = digestBuckets.get(key);
if (bucket == null) {
bucket = new Bucket(key, partitions);
digestBuckets.put(key, bucket);
}
bucket.add(vsn);
}
public List toDigests() {
List result = new ArrayList();
for (Bucket bucket : digestBuckets.values()) {
result.add(bucket.toDigest());
}
return result;
}
private static class BucketKey {
private final Map attributes;
public BucketKey(Map attributes) {
this.attributes = attributes;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BucketKey bucketKey = (BucketKey) o;
if (attributes != null ? !attributes.equals(bucketKey.attributes) : bucketKey.attributes != null) return false;
return true;
}
@Override
public int hashCode() {
return attributes != null ? attributes.hashCode() : 0;
}
}
private static class Bucket {
private final BucketKey key;
private final Map attributes;
private final String digestAlgorithm = "MD5";
private final MessageDigest messageDigest;
private String digest = null;
public Bucket(BucketKey key, Map attributes) {
this.key = key;
this.attributes = attributes;
try {
this.messageDigest = MessageDigest.getInstance(digestAlgorithm);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException("MD5 digest algorithm no available");
}
}
/**
* Adds a version to be included the digest computation
* @param vsn The version string to add.
* @throws SealedBucketException When the digest for the current builder instance has already been computed.
*/
public void add(String vsn) {
if (digest != null) {
throw new SealedBucketException(vsn, getLabel());
}
byte[] vsnBytes = vsn.getBytes(Charset.forName("UTF-8"));
messageDigest.update(vsnBytes, 0, vsnBytes.length);
}
public ScanResultEntry toDigest() {
if (digest == null) {
digest = new String(Hex.encodeHex(messageDigest.digest()));
}
return ScanResultEntry.forAggregate(digest, attributes);
}
public String getLabel() {
StringBuilder labelBuilder = new StringBuilder();
String[] keys = attributes.keySet().toArray(new String[attributes.size()]);
Arrays.sort(keys);
for(String key : keys) {
if (labelBuilder.length() > 0) labelBuilder.append("_");
labelBuilder.append(attributes.get(key));
}
return labelBuilder.toString();
}
}
}