/*
* Copyright (c) 2008-2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.db.client.model;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.util.KeyspaceUtil;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.model.valid.Length;
/**
* Base data object
*/
public abstract class DataObject implements Serializable {
static final long serialVersionUID = -5278839624050514418L;
public static final String INACTIVE_FIELD_NAME = "inactive";
private static final int DEFAULT_MIN_LABEL_LENGTH = 2;
private static final String READ_LABEL_METHOD_NAME = "getLabel";
private static final Logger _log = LoggerFactory.getLogger(DataObject.class);
// urn:<zone id>:<record uuid>
protected URI _id;
// user label
private String _label = "";
// delete marker
protected Boolean _inactive;
// status for operations in flight - updates to this field
// lives for 6 hours
protected transient OpStatusMap _status;
// track - set of fields modified
protected transient Set<String> _changed;
// track - set of fields set
protected transient Set<String> _initialized = new HashSet<>();
// tags
private transient ScopedLabelSet _tags;
// creation time
private Calendar _creationTime;
// A bitwise OR of supported internal flags that can be used
// to control or restrict specific behaviors relative to the
// data object.
private Long _internalFlags = new Long(0);
private Boolean _global;
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(_id);
out.writeObject(_label);
if (_inactive == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(_inactive);
}
out.writeObject(_creationTime);
out.writeLong(_internalFlags);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
_id = (URI) in.readObject();
_label = (String) in.readObject();
_inactive = in.readBoolean();
_creationTime = (Calendar) in.readObject();
_internalFlags = in.readLong();
_initialized = new HashSet<>();
}
/**
* get identifier
*
* @return
*/
@XmlElement
@Id
public URI getId() {
return _id;
}
/**
* set identifier
*
* @param id
*/
public void setId(URI id) {
_id = id;
_global = (KeyspaceUtil.isGlobal(this.getClass()));
}
/**
* get label
*
* @return
*/
@XmlElement(name = "name")
@PrefixIndex(cf = "LabelPrefixIndex")
@Name("label")
@Length(min = 2, max = 30)
public String getLabel() {
return _label;
}
/**
* set label
*
* @param label
*/
public void setLabel(String label) {
// COP-18886 revert this fix for Darth SP1 to unblock unmanaged volume ingestion
// it didn't really help us much if we don't fix existing records anyways.
// validateLabel(label);
_label = label;
setChanged("label");
}
private void validateLabel(String label) {
int minLength = getPrefixIndexMinLength();
if (label != null && label.length() < minLength) {
String clazzName = this.getClass().getSimpleName();
throw DatabaseException.fatals.fieldLengthTooShort(clazzName, this.getId(), READ_LABEL_METHOD_NAME, label.length(), minLength);
}
}
private int getPrefixIndexMinLength() {
int length = DEFAULT_MIN_LABEL_LENGTH;
try {
Method method = DataObject.class.getDeclaredMethod(READ_LABEL_METHOD_NAME, null);
PrefixIndex annotation = method.getAnnotation(PrefixIndex.class);
length = annotation.minChars();
} catch (Exception e) {
_log.error("get declared method error:", e);
}
return length;
}
/**
* get inactive
*
* @return
*/
@DecommissionedIndex("Decommissioned")
@XmlElement
@Name("inactive")
public Boolean getInactive() {
return (_inactive != null) && _inactive;
}
/**
* set inactive
*
* @param inactive
*/
public void setInactive(Boolean inactive) {
_inactive = inactive;
setChanged("inactive");
}
/**
* Get status
*
* @return
*/
@XmlElementWrapper(name = "operationStatus")
@Ttl(60 * 60 * 6)
@Name("status")
@ClockIndependent(Operation.class)
public OpStatusMap getOpStatus() {
if (_status == null) {
_status = new OpStatusMap();
}
return _status;
}
/**
* Set status map - overwrites the existing map
*
* @param map StringMap to set
*/
public void setOpStatus(OpStatusMap map) {
_status = map;
}
@Name("tags")
@XmlElementWrapper(name = "tags")
@ScopedLabelIndex(cf = "ScopedTagPrefixIndex")
public ScopedLabelSet getTag() {
return _tags;
}
/**
* Tag setter
*
* @param tags
*/
public void setTag(ScopedLabelSet tags) {
_tags = tags;
}
@Name("creationTime")
public Calendar getCreationTime() {
return _creationTime;
}
public void setCreationTime(Calendar creationTime) {
_creationTime = creationTime;
setChanged("creationTime");
}
/**
* This accessor is deprecated in favor of the safer
* enum-based versions and should generally only be
* used by the db serialization logic
*/
@Name("internalFlags")
@Deprecated
public Long getInternalFlags() {
return _internalFlags;
}
/**
* This accessor is deprecated in favor of the safer
* enum-based versions and should generally only be
* used by the db serialization logic
*/
@Deprecated
public void setInternalFlags(Long flags) {
if (flags == null) {
flags = new Long(0);
}
// make sure we don't trigger a column update if the flag values aren't actually changed
if (!flags.equals(_internalFlags)) {
_internalFlags = flags;
setChanged("internalFlags");
}
}
/**
* Returns true if the provided flag is set
*
* @param flag the flag to test
* @return true if set
*/
public boolean checkInternalFlags(Flag flag) {
if (flag == null) {
_log.warn("checkInternalFlags called with null argument");
return false;
}
return (_internalFlags & flag.getMask()) != 0;
}
/**
* Clear all supplied bit flags
*/
public void clearInternalFlags(Flag... flags) {
if (flags == null) {
_log.warn("clearInternalFlags called with null argument");
return;
}
long removeMask = 0;
for (Flag flag : flags) {
removeMask |= flag.getMask();
}
setInternalFlags(_internalFlags & ~removeMask);
}
/**
* Set all supplied bit flags
*/
public void addInternalFlags(Flag... flags) {
if (flags == null) {
_log.warn("addInternalFlags called with null argument");
return;
}
long addMask = 0;
for (Flag flag : flags) {
addMask |= flag.getMask();
}
setInternalFlags(_internalFlags | addMask);
}
/**
* Mark a field as modified
*
* @param field name of the field modified
*/
protected void setChanged(String field) {
if (_changed != null) {
_changed.add(field);
}
_initialized.add(field);
}
/**
* Checks if the field with the given name is marked as changed
*
* @param field name of the field to check
* @return true if modified, false otherwise
*/
public boolean isChanged(String field) {
return (_changed != null && _changed.contains(field));
}
/**
* clears changed flag for the given name
*
* @param field name of the field to check
*/
public void clearChangedValue(String field) {
if (_changed != null) {
_changed.remove(field);
}
}
/**
* mark changed flag for the given name
*
* @param field name of the field to check
*/
public void markChangedValue(String field) {
setChanged(field);
}
/**
* Checks if the field the given name was instanciated from the DB
*
* @param field name of the field to check
* @return true if modified, false otherwise
*/
public boolean isInitialized(String field) {
return (_initialized.contains(field));
}
/**
* Start tracking changes for this object
*/
public void trackChanges() {
_changed = new HashSet<String>();
}
/**
* This method will be called to check if this object is safe for deletion
* overload this method in the derived class if there is anything specific to
* check for on the object before deletion
*
* @return null if no active references, otherwise, detail type of the depedency returned
*/
public String canBeDeleted() {
return null;
}
protected DataObject() {
trackChanges();
}
/**
* Static method to create an instance of an object with the specified id
* used from db deserialize to instantiate objects
*
* @param clazz DataObject class to create
* @param id URI of the object
* @param <T> DataObject type
* @return Object created
* @throws InstantiationException
* @throws IllegalAccessException
*/
public static <T extends DataObject> DataObject createInstance(Class<T> clazz, URI id)
throws InstantiationException, IllegalAccessException {
T created = clazz.newInstance();
created.setId(id);
created._changed = null;
return created;
}
@Name("global")
public Boolean isGlobal() {
return (_global != null) && _global;
}
/**
* Bit flags that can be set on the data object to control or restrict
* behavior relative to the data object.
*
* We don't yet have the ability to serialize something like an EnumSet into
* the database, so we'll make do with defining the bits as Enum's and then
* providing some typesafe setters/getters.
*/
public static enum Flag {
INTERNAL_OBJECT(0), // 0x01
NO_METERING(1), // 0x02
NO_PUBLIC_ACCESS(2), // 0x04
SUPPORTS_FORCE(3), // 0x08
RECOVERPOINT(4), // 0x10
DELETION_IN_PROGRESS(5), // 0x20
RECOVERPOINT_JOURNAL(6), // 0x40
PARTIALLY_INGESTED(7), // 0x80
// We need to know if the user given request is for entire Application or set of Array replication groups.
// This Flag is temporarily used for replica operation on Volume Group.
// As an alternate, we can change the BlockFullCopyManager method signatures
// to accept multiple URIs but doing it will result in changes in too many Impl classes
// and the real meaning of those methods may not indicate the same.
VOLUME_GROUP_PARTIAL_REQUEST(8); // 0x100
private final long mask;
/**
* Construct an enum, using an explicit bit position rather than just using the ordinal to protect
* against future add/remove of flags
*
* @param bitPosition the bit position to represent this instance
*/
private Flag(int bitPosition) {
if (bitPosition < 0 || bitPosition > 63) {
throw new IllegalArgumentException(bitPosition + " is out of bounds for a long bit mask");
}
this.mask = 1 << bitPosition;
}
public long getMask() {
return mask;
}
}
public void filterOutNulls(Collection<? extends DataObject> args) {
if (args != null) {
Iterator<? extends DataObject> iterator = args.iterator();
while (iterator.hasNext()) {
DataObject dataObject = iterator.next();
if (dataObject == null) {
iterator.remove();
}
}
}
}
public boolean checkForNull(DataObject... args) {
boolean isNullFound = false;
if (args != null) {
for (DataObject dataObject : args) {
if (dataObject == null) {
isNullFound = true;
break;
}
}
}
return isNullFound;
}
public String forDisplay() {
if (_label != null && !_label.isEmpty()) {
return String.format("%s (%s)", _label, _id);
} else {
return _id.toString();
}
}
}