Java tutorial
/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package ddf.catalog.data.impl; import java.io.IOException; import java.io.InputStream; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.builder.HashCodeBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddf.catalog.data.Attribute; import ddf.catalog.data.AttributeDescriptor; import ddf.catalog.data.BinaryContent; import ddf.catalog.data.Metacard; import ddf.catalog.data.MetacardType; /** * Implements the {@link Metacard}'s required attributes. <br/> * <p> * <p> * <b>Serialization Note</b><br/> * <p> * <p> * This class is {@link Serializable} and care should be taken with compatibility if changes are * made. * </p> * <p> * For backward and forward compatibility, {@link ObjectOutputStream#defaultWriteObject()} is * invoked when this object is serialized because it provides "enhanced flexibility" (Joshua Block * <u>Effective Java</u>, Second Edition <i>Item 75</i>). Invoking * {@link ObjectOutputStream#defaultWriteObject()} allows the flexibility to add nontransient * instance fields in later versions if necessary. If earlier versions do not have the newly added * fields, those fields will be ignored and the deserialization will still take place. In addition, * {@link ObjectInputStream#defaultReadObject()} is necessary to facilitate any of the written * fields. </p> * <p> * <p> * For what constitutes a compatible change in serialization, see <a href= * "http://docs.oracle.com/javase/6/docs/platform/serialization/spec/version.html#6678" >Sun's * Guidelines</a>. * </p> * * @author ddf.isgs@lmco.com */ public class MetacardImpl implements Metacard { private static final long serialVersionUID = 1L; private static final Logger LOGGER = LoggerFactory.getLogger(MetacardImpl.class); /** * key/value map of {@link Attribute}s. */ private transient Map<String, Attribute> map = null; private transient Metacard wrappedMetacard; private transient MetacardType type; private String sourceId; /** * Creates a {@link Metacard} with a type of {@link BasicTypes#BASIC_METACARD} and empty * {@link Attribute}s. */ public MetacardImpl() { /* * If any defensive logic is added to this constructor, then that logic should be reflected * in the deserialization (readObject()) of this object so that the integrity of a * serialized object is maintained. For instance, if a null check is added in the * constructor, the same check should be added in the readObject() method. */ this(BasicTypes.BASIC_METACARD); } /** * Creates a {@link Metacard} with the provided {@link MetacardType} and empty {@link Attribute} * s. * * @param type the {@link MetacardType} */ public MetacardImpl(MetacardType type) { /* * If any defensive logic is added to this constructor, then that logic should be reflected * in the deserialization (readObject()) of this object so that the integrity of a * serialized object is maintained. For instance, if a null check is added in the * constructor, the same check should be added in the readObject() method. */ map = new HashMap<String, Attribute>(); if (type != null) { this.type = type; } else { throw new IllegalArgumentException(MetacardType.class.getName() + " instance should not be null."); } } /** * Creates a {@link Metacard} with the provided {@link Metacard}. * * @param metacard the {@link Metacard} to create this new {@code Metacard} from */ public MetacardImpl(Metacard metacard) { /* * If any defensive logic is added to this constructor, then that logic should be reflected * in the deserialization (readObject()) of this object so that the integrity of a * serialized object is maintained. For instance, if a null check is added in the * constructor, the same check should be added in the readObject() method. */ this.wrappedMetacard = metacard; if (metacard.getMetacardType() != null) { this.type = metacard.getMetacardType(); } else { throw new IllegalArgumentException(MetacardType.class.getName() + " instance should not be null."); } } /** * Creates a {@link Metacard} with the provided {@link Metacard} and {@link MetacardType}. * This method does not simply wrap the metacard and keep its type, but will create a new * metacard and clone any attributes defined by the {@link MetacardType} that exist on the * given {@link Metacard}. * * @param metacard the {@link Metacard} to create this new {@code Metacard} from * @param type the {@link MetacardType} of metacard to create */ public MetacardImpl(Metacard metacard, MetacardType type) { if (type != null) { this.type = type; } else { throw new IllegalArgumentException(MetacardType.class.getName() + " instance should not be null."); } map = new HashMap<>(); for (AttributeDescriptor attribute : metacard.getMetacardType().getAttributeDescriptors()) { map.put(attribute.getName(), metacard.getAttribute(attribute.getName())); } } @Override public Date getCreatedDate() { return requestDate(Metacard.CREATED); } /** * Sets the date/time the {@link Metacard} was created. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#CREATED}, created)) * </code> * * @param created {@link Date} when this {@link Metacard} was created. * @see Metacard#CREATED */ public void setCreatedDate(Date created) { setAttribute(Metacard.CREATED, created); } @Override public Date getModifiedDate() { return requestDate(Metacard.MODIFIED); } /** * Sets the date/time the {@link Metacard} was last modifed. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#MODIFIED}, modified)) * </code> * * @param modified {@link Date} when this {@link Metacard} was last modified. * @see Metacard#MODIFIED */ public void setModifiedDate(Date modified) { setAttribute(Metacard.MODIFIED, modified); } @Override public Date getExpirationDate() { return requestDate(Metacard.EXPIRATION); } /** * Sets the date/time this {@link Metacard} is no longer valid and could be removed. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#EXPIRATION}, expiration)) * </code> * * @param expiration {@link Date} when the {@link Metacard} expires and should be removed from any * stores. * @see Metacard#EXPIRATION */ public void setExpirationDate(Date expiration) { setAttribute(Metacard.EXPIRATION, expiration); } @Override public Date getEffectiveDate() { return requestDate(Metacard.EFFECTIVE); } /** * Sets the date/time the {@link Metacard} was last known to be valid. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#EFFECTIVE}, effective)) * </code> * * @param effective {@link Date} when the information represented by the {@link Metacard} was last * known to be valid. * @see Metacard#EFFECTIVE */ public void setEffectiveDate(Date effective) { setAttribute(Metacard.EFFECTIVE, effective); } @Override public String getId() { return requestString(Metacard.ID); } /** * Sets the ID of the {@link Metacard}. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#ID}, id)) * </code> * * @param id unique identifier of the Metacard. * @see Metacard#ID */ public void setId(String id) { setAttribute(Metacard.ID, id); } @Override public String getLocation() { return requestString(Metacard.GEOGRAPHY); } /** * Sets the WKT representation of the geometry. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#GEOGRAPHY}, wkt)) * </code> * * @param wkt WKT-defined geospatial {@link String}, returns null if not applicable * @see Metacard#GEOGRAPHY */ public void setLocation(String wkt) { setAttribute(Metacard.GEOGRAPHY, wkt); } @Override public String getSourceId() { return wrappedMetacard != null ? wrappedMetacard.getSourceId() : sourceId; } public void setSourceId(String sourceId) { if (wrappedMetacard != null) { wrappedMetacard.setSourceId(sourceId); } else { this.sourceId = sourceId; } } @Override public byte[] getThumbnail() { return requestBytes(Metacard.THUMBNAIL); } /** * Sets the thumbnail associated with this {@link Metacard}. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#THUMBNAIL}, bytes)) * </code> * * @param bytes thumbnail for the {@link Metacard}. * @see Metacard#THUMBNAIL */ public void setThumbnail(byte[] bytes) { setAttribute(Metacard.THUMBNAIL, bytes); } @Override public String getTitle() { return requestString(Metacard.TITLE); } /** * Sets the title of this {@link Metacard}. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#TITLE}, id)) * </code> * * @param title Title of the {@link Metacard} * @see Metacard#TITLE */ public void setTitle(String title) { setAttribute(Metacard.TITLE, title); } @Override public String getMetadata() { return requestString(Metacard.METADATA); } /** * Sets the metadata associated with this {@link Metacard}. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#METADATA}, metadata)) * </code> * * @param metadata Associated metadata of the {@link Metacard} * @see Metacard#METADATA */ public void setMetadata(String metadata) { setAttribute(Metacard.METADATA, metadata); } @Override public MetacardType getMetacardType() { return type; } /** * Sets the {@link MetacardType} of the {@link Metacard}. * * @param type {@link MetacardType} of the {@link Metacard} * @see MetacardType */ public void setType(MetacardType type) { this.type = type; } @Override public URI getContentTypeNamespace() { URI uri = null; String uriString = requestString(Metacard.TARGET_NAMESPACE); if (uriString != null && !uriString.isEmpty()) { uri = URI.create(uriString); } return uri; } /** * Some types of metadata use different content types. If utilized, sets the {@link URI} of the * content type. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#TARGET_NAMESPACE}, targetNamespace)) * </code> * * @param targetNamespace {@link URI} of the sub-type, null if unused * @see Metacard#TARGET_NAMESPACE */ public void setTargetNamespace(URI targetNamespace) { setAttribute(Metacard.TARGET_NAMESPACE, targetNamespace.toASCIIString()); } @Override public String getContentTypeName() { return requestString(Metacard.CONTENT_TYPE); } /** * Sets the name of the content type of the {@link Metacard}. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#CONTENT_TYPE}, contentType)) * </code> * * @param contentType name of content type of the {@link Metacard} * @see Metacard#CONTENT_TYPE */ public void setContentTypeName(String contentType) { setAttribute(Metacard.CONTENT_TYPE, contentType); } /** * Sets the tags associated with this metacard * @param tags set of tags */ public void setTags(Set<String> tags) { setAttribute(Metacard.TAGS, new ArrayList<>(tags)); } /** * Returns the Point of Contact for the {@link Metacard} * * @return A String representing the Point of Contact for the {@link Metacard}. */ public String getPointOfContact() { return requestString(Metacard.POINT_OF_CONTACT); } /** * Sets the point of contact for the {@link Metacard}. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#POINT_OF_CONTACT}, pointOfContact)) * </code> * * @param pointOfContact the point of contact for the {@link Metacard} * @see Metacard#POINT_OF_CONTACT */ public void setPointOfContact(String pointOfContact) { setAttribute(Metacard.POINT_OF_CONTACT, pointOfContact); } /** * Returns the description for the {@link Metacard} * * @return A String representing the description for the {@link Metacard}. */ public String getDescription() { return requestString(Metacard.DESCRIPTION); } /** * Sets the description of the {@link Metacard}. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#DESCRIPTION}, description)) * </code> * * @param description the description of the {@link Metacard} * @see Metacard#DESCRIPTION */ public void setDescription(String description) { setAttribute(Metacard.DESCRIPTION, description); } @Override public String getContentTypeVersion() { return requestString(Metacard.CONTENT_TYPE_VERSION); } /** * Sets the version of the content type of the {@link Metacard}. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#CONTENT_TYPE_VERSION}, contentTypeVersion)) * </code> * * @param contentTypeVersion version of content type of the {@link Metacard} * @see Metacard#CONTENT_TYPE_VERSION */ public void setContentTypeVersion(String contentTypeVersion) { setAttribute(Metacard.CONTENT_TYPE_VERSION, contentTypeVersion); } @Override public URI getResourceURI() { URI uri = null; String data = requestString(Metacard.RESOURCE_URI); if (data != null) { try { uri = new URI(data); } catch (URISyntaxException e) { LOGGER.warn("failed parsing URI string, returning null"); } } return uri; } /** * Sets the value of this {@link Metacard}s Resource URI and in the form of a {@link URI} * Object. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#RESOURCE_URI}, uri)) * </code> * * @param uri a {@link URI} representation of the {@link Metacard}'s * {@link Metacard#RESOURCE_URI Resource URI} which itself is stored as a * {@link ddf.catalog.data.AttributeType.AttributeFormat#STRING String ddf.catalog.data.AttributeType.AttributeFormat} * @see Metacard#RESOURCE_URI */ public void setResourceURI(URI uri) { if (uri == null) { return; } setAttribute(RESOURCE_URI, uri.toString()); } @Override public String getResourceSize() { return requestString(Metacard.RESOURCE_SIZE); } /** * Sets the size of the resource which may or may not contain a unit. <br/> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#RESOURCE_SIZE}, dadSize)) * </code> * * @param dadSize {@link String} representation of the size * @see Metacard#RESOURCE_SIZE */ public void setResourceSize(String dadSize) { setAttribute(RESOURCE_SIZE, dadSize); } /** * Returns the security relevant markings on the {@link ddf.catalog.data.Metacard}. * * @return security markings */ public Map<String, List<String>> getSecurity() { return requestData(Metacard.SECURITY, HashMap.class); } /** * Sets the security markings on this {@link Metacard}. <br /> * Convenience method for <code> * {@link #setAttribute setAttribute}(new {@link AttributeImpl}({@link Metacard#SECURITY}, security)) * </code> * * @param security */ public void setSecurity(HashMap<String, List<String>> security) { setAttribute(Metacard.SECURITY, security); } /** * The brains of the operation -- does the interaction with the map or the wrapped metacard. * * @param <T> the type of the Attribute value expected * @param attributeName the name of the {@link Attribute} to retrieve * @param returnType the class that the value of the {@link ddf.catalog.data.AttributeType} is expected to be bound to * @return the value of the requested {@link Attribute} name */ protected <T> T requestData(String attributeName, Class<T> returnType) { Attribute attribute = getAttribute(attributeName); if (attribute == null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Attribute " + attributeName + " was not found, returning null"); } return null; } Serializable data = attribute.getValue(); if (returnType.isAssignableFrom(data.getClass())) { return returnType.cast(data); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug(data.getClass().toString() + " can not be assigned to " + returnType.toString()); } } return null; } /** * Get {@link Date} data from the map or wrapped metacard. <br/> * Convenience method for <code> * {@link #requestData requestData}(key, Date.class)) * </code> * * @param key the name of the {@link Attribute} to retrieve * @return the value of the requested {@link Attribute} name * @see MetacardImpl#requestData(String, Class) */ protected Date requestDate(String key) { return requestData(key, Date.class); } /** * Get {@link Double} data from the map or wrapped metacard. <br/> * Convenience method for <code> * {@link #requestData requestData}(key, Double.class)) * </code> * * @param key the name of the {@link Attribute} to retrieve * @return the value of the requested {@link Attribute} name * @see MetacardImpl#requestData(String, Class) */ protected Double requestDouble(String key) { return requestData(key, Double.class); } /** * Get {@link BinaryContent} data from the map or wrapped metacard. <br/> * Convenience method for <code> * {@link #requestData requestData}(key, BinaryContent.class)) * </code> * * @param key the name of the {@link Attribute} to retrieve * @return the value of the requested {@link Attribute} name * @see MetacardImpl#requestData(String, Class) */ protected InputStream requestInputStream(String key) { BinaryContent data = requestData(key, BinaryContent.class); if (data != null) { return data.getInputStream(); } return null; } /** * Get {@link Integer} data from the map or wrapped metacard. <br/> * Convenience method for <code> * {@link #requestData requestData}(key, Integer.class)) * </code> * * @param key the name of the {@link Attribute} to retrieve * @return the value of the requested {@link Attribute} name * @see MetacardImpl#requestData(String, Class) */ protected Integer requestInteger(String key) { return requestData(key, Integer.class); } /** * Get {@link Long} data from the map or wrapped metacard. <br/> * Convenience method for <code> * {@link #requestData requestData}(key, Long.class)) * </code> * * @param key the name of the {@link Attribute} to retrieve * @return the value of the requested {@link Attribute} name * @see MetacardImpl#requestData(String, Class) */ protected Long requestLong(String key) { return requestData(key, Long.class); } /** * Get {@link String} data from the map or wrapped metacard. <br/> * Convenience method for <code> * {@link #requestData requestData}(key, String.class)) * </code> * * @param key the name of the {@link Attribute} to retrieve * @return the value of the requested {@link Attribute} name * @see MetacardImpl#requestData(String, Class) */ protected String requestString(String key) { return requestData(key, String.class); } /** * Get {@link byte[]} data from the map or wrapped metacard. <br/> * Convenience method for <code> * {@link #requestData requestData}(key, byte[].class)) * </code> * * @param key the name of the {@link Attribute} to retrieve * @return the value of the requested {@link Attribute} name * @see MetacardImpl#requestData(String, Class) */ protected byte[] requestBytes(String key) { return requestData(key, byte[].class); } @Override public Attribute getAttribute(String name) { return (wrappedMetacard != null) ? wrappedMetacard.getAttribute(name) : map.get(name); } /** * Set an attribute via a name/value pair. * * @param name the name of the {@link Attribute} * @param value the value of the {@link Attribute} */ public void setAttribute(String name, Serializable value) { setAttribute(new AttributeImpl(name, value)); } @Override public void setAttribute(Attribute attribute) { if (attribute == null) { return; } if (wrappedMetacard != null) { wrappedMetacard.setAttribute(attribute); } else { String name = attribute.getName(); Serializable value = attribute.getValue(); if (name != null) { if (value != null) { map.put(name, attribute); } else { map.remove(name); } } } } /** * Serializes this {@link MetacardImpl} instance. * * @param stream the {@link ObjectOutputStream} that contains the object to be serialized * @throws IOException * @serialData First, all non-transient fields are written out by the default Java serialization * implementation ( {@link ObjectOutputStream#defaultWriteObject()}) . Next, the * {@link MetacardType} is written out as a {@link MetacardTypeImpl}. Then the * <i>number</i> of {@code Attribute} objects is written as an {@code int}. After * the number of objects, each {@code Attribute} object is written out. * <p> * <p> * The MetacardType object is written out as a {@link MetacardTypeImpl} object * because {@link MetacardTypeImpl} is a class that is part of the DDF API and is * guaranteed to be on the classpath when this object is deserialized. Secondly, the * {@link MetacardTypeImpl} has a trusted serialization implementation where the * object's logical representation is serialized. * </p> */ private void writeObject(ObjectOutputStream stream) throws IOException { /* * defaultWriteObject() is invoked for greater flexibility and compatibility. See the * *Serialization Note* in MetacardImpl's class Javadoc. */ stream.defaultWriteObject(); /* * Cannot allow unknown implementations of MetacardType to be serialized. Must convert them * to our implementation to guarantee it is serializing the logical representation and not * the physical representation. */ if (type instanceof MetacardTypeImpl) { stream.writeObject(type); } else { MetacardTypeImpl mt = new MetacardTypeImpl(type.getName(), type.getAttributeDescriptors()); stream.writeObject(mt); } if (map != null) { stream.writeInt(map.size()); for (Attribute attribute : this.map.values()) { stream.writeObject(attribute); } } else { if (wrappedMetacard != null && wrappedMetacard.getMetacardType() != null) { MetacardType metacardType = wrappedMetacard.getMetacardType(); List<Attribute> attributes = new ArrayList<Attribute>(); if (metacardType.getAttributeDescriptors() == null) { // no descriptors, means no attributes can be defined. // no attributes defined, means no attributes written to // disk stream.writeInt(0); } else { for (AttributeDescriptor ad : metacardType.getAttributeDescriptors()) { Attribute attribute = wrappedMetacard.getAttribute(ad.getName()); if (attribute != null) { attributes.add(attribute); } } // Must loop again because the size of the attributes list // is not known until list has been fully populated. stream.writeInt(attributes.size()); for (Attribute attribute : attributes) { stream.writeObject(attribute); } } } } } /** * Deserializes this {@link MetacardImpl}'s instance. * * @param stream the {@link ObjectInputStream} that contains the bytes of the object * @throws IOException * @throws ClassNotFoundException */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { /* * defaultReadObject() is invoked for greater flexibility and compatibility. See the * *Serialization Note* in MetacardImpl's class Javadoc. */ stream.defaultReadObject(); map = new HashMap<String, Attribute>(); wrappedMetacard = null; type = (MetacardType) stream.readObject(); if (type == null) { throw new InvalidObjectException(MetacardType.class.getName() + " instance cannot be null."); } int numElements = stream.readInt(); for (int i = 0; i < numElements; i++) { Attribute attribute = (Attribute) stream.readObject(); if (attribute != null) { AttributeDescriptor attributeDescriptor = getMetacardType() .getAttributeDescriptor(attribute.getName()); if (attributeDescriptor != null && attribute.getValue() != null) { attributeDescriptor.getType().getAttributeFormat(); attributeDescriptor.getType().getClass(); } } setAttribute(attribute); } } public int hashCode() { //TODO: add remaining fields for hashCode return new HashCodeBuilder(17, 37).append(this.getId()).append(this.getMetacardType()) .append(this.getMetadata()).toHashCode(); } //TODO: is an equals() method needed? }