/*
* JBoss, Home of Professional Open Source.
*
* See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
*
* See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
*/
package org.teiid.designer.vdb;
import static org.teiid.designer.vdb.Vdb.Event.ENTRY_CHECKSUM;
import java.io.File;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import net.jcip.annotations.ThreadSafe;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.teiid.core.designer.util.ChecksumUtil;
import org.teiid.core.designer.util.FileUtils;
import org.teiid.core.designer.util.OperationUtil;
import org.teiid.core.designer.util.ZipUtil;
import org.teiid.designer.core.ModelerCore;
import org.teiid.designer.vdb.Vdb.Event;
import org.teiid.designer.vdb.manifest.EntryElement;
import org.teiid.designer.vdb.manifest.PropertyElement;
/**
*
*
* @since 8.0
*/
@ThreadSafe
public abstract class VdbEntry extends VdbUnit {
private IPath path;
private final AtomicReference<Synchronization> synchronization = new AtomicReference<Synchronization>(
Synchronization.NotApplicable);
private long checksum;
private final ReadWriteLock checksumLock = new ReentrantReadWriteLock();
/**
* @param vdb
* @param element
* @throws Exception
*/
public VdbEntry( final Vdb vdb, final EntryElement element) throws Exception {
this(vdb, Path.fromPortableString(element.getPath()));
long propChecksum = -1;
for (final PropertyElement property : element.getProperties()) {
final String name = property.getName();
if (EntryElement.CHECKSUM.equals(name)) {
propChecksum = Long.parseLong(property.getValue());
break;
}
}
if (Synchronization.Synchronized.equals(getSynchronization())) {
//
// Already been synchronized from calling 'this' above.
// Means there is a file in the workspace but don't yet know if
// it is still the same file as that in the vdb.
//
// Now check if the checksum created matches that extracted
// from the properties. If it does not then entry is not synchronized
// with workspace.
//
if (this.checksum != propChecksum)
setSynchronization( Synchronization.NotSynchronized);
}
setDescription(element.getDescription() == null ? EMPTY_STRING : element.getDescription());
}
/**
* <li>The name of the entry is the path name WITHOUT the extension
* <li>The path of the entry is available via {@link #getPath()}
*
* @param vdb
* @param path
* @throws Exception
*
*/
public VdbEntry( final Vdb vdb, final IPath path) throws Exception {
super(vdb);
this.path = path;
setName(path.removeFileExtension().lastSegment());
setSynchronization(synchronizeEntry());
}
/**
* @return path of this entry
*/
public IPath getPath() {
return path;
}
/**
* @param path
*/
public void setPath(IPath path) {
this.path = path;
super.setName(path.removeFileExtension().lastSegment());
}
/**
* @return the file name based on the path's last segment
*/
public String getPathName() {
if (path == null)
return null;
return path.lastSegment();
}
/**
* @return the parent directory represented by the given path
*/
public String getDirectory() {
if (path == null)
return null;
return path.removeLastSegments(1).toOSString();
}
private long computeChecksum( final IFile file ) throws Exception {
return OperationUtil.perform(new OperationUtil.ReturningUnreliable<Long>() {
private InputStream stream = null;
@Override
public void doIfFails() {
setSynchronization(Synchronization.NotSynchronized);
}
@Override
public void finallyDo() throws Exception {
if (stream != null) stream.close();
}
@Override
public Long tryToDo() throws Exception {
stream = file.getContents();
if (stream == null)
return -1L;
return ChecksumUtil.computeChecksum(stream).getValue();
}
});
}
/**
*
*/
public void dispose() {
new File(getVdb().getStagingFolder(), getPath().lastSegment()).delete();
}
/**
* @return <code>true</code> if the associated file exists
*/
public final boolean fileExistsInWorkspace() {
return findFileInWorkspace() != null;
}
/**
* @return the associated workspace file, or <code>null</code> if it doesn't exist
*/
public IFile findFileInWorkspace() {
IResource resource = ModelerCore.getWorkspace().getRoot().findMember(getPath());
if (resource == null) {
// Lets try a little harder since the file may be in the project but not a model resource
if (getVdb() != null && getVdb().getSourceFile() != null && getVdb().getSourceFile().getProject() != null) {
IProject vdbProject = getVdb().getSourceFile().getProject();
resource = vdbProject.findMember(getPath());
}
}
if (!(resource instanceof IFile)) {
setSynchronization(Synchronization.NotApplicable);
return null;
}
return (IFile)resource;
}
/**
* @return the checksum of this entry's associated file
*/
public final long getChecksum() {
checksumLock.readLock().lock();
try {
return checksum;
} finally {
checksumLock.readLock().unlock();
}
}
/**
* @return <code>true</code> if the associated file doesn't exist or this file entry is synchronized with the associated file,
* i.e., the entry information matches the file information.
*/
public final Synchronization getSynchronization() {
return synchronization.get();
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((this.path == null) ? 0 : this.path.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
VdbEntry other = (VdbEntry)obj;
if (this.path == null) {
if (other.path != null)
return false;
} else if (!this.path.equals(other.path))
return false;
return true;
}
/**
* @param out
* @throws Exception
*/
public void save( final ZipOutputStream out) throws Exception {
String zipName = getPath().toString();
//
// Path on Windows will be using backslashes but zip entries only
// deal with forward slashes so need to replace with them.
//
zipName = zipName.replace(DOUBLE_BACK_SLASH, FORWARD_SLASH);
// Need to strip off the leading delimeter if it exists, else a "jar" extract command will result in models
// being located at the file system "root" folder.
if (zipName.startsWith(FORWARD_SLASH)) {
zipName = zipName.substring(1, zipName.length());
}
final ZipEntry zipEntry = new ZipEntry(zipName);
zipEntry.setComment(getDescription());
save(out, zipEntry, new File(getVdb().getStagingFolder(), getPath().toOSString()));
}
/**
* @param out
* @param zipEntry
* @param file
* @throws Exception
*/
protected final void save( final ZipOutputStream out,
final ZipEntry zipEntry,
final File file) throws Exception {
ZipUtil.copy(file, zipEntry, out);
}
/**
* @param synchronization the new sychronization
*/
public void setSynchronization( final Synchronization synchronization ) {
final Synchronization oldSynchronization = getSynchronization();
if (oldSynchronization == synchronization) return;
this.synchronization.set(synchronization);
setModified(this, Event.ENTRY_SYNCHRONIZATION, oldSynchronization, synchronization);
}
/**
* @throws Exception
*/
public void synchronize() throws Exception {
if (synchronization.get() != Synchronization.NotSynchronized) return;
setSynchronization(synchronizeEntry());
}
/*
* Private since called by constructor and don't want subclasses overriding
*/
protected Synchronization synchronizeEntry() throws Exception {
final IFile workspaceFile = findFileInWorkspace();
if (workspaceFile == null) return Synchronization.NotApplicable;
long oldChecksum = 0L;
checksumLock.writeLock().lock();
try {
oldChecksum = checksum;
checksum = computeChecksum(workspaceFile);
// Copy snapshot of workspace file to VDB folder
FileUtils.copy(workspaceFile.getLocation().toFile(),
new File(getVdb().getStagingFolder(), getPath().toOSString()).getParentFile(),
true);
} finally {
checksumLock.writeLock().unlock();
}
setModified(this, ENTRY_CHECKSUM, oldChecksum, checksum);
return Synchronization.Synchronized;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public final String toString() {
final StringBuilder builder = new StringBuilder(getClass().getSimpleName());
builder.append("(name="); //$NON-NLS-1$
builder.append(getName());
builder.append(", synchronization="); //$NON-NLS-1$
builder.append(synchronization);
builder.append(", description="); //$NON-NLS-1$
builder.append(getDescription());
toString(builder);
builder.append(')');
return builder.toString();
}
/**
* Intended for a subclass to append its properties and their values, in the form ", <name>=<value>, ...", to the supplied
* string builder, which represents the entry's {@link #toString()} value. Each name-value pair, including the first, must be
* preceded by a comma followed by a space.
*
* @param builder
*/
protected void toString( final StringBuilder builder ) {
// Does Nothing
}
/**
*
*/
public enum Synchronization {
/**
* This entry is synchronized with its corresponding workspace file
*/
Synchronized,
/**
* This entry is out-of-sync with its corresponding workspace file
*/
NotSynchronized,
/**
* Synchronization is not applicable to this entry, generally because the corresponding workspace file does not exist
*/
NotApplicable;
}
}