GsonDefinitionModelStore.java
/*
* Copyright © 2017, Salesforce.com, Inc
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.salesforce.apt.graph.model.storage.classpath;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.salesforce.apt.graph.model.DefinitionModel;
import com.salesforce.apt.graph.model.storage.DefinitionModelStore;
import com.salesforce.apt.graph.model.storage.DefinitionOutputStreamProvider;
import com.salesforce.apt.graph.model.storage.Resource;
import com.salesforce.apt.graph.model.storage.ResourceLoader;
public class GsonDefinitionModelStore implements DefinitionModelStore {
private final ResourceLoader resourceLocator;
private final DefinitionOutputStreamProvider definitionModelToStore;
private final Gson gson;
protected Gson getGson() {
return gson;
}
protected DefinitionOutputStreamProvider getDefinitionOutputStreamProvider() {
return definitionModelToStore;
}
protected ResourceLoader getResourceLocator() {
return resourceLocator;
}
public GsonDefinitionModelStore(ResourceLoader resourceLocator,
DefinitionOutputStreamProvider definitionModelToStore) {
gson = new GsonBuilder().setPrettyPrinting().create();
this.resourceLocator = resourceLocator;
this.definitionModelToStore = definitionModelToStore;
}
/**
* Find definitions by name.
*
* @param name the name of the definition to find.
* @return A list of all definitions that happen to have the name (from multiple jars?)
*/
@Override
public List<DefinitionModel> lookup(String name) {
List<DefinitionModel> output = new ArrayList<>();
for (Resource resource : resourceLocator.getEntries(name)) {
try (DigestInputStream digestInputStream = new DigestInputStream(resource.getInputStream(), getSha256Digest());
Reader reader = new InputStreamReader(digestInputStream, StandardCharsets.UTF_8)) {
DefinitionModel definitionModel = gson.fromJson(reader, DefinitionModel.class);
definitionModel.setSourceLocation(resource.getLocation());
definitionModel.setSha256(bytesToHex(digestInputStream.getMessageDigest().digest()));
output.add(definitionModel);
} catch (IOException ex) {
return null;
}
}
return output;
}
/**
* Hex encoded bytes from the input array. presented as a String
* @param bytes to convert
* @return String of hex values representing the byte array.
*/
public static String bytesToHex(byte[] bytes) {
final char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int value = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[value >>> 4];
hexChars[j * 2 + 1] = hexArray[value & 0x0F];
}
return new String(hexChars);
}
@Override
public boolean store(DefinitionModel model) {
OutputStream stream = null;
try {
stream = definitionModelToStore.store(model);
DigestOutputStream digesterStream = new DigestOutputStream(stream, getSha256Digest());
try (OutputStreamWriter writer = new OutputStreamWriter(digesterStream, StandardCharsets.UTF_8)) {
gson.toJson(model, writer);
}
digesterStream.flush();
model.setSha256(bytesToHex(digesterStream.getMessageDigest().digest()));
//when in incremental mode, the underlying streams throw exceptions if closed multiple times.
try {
digesterStream.close();
} catch (IOException ioe) {
//prevent m2e not letting the underlying stream be closed multiple times from
//causing an IOException that would cause storage to appear to break.
}
try {
stream.close();
} catch (IOException ioe) {
//prevent m2e not letting the underlying stream be closed multiple times from
//causing an IOException that would cause storage to appear to break.
}
return true;
} catch (IOException ex) {
//indicate that the file could not be stored.
return false;
}
}
/**
* Get the standard SHA-256 message digest. Every implementation of MessageDigest must
* provide SHA-256 message digest
* @return SHA-256 message digest
*/
protected MessageDigest getSha256Digest() {
try {
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException nsae) {
throw new IllegalStateException("Your jvm doesn't implement the default MessageDigesters... namely sha256. Fail.");
}
}
}