blob: 39ce0cf37784c5ff0099f7cbc39ff2d3c52503ef [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1998, 2012 Oracle. 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.db.model.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.utility.XMLTools;
import org.eclipse.persistence.tools.utility.collection.CollectionTools;
import org.eclipse.persistence.tools.utility.collection.HashBag;
import org.eclipse.persistence.tools.utility.io.FileTools;
import org.eclipse.persistence.tools.utility.iterable.LiveCloneIterable;
import org.eclipse.persistence.tools.utility.iterator.IteratorTools;
import org.eclipse.persistence.tools.utility.iterator.TransformationIterator;
import org.eclipse.persistence.tools.utility.node.AbstractNode;
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.
*
* @version 2.6
*/
@SuppressWarnings("nls")
public final class DatabasePlatformRepository
extends AbstractNode
{
/** 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<DatabasePlatform> 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
/**
* store the file names, so we can determine what to delete on save;
* this is transient and for internal use only
*/
private Collection<String> 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 **********
/**
* Returns 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(null);
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(null);
if (name == null) {
throw new NullPointerException();
}
this.name = name;
this.jdbcTypeRepository = new JDBCTypeRepository(this);
this.originalPlatformShortFileNames = Collections.emptySet();
}
// ********** initialization **********
@Override
protected void initialize() {
super.initialize();
this.platforms = new Vector<DatabasePlatform>();
}
@Override
protected void checkParent(org.eclipse.persistence.tools.utility.node.Node parentNode) {
}
// ********** 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.emptySet();
}
}
}
public Iterable<DatabasePlatform> platforms() {
return new LiveCloneIterable<DatabasePlatform>(this.platforms) {
@Override
protected void remove(DatabasePlatform current) {
DatabasePlatformRepository.this.removePlatform(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<DatabasePlatform> 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<DatabasePlatform> 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;
}
// ********** queries **********
private File platformsDirectory() {
return new File(this.file.getParentFile(), PLATFORMS_DIRECTORY_NAME);
}
public DatabasePlatform platformNamed(String databasePlatformName) {
synchronized (this.platforms) {
for (DatabasePlatform platform : this.platforms) {
if (platform.getName().equals(databasePlatformName)) {
return platform;
}
}
throw new IllegalArgumentException("missing database platform named: " + databasePlatformName);
}
}
public DatabasePlatform platformForRuntimePlatformClassNamed(String runtimePlatformClassName) {
synchronized (this.platforms) {
for (DatabasePlatform platform : this.platforms) {
if (platform.getRuntimePlatformClassName().equals(runtimePlatformClassName)) {
return platform;
}
}
throw new IllegalArgumentException("missing database platform for run-time platform class: " + runtimePlatformClassName);
}
}
private Iterator<String> platformNames() {
return new TransformationIterator<DatabasePlatform, String>(this.platforms()) {
@Override
protected String transform(DatabasePlatform next) {
return next.getName();
}
};
}
private Iterator<String> platformShortFileNames() {
return new TransformationIterator<DatabasePlatform, String>(this.platforms()) {
@Override
protected String transform(DatabasePlatform next) {
return next.getShortFileName();
}
};
}
private Iterator<String> lowerCasePlatformShortFileNames() {
return new TransformationIterator<String, String>(this.platformShortFileNames()) {
@Override
protected String transform(String next) {
return next.toLowerCase();
}
};
}
// ********** behavior **********
@Override
protected void addChildrenTo(List<org.eclipse.persistence.tools.utility.node.Node> children) {
super.addChildrenTo(children);
synchronized (this.platforms) { children.addAll(this.platforms); }
children.add(this.jdbcTypeRepository);
}
@Override
protected void addTransientAspectNamesTo(Set<String> transientAspectNames) {
super.addTransientAspectNamesTo(transientAspectNames);
transientAspectNames.add(PLATFORMS_COLLECTION);
}
/**
* 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(this.platforms.iterator().next());
}
}
}
}
/**
* Returns a clone of the specified platform;
* the clone will be identical to the original, except its
* name and file name will be slightly different
*/
@SuppressWarnings("null")
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 (DatabasePlatform platform : this.platforms) {
platform.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 (IteratorTools.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 (IteratorTools.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.getChild(document, "platforms");
if (root == null) {
throw this.buildCorruptXMLException("missing root node: platforms");
}
this.name = XMLTools.getChildTextContent(root, "name", null);
if ((this.name == null) || (this.name.length() == 0)) {
throw this.buildCorruptXMLException("name is required");
}
// read up the JDBC repository first, since the JDBC types are referenced elsewhere
this.jdbcTypeRepository = new JDBCTypeRepository(this, XMLTools.getChild(root, "jdbc-type-repository"));
this.readPlatforms();
String defaultPlatformName = XMLTools.getChildTextContent(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 (DatabasePlatform platform : this.platforms) {
platform.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<String> deletedPlatformFileNames = new HashBag<String>(this.originalPlatformShortFileNames);
Collection<String> currentPlatformFileNames = CollectionTools.collection(this.platformShortFileNames());
deletedPlatformFileNames.removeAll(currentPlatformFileNames);
// now delete them
for (String fileName : deletedPlatformFileNames) {
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);
// 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 **********
@Override
public String displayString() {
return this.name;
}
public void toString(StringBuffer sb) {
for (Iterator<DatabasePlatform> stream = this.platforms().iterator(); stream.hasNext(); ) {
DatabasePlatform platform = stream.next();
platform.toString(sb);
if (stream.hasNext()) {
sb.append(", ");
}
}
}
}