/*
* Copyright 2013-2015 EMC Corporation. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* or in the "license" file accompanying this file. This file 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 com.emc.ecs.sync.model;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import javax.xml.bind.annotation.XmlTransient;
import java.io.File;
import java.util.*;
public class ObjectMetadata implements Cloneable {
public static final String METADATA_DIR = ".ecsmeta";
public static final String DIR_META_FILE = ".dirmeta";
protected String instanceClass = ObjectMetadata.class.getName();
protected boolean directory;
private String cacheControl;
private String contentDisposition;
private String contentEncoding;
private long contentLength;
private String contentType;
private Date httpExpires;
private Date modificationTime;
private Date metaChangeTime;
private Map<String, UserMetadata> userMetadata = new TreeMap<>();
protected Checksum checksum;
private Date expirationDate;
private String retentionPolicy;
private Date retentionEndDate;
/**
* Returns whether this object represents a directory or prefix. If false, assume this is a data object (even if
* size is zero). Note that if you override this method, it *CANNOT* throw an exception. This would silently break
* several logic flows.
*/
public boolean isDirectory() {
return directory;
}
public void setDirectory(boolean directory) {
this.directory = directory;
}
public ObjectMetadata withDirectory(boolean directory) {
setDirectory(directory);
return this;
}
public String getCacheControl() {
return cacheControl;
}
public void setCacheControl(String cacheControl) {
this.cacheControl = cacheControl;
}
public ObjectMetadata withCacheControl(String cacheControl) {
setCacheControl(cacheControl);
return this;
}
public String getContentDisposition() {
return contentDisposition;
}
public void setContentDisposition(String contentDisposition) {
this.contentDisposition = contentDisposition;
}
public ObjectMetadata withContentDisposition(String contentDisposition) {
setContentDisposition(contentDisposition);
return this;
}
public String getContentEncoding() {
return contentEncoding;
}
public void setContentEncoding(String contentEncoding) {
this.contentEncoding = contentEncoding;
}
public ObjectMetadata withContentEncoding(String contentEncoding) {
setContentEncoding(contentEncoding);
return this;
}
public long getContentLength() {
return contentLength;
}
public void setContentLength(long contentLength) {
this.contentLength = contentLength;
}
public ObjectMetadata withContentLength(long contentLength) {
setContentLength(contentLength);
return this;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public ObjectMetadata withContentType(String contentType) {
setContentType(contentType);
return this;
}
public Date getHttpExpires() {
return httpExpires;
}
public void setHttpExpires(Date httpExpires) {
this.httpExpires = httpExpires;
}
public ObjectMetadata withHttpExpires(Date httpExpires) {
setHttpExpires(httpExpires);
return this;
}
public Date getModificationTime() {
return modificationTime;
}
public void setModificationTime(Date modificationTime) {
this.modificationTime = modificationTime;
}
public ObjectMetadata withModificationTime(Date modificationTime) {
setModificationTime(modificationTime);
return this;
}
public Date getMetaChangeTime() {
return metaChangeTime;
}
public void setMetaChangeTime(Date metaChangeTime) {
this.metaChangeTime = metaChangeTime;
}
public ObjectMetadata withMetaChangeTime(Date metaChangeTime) {
setMetaChangeTime(metaChangeTime);
return this;
}
public Map<String, UserMetadata> getUserMetadata() {
return userMetadata;
}
@XmlTransient
public Map<String, String> getUserMetadataValueMap() {
return new StringView();
}
public String getUserMetadataValue(String key) {
UserMetadata meta = getUserMetadata().get(key);
if (meta == null) return null;
return meta.getValue();
}
public void setUserMetadata(Map<String, UserMetadata> userMetadata) {
this.userMetadata = userMetadata;
}
public void setUserMetadataValue(String key, String value) {
UserMetadata meta = getUserMetadata().get(key);
if (meta == null) {
setUserMetadataValue(key, value, false);
} else {
meta.setValue(value);
}
}
public void setUserMetadataValue(String key, String value, boolean indexed) {
getUserMetadata().put(key, new UserMetadata(key, value, indexed));
}
public Checksum getChecksum() {
return checksum;
}
public void setChecksum(Checksum checksum) {
this.checksum = checksum;
}
public Date getExpirationDate() {
return expirationDate;
}
public void setExpirationDate(Date expirationDate) {
this.expirationDate = expirationDate;
}
public String getRetentionPolicy() {
return retentionPolicy;
}
public void setRetentionPolicy(String retentionPolicy) {
this.retentionPolicy = retentionPolicy;
}
public Date getRetentionEndDate() {
return retentionEndDate;
}
public void setRetentionEndDate(Date retentionEndDate) {
this.retentionEndDate = retentionEndDate;
}
/**
* For a given object path, returns the appropriate path that should contain that
* object's Metadata container. This is a path/file with the same name inside the
* .ecsmeta subdirectory. If the object itself is a directory, it's
* ./.ecsmeta/.dirmeta.
*
* @param relativePath the relative path of the object to compute the metadata path name from
* @return the path that should contain this object's metadata. This path may not exist.
*/
public static String getMetaPath(String relativePath, boolean directory) {
String name = new File(relativePath).getName();
String base = directory ? relativePath : new File(relativePath).getParent();
return new File(new File(base, METADATA_DIR), directory ? DIR_META_FILE : name).getPath();
}
public static ObjectMetadata fromJson(String json) {
JsonObject root = new JsonParser().parse(json).getAsJsonObject();
JsonElement instanceClass = root.get("instanceClass");
if (instanceClass == null) { // might be data from an older version; make sure user is aware
throw new RuntimeException("could not parse meta-file. this could mean you used an older version of EcsSync to move data to the source location. either use that same version again or use --no-sync-user-metadata to continue using this version");
} else {
try {
return ((ObjectMetadata) Class.forName(instanceClass.getAsString()).newInstance()).createFromJson(json);
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
/**
* Override in subclasses to add fidelity
*/
protected ObjectMetadata createFromJson(String json) {
return new GsonBuilder().serializeNulls().create().fromJson(json, ObjectMetadata.class);
}
public final String toJson() {
return new GsonBuilder().serializeNulls().create().toJson(this);
}
public static class UserMetadata {
private String key;
private String value;
private boolean indexed;
public UserMetadata(String key, String value) {
this(key, value, false);
}
public UserMetadata(String key, String value, boolean indexed) {
this.key = key;
this.value = value;
this.indexed = indexed;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public boolean isIndexed() {
return indexed;
}
public void setIndexed(boolean indexed) {
this.indexed = indexed;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof UserMetadata)) return false;
UserMetadata that = (UserMetadata) o;
if (indexed != that.indexed) return false;
if (!key.equals(that.key)) return false;
if (value != null ? !value.equals(that.value) : that.value != null) return false;
return true;
}
@Override
public int hashCode() {
int result = key.hashCode();
result = 31 * result + (value != null ? value.hashCode() : 0);
result = 31 * result + (indexed ? 1 : 0);
return result;
}
}
private class StringView implements Map<String, String> {
@Override
public int size() {
return userMetadata.size();
}
@Override
public boolean isEmpty() {
return userMetadata.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return userMetadata.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return userMetadata.containsValue(value);
}
@Override
public String get(Object key) {
return getUserMetadataValue((String) key);
}
@Override
public String put(String key, String value) {
String oldValue = get(key);
setUserMetadataValue(key, value);
return oldValue;
}
@Override
public String remove(Object key) {
String oldValue = get(key);
userMetadata.remove(key);
return oldValue;
}
@Override
public void putAll(Map<? extends String, ? extends String> m) {
for (String key : m.keySet()) {
put(key, m.get(key));
}
}
@Override
public void clear() {
userMetadata.clear();
}
@Override
public Set<String> keySet() {
return userMetadata.keySet();
}
@Override
public Collection<String> values() {
throw new UnsupportedOperationException("cannot get values collection");
}
@Override
public Set<Entry<String, String>> entrySet() {
throw new UnsupportedOperationException("cannot get entry set");
}
}
}