/*
* 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.core.index;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.teiid.core.designer.CoreModelerPlugin;
import org.teiid.core.designer.util.CoreArgCheck;
import org.teiid.core.designer.util.CoreStringUtil;
import org.teiid.core.designer.util.FileUtil;
import org.teiid.core.designer.util.FileUtils;
import org.teiid.core.designer.util.TempDirectory;
/**
* <p>
* This selector reads in a bunch of index files that the QueryMetadataInterface implementation needs to use for looking up
* metadata. The index files could be part of a directory, and archive file or an index file by itself.
* </p>
*
* @since 8.0
*/
public class RuntimeIndexSelector extends AbstractIndexSelector {
// ############################################################################################################################
// # Constants #
// ############################################################################################################################
private static final Random random = new Random(System.currentTimeMillis());
public static final String EXTENSION_VDB = "vdb"; //$NON-NLS-1$
// ############################################################################################################################
// # Variables #
// ############################################################################################################################
protected Index[] indexes;
// indexes variable may be null so lock on this object
private Object indexesLock = new Object();
protected File vdbFile;
protected String indexDirectoryPath;
// size of file being returned by content methods
private long fileSize;
private TempDirectory tempDirectory;
// ==================================================================================
// C O N S T R U C T O R S
// ==================================================================================
/**
* Construct an instance of RuntimeIndexSelector. The given file path could point to
* <OL>
* <LI>A jar/zip file containing index files.
* <LI>A directory containing index files
* <LI>An index file
* </OL>
*
* @param filePath The location where the archive/index files are.
*/
public RuntimeIndexSelector( final String filePath ) {
CoreArgCheck.isNotNull(filePath);
this.vdbFile = new File(filePath);
CoreArgCheck.isTrue(this.vdbFile.exists(), "No file/directory exists at the given location " + filePath); //$NON-NLS-1$
checkForValidFile();
}
/**
* Construct an instance of RuntimeIndexSelector. The given file path could point to
* <OL>
* <LI>A jar/zip file containing index files.
* <LI>A directory containing index files
* <LI>An index file
* </OL>
*
* @param filePath The location where the archive/index files are.
*/
public RuntimeIndexSelector( final String vdbName,
byte[] contents ) throws IOException {
CoreArgCheck.isNotNull(vdbName);
CoreArgCheck.isNotNull(contents);
try {
save(vdbName, contents);
checkForValidFile();
} catch (IOException e) {
clearVDB();
throw e;
}
}
private void checkForValidFile() {
if (this.vdbFile.isFile()) {
CoreArgCheck.isTrue(checkValidType(this.vdbFile),
"Invalid file type, expected an archive file or an index file " + vdbFile); //$NON-NLS-1$
}
}
public RuntimeIndexSelector( final String vdbName,
InputStream contents ) throws IOException {
CoreArgCheck.isNotNull(vdbName);
CoreArgCheck.isNotNull(contents);
try {
save(vdbName, contents);
checkForValidFile();
} catch (IOException e) {
clearVDB();
throw e;
}
}
/**
* Construct an instance of RuntimeIndexSelector. The given url to a jar/zip file containing index files.
*
* @param vdbUrl The location where the archive/index files are.
*/
public RuntimeIndexSelector( final URL vdbUrl ) throws IOException {
CoreArgCheck.isNotNull(vdbUrl);
try {
save(vdbUrl);
checkForValidFile();
} catch (IOException e) {
clearVDB();
throw e;
}
}
private void save( String vdbName,
byte[] contents ) throws IOException {
String vdbFilePath = getIndexDirectoryPath() + FileUtils.SEPARATOR + vdbName;
vdbFile = new File(vdbFilePath);
FileUtils.write(contents, vdbFile);
}
private void save( String vdbName,
InputStream contents ) throws IOException {
String vdbFilePath = getIndexDirectoryPath() + FileUtils.SEPARATOR + vdbName;
vdbFile = new File(vdbFilePath);
FileUtils.write(contents, vdbFile);
contents.close();
}
private void save( URL vdbUrl ) throws IOException {
String vdbPath = vdbUrl.getPath();
int index = vdbPath.lastIndexOf(FileUtils.SEPARATOR);
String vdbName = vdbPath.substring(index + 1);
InputStream vdbStream = vdbUrl.openStream();
String vdbFilePath = getIndexDirectoryPath() + FileUtils.SEPARATOR + vdbName;
this.vdbFile = new File(vdbFilePath);
FileUtils.write(vdbStream, this.vdbFile);
}
// ==================================================================================
// I N T E R F A C E M E T H O D S
// ==================================================================================
/*
* @See org.teiid.designer.core.index.IndexSelector#getIndexes()
*/
@Override
public Index[] getIndexes() throws IOException {
if (indexes == null) {
// initialize by reading in the index files
// double cheking if indexes are available
synchronized (indexesLock) {
if (indexes == null) {
init();
}
}
}
return indexes;
}
// ==================================================================================
// P R O T E C T E D M E T H O D S
// ==================================================================================
/**
* Clear index files and the temporary directory location
*/
public void clearVDB() {
if (this.tempDirectory != null) {
this.tempDirectory.remove();
this.tempDirectory = null;
this.indexDirectoryPath = null;
}
if (this.indexDirectoryPath != null) {
File indexDirectory = new File(this.indexDirectoryPath);
clear(indexDirectory);
this.indexDirectoryPath = null;
}
setValid(false);
}
/**
* Recursively delete the contents or the given directory.
*
* @param directory
*/
private static void clear( File file ) {
if (file.exists()) {
if (file.isDirectory()) {
File[] indexFiles = file.listFiles();
if (indexFiles != null) {
for (int i = 0; i < indexFiles.length; i++) {
File indexFile = indexFiles[i];
clear(indexFile);
}
}
}
if (!file.delete()) {
clearOnExit(file);
}
}
}
/**
* Recursively delete the contents or the given directory.
*
* @param directory
*/
private static void clearOnExit( File file ) {
if (file.exists()) {
if (file.isDirectory()) {
File[] indexFiles = file.listFiles();
if (indexFiles != null) {
for (int i = 0; i < indexFiles.length; i++) {
File indexFile = indexFiles[i];
clearOnExit(indexFile);
}
}
}
file.deleteOnExit();
}
}
/**
* Reads the index files that are part of a Archive/directory and created MtkIndex objects.
*/
protected void init() throws IOException {
if (isArchive(this.vdbFile)) {
indexes = loadIndexesFromZip(this.vdbFile);
}
// read index files in the given directory and create MtkIndexes
else if (this.vdbFile.isDirectory()) {
indexes = loadIndexesFromFolder(this.vdbFile);
}
// the file is an index file
else {
indexes = loadIndexesFromFile(this.vdbFile);
}
}
/**
* Return the MtkIndex[] containing the specified file if it is a index file.
*
* @param file
* @return
* @throws IOException
*/
public Index[] loadIndexesFromFile( final File file ) throws IOException {
List<Index> tmp = new ArrayList<Index>();
if (SimpleIndexUtil.indexFileExists(file.getAbsolutePath())) {
tmp.add(new Index(file.getAbsolutePath(), true));
}
return tmp.toArray(new Index[tmp.size()]);
}
/**
* Return the MtkIndex[] constructed from index files found in the specified folder
*
* @param folder
* @return
* @throws IOException
*/
public Index[] loadIndexesFromFolder( final File folder ) throws IOException {
List<Index> tmp = new ArrayList<Index>();
File[] files = folder.listFiles();
for (int i = 0; i < files.length; i++) {
if (SimpleIndexUtil.indexFileExists(files[i].getAbsolutePath())) {
tmp.add(new Index(files[i].getAbsolutePath(), true));
}
}
return tmp.toArray(new Index[tmp.size()]);
}
/**
* Return the MtkIndex[] constructed from index files found in the specified archive
*
* @param folder
* @return
* @throws IOException
*/
public Index[] loadIndexesFromZip( final File zip ) throws IOException {
// the zip file that would be initialized
// if the file being read is an archive
ZipFile zipFile = null;
// inputStream of the zip File
InputStream zipInputStream = null;
try {
zipFile = new ZipFile(zip);
// List of MtkIndexes, one for each entry in the zip file
List<Index> tmp = new ArrayList<Index>();
// Iterate over all entries in the zip file ...
for (final Enumeration entries = zipFile.entries(); entries.hasMoreElements();) {
// nee to read the entry and write an index file
// to a temporary location
ZipEntry entry = (ZipEntry)entries.nextElement();
// extract the entry only if it is an index file or XSD file
if (this.shouldExtract(entry)) {
// read the contents of the entry
zipInputStream = zipFile.getInputStream(entry);
// Buffer that would contain the contents of the entry
int length = entry.getSize() >= 0 ? (int)entry.getSize() : FileUtils.DEFAULT_BUFFER_SIZE;
// create a file at the temporary location writing
// the contents of the zip entry to this file
File entryFile = new File(getIndexDirectoryPath(), entry.getName());
if (entry.isDirectory()) {
entryFile.mkdirs();
} else {
FileUtils.write(zipInputStream, entryFile, length);
}
if (SimpleIndexUtil.indexFileExists(entryFile)) {
tmp.add(new Index(entryFile.getAbsolutePath(), true));
}
// else if (IndexUtil.isIndexFile(entryFile)) {
// TODO: May need to log an error if index file is of zero length
// }
}
}
return tmp.toArray(new Index[tmp.size()]);
} finally {
if (zipFile != null) {
zipFile.close();
}
if (zipInputStream != null) {
zipInputStream.close();
}
}
}
/**
* Return true if the speciifed entry should be extracted from the archive
*
* @param entry
* @return
*/
private boolean shouldExtract( final ZipEntry entry ) {
// if (entry != null) {
// // get the entry that is an index file or XSD file
// final IPath zipEntryPath = new Path(entry.getName());
// final String extension = zipEntryPath.getFileExtension();
// if (extension.equalsIgnoreCase(IndexConstants.INDEX_EXT) || extension.equalsIgnoreCase(StringConstants.XSD)) {
// return true;
// }
// }
// we need to be able to access any file in the vdb
// so extract all entries
return true;
}
/**
* @see org.teiid.designer.core.index.IndexSelector#getFilePaths()
* @since 4.2
*/
@Override
public String[] getFilePaths() {
ZipFile zipFile = null;
try {
zipFile = new ZipFile(this.vdbFile);
List filePaths = new ArrayList();
// Iterate over all entries in the zip file ...
for (final Enumeration entries = zipFile.entries(); entries.hasMoreElements();) {
// nee to read the entry and write an index file
// to a temporary location
ZipEntry entry = (ZipEntry)entries.nextElement();
String entryName = entry.getName();
// alays starts with file seperator
char firstChar = entryName.charAt(0);
if (firstChar != FileUtils.SEPARATOR) {
entryName = FileUtils.SEPARATOR + entryName;
}
filePaths.add(entryName);
}
// sort it just to be consistent on the order we return
Collections.sort(filePaths);
// get an array of paths from the collection
return (String[])filePaths.toArray(new String[filePaths.size()]);
} catch (IOException e) {
CoreModelerPlugin.Util.log(e);
} finally {
if (zipFile != null)
try {
zipFile.close();
} catch (IOException ex) {
// Nothing required
}
}
return super.getFilePaths();
}
/**
* Read the contents of the files at the specefied paths in the index directory and return the contents as String in a
* collection.
*/
@Override
public List getFileContentsAsString( List paths ) {
CoreArgCheck.isNotEmpty(paths);
List contents = new ArrayList(paths.size());
for (final Iterator pathIter = paths.iterator(); pathIter.hasNext();) {
String relativePath = (String)pathIter.next();
String fileContent = getFileContentAsString(relativePath);
if (fileContent != null) {
contents.add(fileContent);
}
}
return contents;
}
/**
* @see org.teiid.designer.core.index.IndexSelector#getFileContent(java.lang.String)
* @since 4.2
*/
@Override
public InputStream getFileContent( String path ) {
CoreArgCheck.isNotNull(path);
File file = new File(getIndexDirectoryPath(), path);
if (file.exists()) {
this.fileSize = file.length();
try {
// return contents of the file as inputstream
return new FileInputStream(file);
} catch (IOException e) {
CoreModelerPlugin.Util.log(e);
}
}
return null;
}
/**
* @see org.teiid.designer.core.index.IndexSelector#getFile(java.lang.String)
* @since 4.2
*/
@Override
public File getFile( String path ) {
CoreArgCheck.isNotNull(path);
File file = new File(getIndexDirectoryPath(), path);
if (file.exists()) {
return file;
}
return null;
}
/**
* @see org.teiid.designer.core.index.IndexSelector#getFileContentAsString(java.lang.String)
* @since 4.2
*/
@Override
public String getFileContentAsString( String path ) {
CoreArgCheck.isNotNull(path);
File file = new File(getIndexDirectoryPath(), path);
if (file.exists()) {
// Read the contents of a file into a string
try {
String fileContent = FileUtil.readSafe(file);
this.fileSize = fileContent.length();
return fileContent;
} catch (Exception e) {
// return null if file does not exist
}
}
return null;
}
/**
* @see org.teiid.designer.core.index.IndexSelector#getFileContent(java.lang.String, java.lang.String[],
* java.lang.String[])
* @since 4.2
*/
@Override
public InputStream getFileContent( final String path,
final String[] tokens,
final String[] tokenReplacements ) {
CoreArgCheck.isNotNull(tokens);
CoreArgCheck.isNotNull(tokenReplacements);
CoreArgCheck.isEqual(tokens.length, tokenReplacements.length);
String fileContents = getFileContentAsString(path);
if (fileContents != null) {
for (int i = 0; i < tokens.length; i++) {
final String token = tokens[i];
final String tokenReplacement = tokenReplacements[i];
fileContents = CoreStringUtil.replaceAll(fileContents, token, tokenReplacement);
}
this.fileSize = fileContents.length();
return new ByteArrayInputStream(fileContents.getBytes());
}
return null;
}
/**
* @see org.teiid.designer.core.index.IndexSelector#getFileSize(java.lang.String)
* @since 4.2
*/
@Override
public long getFileSize( String path ) {
return this.fileSize;
}
/**
* Returns the location of the directory into which index files will be written
*
* @return
*/
public String getIndexDirectoryPath() {
if (this.indexDirectoryPath == null) {
this.tempDirectory = new TempDirectory(System.currentTimeMillis(), random.nextLong());
this.tempDirectory.create();
this.indexDirectoryPath = this.tempDirectory.getPath();
}
return this.indexDirectoryPath;
}
protected void setIndexDirectoryPath( final String path ) {
this.indexDirectoryPath = path;
this.tempDirectory = null;
}
/**
* Check if the given file is of a valid file type. The valid types are A zip/jar file or a MetaMatrix model file.
*
* @param file The file whose type is being checked.
* @return true if it a valid type
*/
protected boolean checkValidType( File file ) {
String fileName = file.getName();
if (isArchive(file) || SimpleIndexUtil.isIndexFile(fileName)) {
return true;
}
return false;
}
/**
* Check if the given file is an archive, by checking the type and extension.
*
* @param file The File instance to check.
* @return true if it is an archive file.
*/
protected boolean isArchive( final File file ) {
if (file != null && file.isFile() && file.exists()) {
String fileName = file.getName();
String fileExtension = FileUtils.getExtension(file);
if (EXTENSION_VDB.equalsIgnoreCase(fileExtension) || FileUtils.isArchiveFileName(fileName)) {
return true;
}
}
return false;
}
/**
* Clean up the vdb if for some reason cleanVDB has not been called explicitly.
*
* @see java.lang.Object#finalize()
* @since 4.2
*/
@Override
protected void finalize() {
this.clearVDB();
}
}