/******************************************************************************* | |
* Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved. | |
* This program and the accompanying materials are made available under the | |
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* Contributors: | |
* Oracle - initial API and implementation from Oracle TopLink | |
******************************************************************************/ | |
package org.eclipse.persistence.tools.workbench.platformsmodel; | |
import java.io.File; | |
import java.net.URISyntaxException; | |
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.Iterator; | |
import java.util.List; | |
import java.util.Set; | |
import java.util.Vector; | |
import org.eclipse.persistence.tools.workbench.utility.ClassTools; | |
import org.eclipse.persistence.tools.workbench.utility.CollectionTools; | |
import org.eclipse.persistence.tools.workbench.utility.HashBag; | |
import org.eclipse.persistence.tools.workbench.utility.XMLTools; | |
import org.eclipse.persistence.tools.workbench.utility.events.ChangeNotifier; | |
import org.eclipse.persistence.tools.workbench.utility.events.DefaultChangeNotifier; | |
import org.eclipse.persistence.tools.workbench.utility.io.FileTools; | |
import org.eclipse.persistence.tools.workbench.utility.iterators.CloneIterator; | |
import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator; | |
import org.eclipse.persistence.tools.workbench.utility.node.AbstractNodeModel; | |
import org.w3c.dom.Document; | |
import org.w3c.dom.Node; | |
/** | |
* This is a repository of all the database platforms in the platforms | |
* resource directory. We also hold on to the JDBC type repository. | |
*/ | |
public final class DatabasePlatformRepository | |
extends AbstractNodeModel | |
{ | |
/** something worth displaying */ | |
private String name; | |
public static final String NAME_PROPERTY = "name"; | |
/** the file that holds the platform repository settings and the JDBC type repository */ | |
private File file; | |
public static final String FILE_PROPERTY = "file"; | |
/** the database platforms */ | |
private Collection platforms; | |
public static final String PLATFORMS_COLLECTION = "platforms"; | |
/** the default database platform */ | |
private DatabasePlatform defaultPlatform; | |
public static final String DEFAULT_PLATFORM_PROPERTY = "defaultPlatform"; | |
/** used to map Java and JDBC types to each other */ | |
private JDBCTypeRepository jdbcTypeRepository; | |
// the JDBC type repository is never replaced once the platform repository is built | |
/** used to notifier listeners of changes */ | |
private ChangeNotifier changeNotifier; | |
/** used to generate problems */ | |
private Validator validator; | |
/** | |
* store the file names, so we can determine what to delete on save; | |
* this is transient and for internal use only | |
*/ | |
private Collection originalPlatformShortFileNames; | |
/** | |
* store the original file when it is renamed (but not moved), | |
* so we can delete it on save; | |
* this is transient and for internal use only | |
*/ | |
private File originalFile; | |
/** | |
* the name of the directory that holds the platform XML files, | |
* relative to the repository file's location | |
*/ | |
private static final String PLATFORMS_DIRECTORY_NAME = "platforms"; | |
/** | |
* the name of the default database platform repository file, | |
* it should be on the classpath | |
*/ | |
private static final String DEFAULT_PLATFORM_REPOSITORY_FILE_NAME = "platforms.dpr"; | |
/** | |
* the default database platform repository, built from the file | |
* named above | |
*/ | |
private static DatabasePlatformRepository defaultRepository; | |
// ********** static methods ********** | |
/** | |
* return the default database platform repository, which is built | |
* from the file platforms.dpr found on the classpath | |
*/ | |
public static DatabasePlatformRepository getDefault() { | |
if (defaultRepository == null) { | |
defaultRepository = buildDefault(); | |
} | |
return defaultRepository; | |
} | |
private static DatabasePlatformRepository buildDefault() { | |
try { | |
return new DatabasePlatformRepository(buildDefaultFile()); | |
} catch (CorruptXMLException ex) { | |
throw new RuntimeException(ex); | |
} | |
} | |
private static File buildDefaultFile() { | |
try { | |
return FileTools.resourceFile("/" + DEFAULT_PLATFORM_REPOSITORY_FILE_NAME); | |
} catch (URISyntaxException ex) { | |
throw new RuntimeException(ex); | |
} | |
} | |
// ********** constructors ********** | |
/** | |
* (when reading in an existing repository) | |
* clients must specify where we find the file... | |
*/ | |
public DatabasePlatformRepository(File file) throws CorruptXMLException { | |
super(); | |
if (file == null) { | |
throw new NullPointerException(); | |
} | |
this.file = file; | |
this.read(); | |
} | |
/** | |
* ...or clients must specify the name of the repository | |
* (when building one from scratch) | |
*/ | |
public DatabasePlatformRepository(String name) { | |
super(); | |
if (name == null) { | |
throw new NullPointerException(); | |
} | |
this.name = name; | |
this.jdbcTypeRepository = new JDBCTypeRepository(this); | |
this.originalPlatformShortFileNames = Collections.EMPTY_SET; | |
} | |
// ********** initialization ********** | |
protected void initialize() { | |
super.initialize(); | |
this.platforms = new Vector(); | |
this.changeNotifier = DefaultChangeNotifier.instance(); | |
this.validator = NULL_VALIDATOR; | |
} | |
// ********** accessors ********** | |
public String getName() { | |
return this.name; | |
} | |
public void setName(String name) { | |
if (name == null) { | |
throw new NullPointerException(); | |
} | |
Object old = this.name; | |
this.name = name; | |
this.firePropertyChanged(NAME_PROPERTY, old, name); | |
} | |
/** | |
* this will only be null on a newly-created, unsaved repository | |
*/ | |
public File getFile() { | |
return this.file; | |
} | |
public void setFile(File file) { | |
if (file == null) { | |
throw new NullPointerException(); | |
} | |
File old = this.file; | |
this.file = file; | |
this.firePropertyChanged(FILE_PROPERTY, old, file); | |
if ((old != null) && old.exists()) { | |
if (old.getParentFile().equals(file.getParentFile())) { | |
// if the file was renamed but not moved, save the original for later deletion | |
if ( ! old.getName().equals(file.getName())) { | |
this.originalFile = old; | |
} | |
} else { | |
// if the location of the file has changed, mark everything dirty... | |
this.markEntireBranchDirty(); | |
// ...and clear out the original file names, since we won't be deleting them on save | |
this.originalPlatformShortFileNames = Collections.EMPTY_SET; | |
} | |
} | |
} | |
public Iterator platforms() { | |
return new CloneIterator(this.platforms) { | |
protected void remove(Object current) { | |
DatabasePlatformRepository.this.removePlatform((DatabasePlatform) current); | |
} | |
}; | |
} | |
public int platformsSize() { | |
return this.platforms.size(); | |
} | |
/** | |
* the file name is the "short" file name - it should not include the directory; | |
* since the file name is no longer derived from the platform name, | |
* we need to specify both the name of the platform and where | |
* it will be stored, both of which will be checked for uniqueness | |
*/ | |
public DatabasePlatform addPlatform(String platformName, String platformShortFileName) { | |
this.checkPlatform(platformName, platformShortFileName); | |
return this.addPlatform(new DatabasePlatform(this, platformName, platformShortFileName)); | |
} | |
private DatabasePlatform addPlatform(DatabasePlatform platform) { | |
this.addItemToCollection(platform, this.platforms, PLATFORMS_COLLECTION); | |
// the repository itself is not "dirty", but its branch is | |
this.markBranchDirty(); | |
if (this.defaultPlatform == null) { | |
this.setDefaultPlatform(platform); | |
} | |
return platform; | |
} | |
public void removePlatform(DatabasePlatform platform) { | |
this.removeItemFromCollection(platform, this.platforms, PLATFORMS_COLLECTION); | |
// the repository itself is not "dirty", but its branch is | |
this.markBranchDirty(); | |
this.resetDefaultPlatform(); | |
} | |
public void removePlatforms(Collection pforms) { | |
this.removeItemsFromCollection(pforms, this.platforms, PLATFORMS_COLLECTION); | |
// the repository itself is not "dirty", but its branch is | |
this.markBranchDirty(); | |
this.resetDefaultPlatform(); | |
} | |
public void removePlatforms(Iterator pforms) { | |
this.removeItemsFromCollection(pforms, this.platforms, PLATFORMS_COLLECTION); | |
// the repository itself is not "dirty", but its branch is | |
this.markBranchDirty(); | |
this.resetDefaultPlatform(); | |
} | |
/** | |
* this will only be null when we have no platforms | |
*/ | |
public DatabasePlatform getDefaultPlatform() { | |
return this.defaultPlatform; | |
} | |
/** | |
* the default cannot be set to null unless we have no | |
* platforms | |
*/ | |
public void setDefaultPlatform(DatabasePlatform defaultPlatform) { | |
if ((defaultPlatform == null) && (this.platforms.size() > 0)) { | |
throw new NullPointerException(); | |
} | |
Object old = this.defaultPlatform; | |
this.defaultPlatform = defaultPlatform; | |
this.firePropertyChanged(DEFAULT_PLATFORM_PROPERTY, old, defaultPlatform); | |
} | |
public JDBCTypeRepository getJDBCTypeRepository() { | |
return this.jdbcTypeRepository; | |
} | |
/** | |
* as the root node, we must implement this method | |
*/ | |
public ChangeNotifier getChangeNotifier() { | |
return this.changeNotifier; | |
} | |
/** | |
* allow clients to install another change notifier | |
*/ | |
public void setChangeNotifier(ChangeNotifier changeNotifier) { | |
this.changeNotifier = changeNotifier; | |
} | |
/** | |
* as the root node, we must implement this method | |
*/ | |
public Validator getValidator() { | |
return this.validator; | |
} | |
/** | |
* allow clients to install an active validator | |
*/ | |
public void setValidator(Validator validator) { | |
this.validator = validator; | |
} | |
// ********** queries ********** | |
private File platformsDirectory() { | |
return new File(this.file.getParentFile(), PLATFORMS_DIRECTORY_NAME); | |
} | |
public DatabasePlatform platformNamed(String databasePlatformName) { | |
synchronized (this.platforms) { | |
for (Iterator stream = this.platforms.iterator(); stream.hasNext(); ) { | |
DatabasePlatform platform = (DatabasePlatform) stream.next(); | |
if (platform.getName().equals(databasePlatformName)) { | |
return platform; | |
} | |
} | |
throw new IllegalArgumentException("missing database platform named: " + databasePlatformName); | |
} | |
} | |
public DatabasePlatform platformForRuntimePlatformClassNamed(String runtimePlatformClassName) { | |
synchronized (this.platforms) { | |
for (Iterator stream = this.platforms.iterator(); stream.hasNext(); ) { | |
DatabasePlatform platform = (DatabasePlatform) stream.next(); | |
if (platform.getRuntimePlatformClassName().equals(runtimePlatformClassName)) { | |
return platform; | |
} | |
} | |
throw new IllegalArgumentException("missing database platform for run-time platform class: " + runtimePlatformClassName); | |
} | |
} | |
private Iterator platformNames() { | |
return new TransformationIterator(this.platforms()) { | |
protected Object transform(Object next) { | |
return ((DatabasePlatform) next).getName(); | |
} | |
}; | |
} | |
private Iterator platformShortFileNames() { | |
return new TransformationIterator(this.platforms()) { | |
protected Object transform(Object next) { | |
return ((DatabasePlatform) next).getShortFileName(); | |
} | |
}; | |
} | |
private Iterator lowerCasePlatformShortFileNames() { | |
return new TransformationIterator(this.platformShortFileNames()) { | |
protected Object transform(Object next) { | |
return ((String) next).toLowerCase(); | |
} | |
}; | |
} | |
// ********** behavior ********** | |
protected void addChildrenTo(List children) { | |
super.addChildrenTo(children); | |
synchronized (this.platforms) { children.addAll(this.platforms); } | |
children.add(this.jdbcTypeRepository); | |
} | |
protected void addTransientAspectNamesTo(Set transientAspectNames) { | |
super.addTransientAspectNamesTo(transientAspectNames); | |
transientAspectNames.add(PLATFORMS_COLLECTION); | |
} | |
protected void addProblemsTo(List currentProblems) { | |
if (this.platforms.isEmpty()) { | |
currentProblems.add(this.buildProblem("001")); | |
} | |
super.addProblemsTo(currentProblems); | |
} | |
/** | |
* check whether the default platform is still in the repository; | |
* if it's not, fix it | |
*/ | |
private void resetDefaultPlatform() { | |
synchronized (this.platforms) { | |
if ( ! this.platforms.contains(this.defaultPlatform)) { | |
if (this.platforms.isEmpty()) { | |
this.setDefaultPlatform(null); | |
} else { | |
this.setDefaultPlatform((DatabasePlatform) this.platforms.iterator().next()); | |
} | |
} | |
} | |
} | |
/** | |
* add and return a clone of the specified platform; | |
* the clone will be identical to the original, except its | |
* name and file name will be slightly different | |
*/ | |
public DatabasePlatform clone(DatabasePlatform original) { | |
String originalName = original.getName(); | |
String originalFileName = original.getShortFileName(); | |
String originalFileNameBase = FileTools.stripExtension(originalFileName); | |
String originalFileNameExtension = FileTools.extension(originalFileName); | |
DatabasePlatform clone = null; | |
int cloneCount = 1; | |
boolean success = false; | |
while ( ! success) { | |
cloneCount++; | |
String cloneName = originalName + cloneCount; | |
String cloneFileName = originalFileNameBase + cloneCount + originalFileNameExtension; | |
try { | |
clone = this.addPlatform(cloneName, cloneFileName); | |
success = true; | |
} catch (IllegalArgumentException ex) { | |
String msg = ex.getMessage(); | |
if ((msg.indexOf(cloneName) != -1) || (msg.indexOf(cloneFileName) != -1)) { | |
continue; // try again | |
} | |
throw ex; // must be some other problem... | |
} | |
} | |
clone.cloneFrom(original); | |
return clone; | |
} | |
/** | |
* tell all the platforms a JDBC type has been added to the | |
* JDBC type repository, so they need to synchronize | |
*/ | |
void jdbcTypeAdded(JDBCType addedJDBCType) { | |
synchronized (this.platforms) { | |
for (Iterator stream = this.platforms.iterator(); stream.hasNext(); ) { | |
((DatabasePlatform) stream.next()).jdbcTypeAdded(addedJDBCType); | |
} | |
} | |
} | |
/** | |
* disallow duplicate platform names and files within a single repository | |
*/ | |
private void checkPlatform(DatabasePlatform platform) { | |
this.checkPlatform(platform.getName(), platform.getShortFileName()); | |
} | |
private void checkPlatform(String platformName, String platformShortFileName) { | |
this.checkPlatformName(platformName); | |
this.checkPlatformShortFileName(platformShortFileName); | |
} | |
/** | |
* check whether a platform with the same name already exists; | |
* if it does, throw an IllegalArgumentException | |
*/ | |
void checkPlatformName(String platformName) { | |
if ((platformName == null) || (platformName.length() == 0)) { | |
throw new IllegalArgumentException("platform name is required"); | |
} | |
if (CollectionTools.contains(this.platformNames(), platformName)) { | |
throw new IllegalArgumentException("duplicate platform name: " + platformName); | |
} | |
} | |
/** | |
* check whether a platform with the same file name already exists; | |
* if it does, throw an IllegalArgumentException; | |
* ignore case since Windows file names are case-insensitive - meaning | |
* we cannot have two files whose names differ only by their case | |
*/ | |
void checkPlatformShortFileName(String platformShortFileName) { | |
if ((platformShortFileName == null) || (platformShortFileName.length() == 0)) { | |
throw new IllegalArgumentException("platform short file name is required"); | |
} | |
if (FileTools.fileNameIsInvalid(platformShortFileName)) { | |
throw new IllegalArgumentException("invalid file name: " + platformShortFileName); | |
} | |
if (CollectionTools.contains(this.lowerCasePlatformShortFileNames(), platformShortFileName.toLowerCase())) { | |
throw new IllegalArgumentException("duplicate file name: " + platformShortFileName); | |
} | |
} | |
// ********** i/o ********** | |
// ***** read | |
private void read() throws CorruptXMLException { | |
Document document = XMLTools.parse(this.file); | |
Node root = XMLTools.child(document, "platforms"); | |
if (root == null) { | |
throw this.buildCorruptXMLException("missing root node: platforms"); | |
} | |
this.name = XMLTools.childTextContent(root, "name", null); | |
if ((this.name == null) || (this.name.length() == 0)) { | |
throw this.buildCorruptXMLException("name is required"); | |
} | |
ClassTools.setFieldValue(this, "comment", XMLTools.childTextContent(root, "comment", "")); | |
// read up the JDBC repository first, since the JDBC types are referenced elsewhere | |
this.jdbcTypeRepository = new JDBCTypeRepository(this, XMLTools.child(root, "jdbc-type-repository")); | |
this.readPlatforms(); | |
String defaultPlatformName = XMLTools.childTextContent(root, "default-platform", null); | |
if ((defaultPlatformName == null) || (defaultPlatformName.length() == 0)) { | |
if (this.platforms.size() == 0) { | |
// no problem | |
} else { | |
throw this.buildCorruptXMLException("default platform name is required"); | |
} | |
} else { | |
if (this.platforms.size() == 0) { | |
throw this.buildCorruptXMLException("default platform should not be specified when there are no platforms"); | |
} | |
try { | |
this.defaultPlatform = this.platformNamed(defaultPlatformName); | |
} catch (IllegalArgumentException ex) { | |
throw this.buildCorruptXMLException(ex); | |
} | |
} | |
// now save all the platform file names for later | |
this.originalPlatformShortFileNames = CollectionTools.collection(this.platformShortFileNames()); | |
this.markEntireBranchClean(); | |
} | |
/** | |
* read in all the platform files | |
*/ | |
private void readPlatforms() throws CorruptXMLException { | |
File platformsDirectory = this.platformsDirectory(); | |
if (platformsDirectory.exists() && platformsDirectory.isDirectory()) { | |
File[] platformFiles = platformsDirectory.listFiles(); | |
for (int i = platformFiles.length; i-- > 0; ) { | |
this.readPlatform(platformFiles[i]); | |
} | |
} | |
} | |
/** | |
* read only files with an extension of .xml | |
*/ | |
private void readPlatform(File platformFile) throws CorruptXMLException { | |
if (platformFile.isFile() && FileTools.extension(platformFile).toLowerCase().equals(".xml")) { | |
DatabasePlatform platform = new DatabasePlatform(this, platformFile); | |
try { | |
this.checkPlatform(platform); // check for duplicates | |
} catch (IllegalArgumentException ex) { | |
throw this.buildCorruptXMLException(ex); | |
} | |
this.platforms.add(platform); | |
} | |
} | |
/** | |
* tack the repository file on to the message | |
*/ | |
private CorruptXMLException buildCorruptXMLException(String message) { | |
return new CorruptXMLException(message + " (" + this.file.getPath() + ")"); | |
} | |
/** | |
* tack the repository file on to the message | |
*/ | |
private CorruptXMLException buildCorruptXMLException(Throwable t) { | |
return new CorruptXMLException(this.file.getPath(), t); | |
} | |
// ***** write | |
public void write() { | |
if (this.isCleanBranch()) { | |
return; | |
} | |
if (this.file == null) { | |
throw new IllegalStateException("the repository's file must be set before it is written"); | |
} | |
// write the platforms first, that might be all we need to write out | |
this.writePlatforms(); | |
// if, after writing out all the platforms, the repository is still dirty, | |
// we need to write out the repository itself | |
if (this.isDirtyBranch()) { | |
this.writeRepositoryFile(); | |
this.markEntireBranchClean(); | |
} | |
} | |
private void writePlatforms() { | |
File platformsDirectory = this.platformsDirectory(); | |
if (platformsDirectory.exists()) { | |
if ( ! platformsDirectory.isDirectory()) { | |
throw new IllegalStateException("platforms directory is not a directory: " + platformsDirectory.getAbsolutePath()); | |
} | |
} else { | |
if ( ! platformsDirectory.mkdirs()) { | |
throw new RuntimeException("unable to create platforms directory: " + platformsDirectory.getAbsolutePath()); | |
} | |
} | |
this.deleteOldPlatformFiles(platformsDirectory); | |
synchronized (this.platforms) { | |
for (Iterator stream = this.platforms.iterator(); stream.hasNext(); ) { | |
((DatabasePlatform) stream.next()).write(platformsDirectory); | |
} | |
} | |
} | |
/** | |
* delete the platform files that were read in | |
* earlier but are no longer needed | |
*/ | |
private void deleteOldPlatformFiles(File platformsDirectory) { | |
// build the list of files to be deleted | |
Collection deletedPlatformFileNames = new HashBag(this.originalPlatformShortFileNames); | |
Collection currentPlatformFileNames = CollectionTools.collection(this.platformShortFileNames()); | |
deletedPlatformFileNames.removeAll(currentPlatformFileNames); | |
// now delete them | |
for (Iterator stream = deletedPlatformFileNames.iterator(); stream.hasNext(); ) { | |
String fileName = (String) stream.next(); | |
(new File(platformsDirectory, fileName)).delete(); | |
} | |
// reset the file names for the next write | |
this.originalPlatformShortFileNames = currentPlatformFileNames; | |
} | |
private void writeRepositoryFile() { | |
Document document = XMLTools.newDocument(); | |
Node root = document.createElement("platforms"); | |
document.appendChild(root); | |
XMLTools.addSimpleTextNode(root, "name", this.name); | |
XMLTools.addSimpleTextNode(root, "comment", (String) ClassTools.getFieldValue(this, "comment"), ""); | |
// the default platform can be null when there are no platforms | |
if (this.defaultPlatform != null) { | |
XMLTools.addSimpleTextNode(root, "default-platform", this.defaultPlatform.getName()); | |
} | |
this.jdbcTypeRepository.write(root.appendChild(document.createElement("jdbc-type-repository"))); | |
XMLTools.print(document, this.file); | |
if (this.originalFile != null) { | |
// the "original file" is only set when the repos file is renamed but not moved | |
if ( ! this.originalFile.delete()) { | |
throw new RuntimeException("unable to delete original file: " + this.originalFile.getPath()); | |
} | |
this.originalFile = null; | |
} | |
} | |
// ********** printing and displaying ********** | |
public String displayString() { | |
return this.name; | |
} | |
public void toString(StringBuffer sb) { | |
for (Iterator stream = this.platforms(); stream.hasNext(); ) { | |
DatabasePlatform platform = (DatabasePlatform) stream.next(); | |
platform.toString(sb); | |
if (stream.hasNext()) { | |
sb.append(", "); | |
} | |
} | |
} | |
} |