org.elastos.did.DIDEntity Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of didsdk Show documentation
Show all versions of didsdk Show documentation
Elastos decentralized identity library
The newest version!
/*
* Copyright (c) 2019 Elastos Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.elastos.did;
import static com.google.common.base.Preconditions.checkArgument;
import java.io.File;
import java.io.FileWriter;
import java.io.FilterWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.elastos.did.exception.DIDSyntaxException;
import org.elastos.did.exception.UnknownInternalException;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
/**
* The abstract super class for all DID entities.
*
*
* This class provides a skeletal implementation for JSON and object mapping,
* include DIDEntity serialize to JSON and JSON deserialize to DIDEntity
* objects.
*
*
* @param the type of the class modeled by this DIDEntity object
*/
public abstract class DIDEntity {
private final static boolean NORMALIZED_DEFAULT = true;
/**
* The default data format.
*/
protected final static SimpleDateFormat dateFormat =
new SimpleDateFormat(Constants.DATE_FORMAT);
/**
* The ISO8601 compatible data format.
*/
protected final static SimpleDateFormat isoDateFormat =
new SimpleDateFormat(Constants.DATE_FORMAT_ISO_8601);
/**
* DID serialization context key name.
*/
protected final static String CONTEXT_KEY = "org.elastos.did.context";
static {
dateFormat.setTimeZone(Constants.UTC);
isoDateFormat.setTimeZone(Constants.UTC);
}
/**
* The DID serialization context class.
*/
protected static class SerializeContext {
private boolean normalized;
private DID did;
private SerializeContext() {
this(false, null);
}
private SerializeContext(boolean normalized, DID did) {
this.normalized = normalized;
this.did = did;
}
/**
* Check whether the current serializer working in normalized mode.
*
* @return true if the serializer working in normalized mode, false otherwise
*/
public boolean isNormalized() {
return normalized;
}
/**
* Set the current serializer work in normalized mode or not.
*
* @param normalized true for normalized mode, false otherwise
* @return the SerializeContext instance for method chaining
*/
public SerializeContext setNormalized(boolean normalized) {
this.normalized = normalized;
return this;
}
/**
* Get the DID object who own the current serialize object.
*
* @return a DID object
*/
public DID getDid() {
return did;
}
/**
* Set the DID object who own the current serialize object.
*
* @param did the owner of the current serialize object
* @return the SerializeContext instance for method chaining
*/
public SerializeContext setDid(DID did) {
this.did = did;
return this;
}
}
/**
* DIDPropertyFilter implementation that only uses property name to
* determine whether to serialize property as is, or to filter it out.
*
*
* It will include all properties by default. The subclasses could
* override the include method to filter the properties.
*
*/
protected static class DIDPropertyFilter implements PropertyFilter {
/**
* Method called to determine whether property will be included
* (if 'true' returned) or filtered out (if 'false' returned).
*
* @param writer object called to do actual serialization of the field, if not filtered out
* @param pojo object that contains property value to serialize
* @param context the serialization context object
* @return true for include this property, false filtered out
*/
protected boolean include(PropertyWriter writer, Object pojo, SerializeContext context) {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public void serializeAsField(Object pojo, JsonGenerator gen, SerializerProvider provider,
PropertyWriter writer) throws Exception {
SerializeContext context = (SerializeContext)provider.getConfig()
.getAttributes().getAttribute(DIDEntity.CONTEXT_KEY);
if (include(writer, pojo, context)) {
writer.serializeAsField(pojo, gen, provider);
} else if (!gen.canOmitFields()) { // since 2.3
writer.serializeAsOmittedField(pojo, gen, provider);
}
}
/**
* {@inheritDoc}
*/
@Override
public void serializeAsElement(Object elementValue, JsonGenerator gen, SerializerProvider provider,
PropertyWriter writer) throws Exception {
writer.serializeAsElement(elementValue, gen, provider);
}
/**
* {@inheritDoc}
*/
@Override
@Deprecated
public void depositSchemaProperty(PropertyWriter writer, ObjectNode propertiesNode,
SerializerProvider provider) throws JsonMappingException {
writer.depositSchemaProperty(propertiesNode, provider);
}
/**
* {@inheritDoc}
*/
@Override
public void depositSchemaProperty(PropertyWriter writer, JsonObjectFormatVisitor objectVisitor,
SerializerProvider provider) throws JsonMappingException {
writer.depositSchemaProperty(objectVisitor, provider);
}
}
static class DateDeserializer extends StdDeserializer {
private static final long serialVersionUID = -4252894239212420927L;
public DateDeserializer() {
this(null);
}
public DateDeserializer(Class> t) {
super(t);
}
@Override
public Date deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonToken token = p.getCurrentToken();
if (!token.equals(JsonToken.VALUE_STRING))
throw ctxt.weirdStringException(p.getText(),
Date.class, "Invalid datetime string");
String dateStr = p.getValueAsString();
try {
return dateFormat.parse(dateStr);
} catch (ParseException ignore) {
}
// Fail-back to ISO 8601 format.
try {
return isoDateFormat.parse(dateStr);
} catch (ParseException e) {
throw ctxt.weirdStringException(p.getText(),
Date.class, "Invalid datetime string");
}
}
}
static class NormalizedWriter extends FilterWriter {
private char cb[];
private int nChars, nextChar;
private static int defaultCharBufferSize = 4096;
protected NormalizedWriter(Writer out) {
this(out, defaultCharBufferSize);
}
public NormalizedWriter(Writer out, int sz) {
super(out);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.out = out;
cb = new char[sz];
nChars = sz;
nextChar = 0;
}
private int min(int a, int b) {
if (a < b) return a;
return b;
}
void flushBuffer() throws IOException {
synchronized (lock) {
if (nextChar == 0)
return;
String cs = new String(cb, 0, nextChar);
out.write(Normalizer.normalize(cs, Form.NFC));
nextChar = 0;
}
}
@Override
public void write(int c) throws IOException {
synchronized (lock) {
if (nextChar >= nChars)
flushBuffer();
cb[nextChar++] = (char)c;
}
}
@Override
public void write(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
if (len >= nChars) {
/* If the request length exceeds the size of the output buffer,
flush the buffer and then write the data directly. In this
way buffered streams will cascade harmlessly. */
flushBuffer();
String cs = new String(cbuf, off, len);
out.write(Normalizer.normalize(cs, Form.NFC));
return;
}
int b = off, t = off + len;
while (b < t) {
int d = min(nChars - nextChar, t - b);
System.arraycopy(cbuf, b, cb, nextChar, d);
b += d;
nextChar += d;
if (nextChar >= nChars)
flushBuffer();
}
}
}
@Override
public void write(String s, int off, int len) throws IOException {
synchronized (lock) {
int b = off, t = off + len;
while (b < t) {
int d = min(nChars - nextChar, t - b);
s.getChars(b, b + d, cb, nextChar);
b += d;
nextChar += d;
if (nextChar >= nChars)
flushBuffer();
}
}
}
@Override
public void flush() throws IOException {
synchronized (lock) {
flushBuffer();
out.flush();
}
}
@Override
public void close() throws IOException {
synchronized (lock) {
if (out == null) {
return;
}
try (Writer w = out) {
flushBuffer();
} finally {
out = null;
cb = null;
}
}
}
}
/**
* Get current object's DID context.
*
* @return the DID object or null
*/
protected DID getSerializeContextDid() {
return null;
}
/**
* Post sanitize routine after deserialization.
*
* @throws DIDSyntaxException if the DID object is invalid
*/
protected void sanitize() throws DIDSyntaxException {
}
/**
* Get the ObjectMapper for serialization or deserialization.
*
* @return a ObjectMapper instance
*/
protected static ObjectMapper getObjectMapper() {
JsonFactory jsonFactory = new JsonFactory();
jsonFactory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
jsonFactory.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false);
ObjectMapper mapper = new ObjectMapper(jsonFactory);
mapper.disable(MapperFeature.AUTO_DETECT_CREATORS,
MapperFeature.AUTO_DETECT_FIELDS,
MapperFeature.AUTO_DETECT_GETTERS,
MapperFeature.AUTO_DETECT_SETTERS,
MapperFeature.AUTO_DETECT_IS_GETTERS);
// Make the ObjectMapper handle the datetime string correctly
mapper.setDateFormat(dateFormat);
SimpleModule didModule = new SimpleModule();
didModule.addDeserializer(Date.class, new DateDeserializer());
mapper.registerModule(didModule);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
/**
* Get the ObjectMapper for serialization with normalized option.
*
* @param normalized true for normalized output, false otherwise
* @return a ObjectMapper instance
*/
protected ObjectMapper getObjectMapper(boolean normalized) {
ObjectMapper mapper = getObjectMapper();
mapper.setConfig(mapper.getSerializationConfig().withAttribute(CONTEXT_KEY,
new SerializeContext(normalized, getSerializeContextDid())));
SimpleFilterProvider filters = new SimpleFilterProvider();
filters.addFilter("publicKeyFilter", DIDDocument.PublicKey.getFilter());
filters.addFilter("didDocumentProofFilter", DIDDocument.Proof.getFilter());
filters.addFilter("credentialFilter", VerifiableCredential.getFilter());
filters.addFilter("credentialProofFilter", VerifiableCredential.Proof.getFilter());
mapper.setFilterProvider(filters);
return mapper;
}
/**
* Convert data that given JSON tree contains into specific DID entity object.
*
* @param the DID entity type
* @param content the JSON node contains the data
* @param clazz the class object for the target DID entity
* @return the parsed DID entity
* @throws DIDSyntaxException if a error occurs when parsing the object
*/
protected static> T parse(JsonNode content, Class clazz)
throws DIDSyntaxException {
ObjectMapper mapper = getObjectMapper();
try {
T o = mapper.treeToValue(content, clazz);
o.sanitize();
return o;
} catch (JsonProcessingException e) {
throw DIDSyntaxException.instantiateFor(clazz, e.getMessage(), e);
}
}
/**
* Parse a DID entity from a string JSON representation into given
* DIDEntity type.
*
* @param the DID entity type
* @param content the string representation of the DID entity
* @param clazz the class object for the target DID entity
* @return the parsed DID entity
* @throws DIDSyntaxException if a error occurs when parsing the object
*/
public static> T parse(String content, Class clazz)
throws DIDSyntaxException {
checkArgument(content != null && !content.isEmpty(), "Invalid JSON content");
checkArgument(clazz != null, "Invalid result class object");
ObjectMapper mapper = getObjectMapper();
try {
T o = mapper.readValue(content, clazz);
o.sanitize();
return o;
} catch (JsonProcessingException e) {
throw DIDSyntaxException.instantiateFor(clazz, e.getMessage(), e);
}
}
/**
* Parse a DID entity from a reader object into given DIDEntity type.
*
* @param the DID entity type
* @param src the reader object to deserialize the object
* @param clazz the class object for the target DID entity
* @return the parsed DID entity
* @throws DIDSyntaxException if a error occurs when parsing the object
* @throws IOException if an IO error occurs
*/
public static> T parse(Reader src, Class clazz)
throws DIDSyntaxException, IOException {
checkArgument(src != null, "Invalid src reader");
checkArgument(clazz != null, "Invalid result class object");
ObjectMapper mapper = getObjectMapper();
try {
T o = mapper.readValue(src, clazz);
o.sanitize();
return o;
} catch (JsonParseException | JsonMappingException e) {
throw DIDSyntaxException.instantiateFor(clazz, e.getMessage(), e);
}
}
/**
* Parse a DID entity from an input stream into given DIDEntity type.
*
* @param the DID entity type
* @param src the input stream to deserialize the object
* @param clazz the class object for the target DID entity
* @return the parsed DID entity
* @throws DIDSyntaxException if a error occurs when parsing the object
* @throws IOException if an IO error occurs
*/
public static> T parse(InputStream src, Class clazz)
throws DIDSyntaxException, IOException {
checkArgument(src != null, "Invalid src input stream");
checkArgument(clazz != null, "Invalid result class object");
ObjectMapper mapper = getObjectMapper();
try {
T o = mapper.readValue(src, clazz);
o.sanitize();
return o;
} catch (JsonParseException | JsonMappingException e) {
throw DIDSyntaxException.instantiateFor(clazz, e.getMessage(), e);
}
}
/**
* Parse a DID entity from a file into given DIDEntity type.
*
* @param the DID entity type
* @param src the file to deserialize the object
* @param clazz the class object for the target DID entity
* @return the parsed DID entity
* @throws DIDSyntaxException if a error occurs when parsing the object
* @throws IOException if an IO error occurs
*/
public static> T parse(File src, Class clazz)
throws DIDSyntaxException, IOException {
checkArgument(src != null, "Invalid src file");
checkArgument(clazz != null, "Invalid result class object");
ObjectMapper mapper = getObjectMapper();
try {
T o = mapper.readValue(src, clazz);
o.sanitize();
return o;
} catch (JsonParseException | JsonMappingException e) {
throw DIDSyntaxException.instantiateFor(clazz, e.getMessage(), e);
}
}
/**
* Serialize this DID entity to a JSON string.
*
* @param normalized true for normalized output, false otherwise
* @return the serialized JSON string
*/
public String serialize(boolean normalized) {
try {
String str = getObjectMapper(normalized).writeValueAsString(this);
return Normalizer.normalize(str, Form.NFC);
} catch (JsonProcessingException e) {
throw new UnknownInternalException(e);
}
}
/**
* Serialize this DID entity to a JSON string in default normalized mode.
*
* @return the serialized JSON string
*/
public String serialize() {
return serialize(NORMALIZED_DEFAULT);
}
/**
* Serialize this DID entity to a Writer.
*
* @param out the output writer object
* @param normalized true for normalized output, false otherwise
* @throws IOException if an IO error occurs
*/
public void serialize(Writer out, boolean normalized) throws IOException {
checkArgument(out != null, "Invalid out writer");
try {
getObjectMapper(normalized).writeValue(new NormalizedWriter(out), this);
} catch (JsonGenerationException | JsonMappingException e) {
throw new UnknownInternalException(e);
}
}
/**
* Serialize DID object to a Writer in default normalized mode.
*
* @param out the output writer object
* @throws IOException if an IO error occurs
*/
public void serialize(Writer out) throws IOException {
serialize(out, NORMALIZED_DEFAULT);
}
/**
* Serialize DID object to an output stream.
*
* @param out the output stream object
* @param normalized true for normalized output, false otherwise
* @throws IOException if an IO error occurs
*/
public void serialize(OutputStream out, boolean normalized) throws IOException {
checkArgument(out != null, "Invalid out stream");
serialize(new OutputStreamWriter(out), normalized);
}
/**
* Serialize DID object to an output stream in default normalized mode.
*
* @param out the output stream object
* @throws IOException if an IO error occurs
*/
public void serialize(OutputStream out) throws IOException {
serialize(out, NORMALIZED_DEFAULT);
}
/**
* Serialize DID object to a file.
*
* @param out the output file object
* @param normalized true for normalized output, false otherwise
* @throws IOException if an IO error occurs
*/
public void serialize(File out, boolean normalized) throws IOException {
checkArgument(out != null, "Invalid out file");
serialize(new FileWriter(out), normalized);
}
/**
* Serialize DID object to a file in default normalized mode.
*
* @param out the output file object
* @throws IOException if an IO error occurs
*/
public void serialize(File out) throws IOException {
serialize(out, NORMALIZED_DEFAULT);
}
/**
* Get the JSON string representation of the object.
*
* @param normalized true for normalized output, false otherwise
* @return a JSON string representation of the object
*/
public String toString(boolean normalized) {
return serialize(normalized);
}
/**
* Get the JSON string representation of the object in default normalized mode.
*
* @return a JSON string representation of the object
*/
@Override
public String toString() {
return toString(NORMALIZED_DEFAULT);
}
/**
* Serialize this DID entity to a JSON string.
*
* @param normalized true for normalized output, false otherwise
* @return the serialized JSON string
* @deprecated use {@link #serialize(boolean)} instead
*/
@Deprecated
public String toJson(boolean normalized) {
return serialize(normalized);
}
/**
* Serialize this DID entity to a JSON string in default normalized mode.
*
* @return the serialized JSON string
* @deprecated use {@link #serialize()} instead
*/
@Deprecated
public String toJson() {
return serialize();
}
/**
* Serialize this DID entity to a Writer.
*
* @param out the output writer object
* @param normalized true for normalized output, false otherwise
* @throws IOException if an IO error occurs
* @deprecated use {@link #serialize(Writer, boolean)} instead
*/
@Deprecated
public void toJson(Writer out, boolean normalized) throws IOException {
serialize(out, normalized);
}
/**
* Serialize DID object to a Writer in default normalized mode.
*
* @param out the output writer object
* @throws IOException if an IO error occurs
* @deprecated use {@link #serialize(Writer)} instead
*/
@Deprecated
public void toJson(Writer out) throws IOException {
serialize(out);
}
/**
* Serialize DID object to an output stream.
*
* @param out the output stream object
* @param normalized true for normalized output, false otherwise
* @throws IOException if an IO error occurs
* @deprecated use {@link #serialize(OutputStream, boolean)} instead
*/
@Deprecated
public void toJson(OutputStream out, boolean normalized) throws IOException {
serialize(out, normalized);
}
/**
* Serialize DID object to an output stream in default normalized mode.
*
* @param out the output stream object
* @throws IOException if an IO error occurs
* @deprecated use {@link #serialize(OutputStream)} instead
*/
@Deprecated
public void toJson(OutputStream out) throws IOException {
serialize(out);
}
/**
* Serialize DID object to a file.
*
* @param out the output file object
* @param normalized true for normalized output, false otherwise
* @throws IOException if an IO error occurs
* @deprecated use {@link #serialize(File, boolean)} instead
*/
@Deprecated
public void toJson(File out, boolean normalized) throws IOException {
serialize(out, normalized);
}
/**
* Serialize DID object to a file in default normalized mode.
*
* @param out the output file object
* @throws IOException if an IO error occurs
* @deprecated use {@link #serialize(File)} instead
*/
@Deprecated
public void toJson(File out) throws IOException {
serialize(out);
}
}