/*
* JBoss, Home of Professional Open Source.
* Copyright 2009, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* 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 2.1 of
* the License, or (at your option) any later version.
*
* This software 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.system.server.profileservice.repository.clustered.metadata;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
/**
* Description of an individual item (i.e. file) in a clustered repository.
*
* @author Brian Stansberry
*/
@XmlType(name="repositoryItemType", propOrder={"timestampAsString", "originatingNode", "removed", "directory", "relativePath"})
public class RepositoryItemMetadata
implements Identifiable<List<String>>, Serializable, Comparable<RepositoryItemMetadata>
{
/** The serialVersionUID */
private static final long serialVersionUID = 7712110893517082031L;
private static final DateFormat dateFormat;
static
{
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
}
/**
* Marker value to pass to {@link #setTimestampAsString(String)} that will
* generate a timestamp equal to System.currentTimeMillis(). Useful for
* testing.
*/
public static String NOW = "NOW";
private volatile String relativePath;
private volatile long timestamp;
private volatile boolean directory;
/** The parsed elements of relativePath. Lazy initialized */
private volatile transient List<String> pathElements;
private volatile String originatingNode;
private volatile boolean removed;
private String rootName;
public static List<String> getPathElements(String path)
{
String[] elements = path.split("/");
return Collections.unmodifiableList(Arrays.asList(elements));
}
/**
* Constructor for XML parser.
*/
public RepositoryItemMetadata()
{
}
public RepositoryItemMetadata(List<String> pathElements, long timestamp, String originatingNode, boolean directory, boolean removed)
{
setDirectory(directory);
setRelativePathElements(pathElements);
setTimestamp(timestamp);
setOriginatingNode(originatingNode);
setRemoved(removed);
}
/**
* Copy constructor. Performs a deep copy of the path element list.
*
* @param toCopy the item to copy
*
* @throws NullPointerException if <code>toCopy</code> is <code>null</code>
*/
public RepositoryItemMetadata(RepositoryItemMetadata toCopy)
{
this(toCopy.getRelativePathElements(), toCopy.getTimestamp(),
toCopy.getOriginatingNode(), toCopy.isDirectory(), toCopy.isRemoved());
}
public List<String> getId()
{
return getRelativePathElements();
}
@XmlTransient
public String getRootName()
{
return rootName;
}
public void setRootName(String rootName)
{
this.rootName = rootName;
}
@XmlAttribute(name = "relative-path", required=true)
public String getRelativePath()
{
return relativePath;
}
public void setRelativePath(String path)
{
if (path != null && path.length() > 0 && '/' == path.charAt(0))
{
path = path.length() == 0 ? "" : path.substring(1);
}
this.relativePath = path;
this.pathElements = null;
}
@XmlAttribute(name = "directory")
public boolean isDirectory()
{
return directory;
}
public void setDirectory(boolean directory)
{
this.directory = directory;
if (relativePath != null)
{
if (!directory && relativePath.endsWith("/"))
{
relativePath = relativePath.substring(0, relativePath.length() - 1);
}
else if (directory && relativePath.endsWith("/") == false)
{
relativePath += "/";
}
}
}
@XmlTransient
public long getTimestamp()
{
return timestamp;
}
public void setTimestamp(long timestamp)
{
this.timestamp = timestamp;
}
@XmlAttribute(name = "timestamp", required=true)
public String getTimestampAsString()
{
Date d = new Date(timestamp);
synchronized (dateFormat)
{
return dateFormat.format(d);
}
}
public void setTimestampAsString(String timestamp)
{
if (NOW.equals(timestamp))
{
setTimestamp(System.currentTimeMillis());
}
else
{
try
{
synchronized (dateFormat)
{
Date d = dateFormat.parse(timestamp);
setTimestamp(d.getTime());
}
}
catch (ParseException e)
{
throw new RuntimeException("Failed to parse " + timestamp, e);
}
}
}
@XmlTransient
public List<String> getRelativePathElements()
{
if (pathElements == null && relativePath != null)
{
String[] elements = relativePath.split("/");
setRelativePathElements(Arrays.asList(elements));
}
return pathElements;
}
public void setRelativePathElements(List<String> pathElements)
{
if (pathElements == null)
{
this.pathElements = null;
this.relativePath = null;
}
else
{
this.pathElements = Collections.unmodifiableList(new ArrayList<String>(pathElements));
boolean first = true;
StringBuilder sb = new StringBuilder();
for (String element : pathElements)
{
if (!first)
{
sb.append('/');
}
else
{
first = false;
}
sb.append(element);
}
if (directory)
{
sb.append('/');
}
this.relativePath = sb.toString();
}
}
/**
* The name of the cluster node that propagated this version of
* the item to the cluster.
*
* @return
*/
@XmlAttribute(name = "originator", required=true)
public String getOriginatingNode()
{
return originatingNode;
}
public void setOriginatingNode(String originatingNode)
{
this.originatingNode = originatingNode;
}
@XmlAttribute(name = "removed")
public boolean isRemoved()
{
return removed;
}
public void setRemoved(boolean removed)
{
this.removed = removed;
}
/**
* Gets whether this item is a child of another item.
*
* @param other the other item. Can be <code>null</code> in which case
* this method will return <code>false</code>
*
* @return <code>true</code> if other is not <code>null</code>, is a
* {@link #isDirectory() directory} and this items path starts
* with <code>other</code>'s path.
*/
public boolean isChildOf(RepositoryItemMetadata other)
{
return other != null && other.isDirectory() && getRelativePath().startsWith(other.getRelativePath());
}
// -------------------------------------------------------------- Comparable
public int compareTo(RepositoryItemMetadata o)
{
int result = 0;
if (this != o)
{
if (this.relativePath != o.relativePath)
{
if (this.relativePath != null)
{
result = o.relativePath == null ? 1 : this.relativePath.compareTo(o.relativePath);
}
else
{
result = o.relativePath == null ? 0 : -1;
}
}
if (result == 0)
{
result = (int) (this.timestamp - o.timestamp);
}
if (result == 0 && this.directory != o.directory)
{
result = this.directory ? 1 : -1;
}
if (result == 0 && this.removed != o.removed)
{
result = this.removed ? 1 : -1;
}
if (result == 0)
{
if (this.originatingNode != null)
{
result = o.originatingNode == null ? -1 : this.originatingNode.compareTo(o.originatingNode);
}
else
{
result = o.originatingNode == null ? 0 : 1;
}
}
}
return result;
}
// -------------------------------------------------------------- Overrides
@Override
public boolean equals(Object obj)
{
boolean result = (this == obj);
if (!result && obj instanceof RepositoryItemMetadata)
{
RepositoryItemMetadata other = (RepositoryItemMetadata) obj;
result = (this.timestamp == other.timestamp)
&& this.removed == other.removed
&& this.directory == other.directory
&& safeEquals(this.getRelativePathElements(), other.getRelativePathElements())
&& safeEquals(this.originatingNode, other.originatingNode);
}
return result;
}
@Override
public int hashCode()
{
int result = 17;
result = 31 * result + ((int) (timestamp ^ (timestamp >>>32)));
result = 31 * result + (removed ? 0 : 1);
result = 31 * result + (directory ? 0 : 1);
List<String> elements = getRelativePathElements();
result = 31 * result + (elements == null ? 0 : elements.hashCode());
result = 31 * result + (originatingNode == null ? 0 : originatingNode.hashCode());
return result;
}
@Override
public String toString()
{
return new StringBuilder(getClass().getName())
.append("[path='")
.append(relativePath)
.append(",timestamp=")
.append(timestamp)
.append(",originatingNode=")
.append(originatingNode)
.append(",removed=")
.append(removed)
.append(']').toString();
}
// -------------------------------------------------------------- Private
private static boolean safeEquals(Object a, Object b)
{
return (a == b || (a != null && a.equals(b)));
}
}