/******************************************************************************* | |
* 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.mappingsio; | |
import java.io.BufferedInputStream; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.FileNotFoundException; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.util.Collection; | |
import java.util.Iterator; | |
import java.util.Vector; | |
import java.util.prefs.Preferences; | |
import org.eclipse.persistence.tools.workbench.mappingsio.legacy.LegacyIOFacade; | |
import org.eclipse.persistence.tools.workbench.mappingsmodel.MWModel; | |
import org.eclipse.persistence.tools.workbench.mappingsmodel.ProjectSubFileComponentContainer; | |
import org.eclipse.persistence.tools.workbench.mappingsmodel.project.MWProject; | |
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.DefaultSPIManager; | |
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.SPIManager; | |
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.SimpleSPIManager; | |
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.classfile.CFExternalClassRepositoryFactory; | |
import org.eclipse.persistence.tools.workbench.utility.ClassTools; | |
import org.eclipse.persistence.tools.workbench.utility.CollectionTools; | |
import org.eclipse.persistence.tools.workbench.utility.XMLTools; | |
import org.eclipse.persistence.tools.workbench.utility.io.FileTools; | |
import org.eclipse.persistence.tools.workbench.utility.string.StringTools; | |
import org.w3c.dom.Document; | |
import org.w3c.dom.Node; | |
/** | |
* A new instance of this class is created for each project read: | |
* MWProject project = new ProjectReader(ioManager, file, preferences, listener).read() | |
*/ | |
class ProjectReader { | |
/** The I/O manager that created this reader. */ | |
private ProjectIOManager ioManager; | |
/** The project file to be read. */ | |
private File file; | |
/** The user preferences used to configure the SPI manager. */ | |
private Preferences preferences; | |
/** A listener that will be notified whenever an "expected" file is missing. */ | |
private FileNotFoundListener listener; | |
/** | |
* A callback object that checks whether the reader | |
* should continue with the reading a legacy project. | |
*/ | |
private LegacyProjectReadCallback legacyProjectReadCallback; | |
/** An XML document corresponding to the project file. */ | |
private Document document; | |
/** The version of the schema describing the project file. */ | |
private String schemaVersion; | |
/** @see MWProject#CURRENT_PROJECT_ROOT_ELEMENT_NAME */ | |
private static final String PROJECT_ROOT_ELEMENT_NAME_4_X = "BldrProject"; | |
/** @see MWProject#CURRENT_SCHEMA_VERSION_ELEMENT_NAME */ | |
private static final String SCHEMA_VERSION_ELEMENT_NAME_5_0 = "schemaVersion"; // 9.0.4/10.0.0 | |
private static final String SCHEMA_VERSION_ELEMENT_NAME_4_X = "version"; | |
/** @see MWProject#CURRENT_SCHEMA_VERSION */ | |
private static final String SCHEMA_VERSION_7_0 = "7.0"; //11.1.X.X | |
private static final String SCHEMA_VERSION_6_0 = "6.0"; // 10.1.3.X.X | |
private static final String SCHEMA_VERSION_5_X = "5"; // 9.0.4/10.0.0 | |
private static final String SCHEMA_VERSION_4_5 = "4.5"; | |
private static final String PRODUCT_VERSION_4_6 = "4.6"; // uses SCHEMA_VERSION_4_5 | |
private static final String PRODUCT_VERSION_9_0_3 = "9.0.3"; // uses SCHEMA_VERSION_4_5 | |
// ********** constructors ********** | |
ProjectReader(ProjectIOManager ioManager, File file, Preferences preferences, FileNotFoundListener listener, LegacyProjectReadCallback legacyProjectReadCallback) { | |
super(); | |
this.ioManager = ioManager; | |
this.file = file; | |
this.preferences = preferences; | |
this.listener = listener; | |
this.legacyProjectReadCallback = legacyProjectReadCallback; | |
} | |
// ********** public stuff ********** | |
/** | |
* Read a project from the file, using the appropriate "schema". | |
* We will return null if we are reading a legacy project and the | |
* legacy project callback indicates we should not read the project. | |
*/ | |
MWProject read() { | |
// pre-parse the file to find the schema version | |
this.document = XMLTools.parse(this.file); | |
this.schemaVersion = this.schemaVersion(); | |
if (this.schemaVersion.equals(MWProject.CURRENT_SCHEMA_VERSION)) { | |
return this.readProject(); | |
} | |
return this.readLegacyProject(); | |
} | |
public String toString() { | |
return StringTools.buildToStringFor(this, this.file); | |
} | |
// ********** internal stuff ********** | |
/** | |
* return the version of the schema used for the project file | |
* and associated xml files | |
* @see ProjectIOManager#CURRENT_SCHEMA_VERSION | |
*/ | |
private String schemaVersion() { | |
Node rootNode = this.rootNode(); | |
// first try the current element name | |
// then move back in time, trying previous element names | |
Node schemaVersionNode = XMLTools.child(rootNode, MWProject.CURRENT_SCHEMA_VERSION_ELEMENT_NAME); | |
if (schemaVersionNode == null) { | |
schemaVersionNode = XMLTools.child(rootNode, SCHEMA_VERSION_ELEMENT_NAME_5_0); | |
} | |
if (schemaVersionNode == null) { | |
schemaVersionNode = XMLTools.child(rootNode, SCHEMA_VERSION_ELEMENT_NAME_4_X); | |
} | |
if (schemaVersionNode == null) { | |
throw new IllegalArgumentException(); // must not be a valid project file... | |
} | |
return XMLTools.textContent(schemaVersionNode); | |
} | |
/** | |
* return the root node of the project file | |
* @see ProjectIOManager#CURRENT_PROJECT_XML_DOCUMENT_NAME | |
*/ | |
private Node rootNode() { | |
// first try the current document name | |
// then move back in time, trying previous document names | |
Node rootNode = XMLTools.child(this.document, MWProject.CURRENT_PROJECT_ROOT_ELEMENT_NAME); | |
if (rootNode == null) { | |
rootNode = XMLTools.child(this.document, PROJECT_ROOT_ELEMENT_NAME_4_X); | |
} | |
if (rootNode == null) { | |
throw new IllegalArgumentException(); // must not be a valid project file... | |
} | |
return rootNode; | |
} | |
/** | |
* read a normal (non-legacy) project from the file and return it | |
*/ | |
private MWProject readProject() { | |
// first read in the project, but none of its components (classes, metadata, descriptors) | |
MWProject project; | |
try { | |
project = (MWProject) this.readObject(this.file); | |
} catch (IOException ex) { | |
throw new RuntimeException(ex); | |
} | |
// we don't set the save directory on legacy projects, | |
// because it will be set by the user and saved if necessary | |
ClassTools.invokeMethod(project, "setSaveDirectoryForIOManager", File.class, this.baseDirectory()); | |
// "inject" the SPIManager once we have the base project | |
this.injectSPIManager(project, new DefaultSPIManager(this.preferences, project.getName())); | |
// then use the names stored throughout the project to read up its components | |
SubComponentReader[] subComponentReaders = this.buildSubComponentReaders(project); | |
for (int i = 0; i < subComponentReaders.length; i++) { | |
subComponentReaders[i].read(); | |
} | |
// now trigger all the handles to resolve etc. | |
project.postProjectBuild(); | |
return project; | |
} | |
/** | |
* build readers for all the project's sub-components that are | |
* read in separately: classes, tables, descriptors | |
*/ | |
private SubComponentReader[] buildSubComponentReaders(MWProject project) { | |
return new SubComponentReader[] { | |
new SubComponentReader(project.getClassRepository()), | |
new SubComponentReader(project.getMetaDataSubComponentContainer()), | |
new SubComponentReader(project.getDescriptorRepository()), | |
}; | |
} | |
/** | |
* Use TopLink to unmarshal an object from the specified XML file. | |
* Let the exceptions through so we can swallow the FileNotFoundException | |
* when reading sub-components. | |
*/ | |
Object readObject(File xmlFile) throws IOException { | |
InputStream stream = null; | |
Object object = null; | |
try { | |
stream = new BufferedInputStream(new FileInputStream(xmlFile)); | |
object = this.ioManager.getUnmarshaller().unmarshal(stream); | |
} finally { | |
if (stream != null) { | |
stream.close(); | |
} | |
} | |
return object; | |
} | |
/** | |
* return the base directory for all the project files; | |
* the project file is in this directory, while all the other | |
* files are in subdirectories of this directory | |
*/ | |
File baseDirectory() { | |
return this.file.getParentFile(); | |
} | |
String defaultFileNameExtension() { | |
return this.ioManager.defaultFileNameExtension(); | |
} | |
String subDirectoryNameFor(Object container) { | |
return this.ioManager.subDirectoryNameFor(container); | |
} | |
void fireFileNotFound(File missingFile) { | |
this.ioManager.fireFileNotFound(this.listener, missingFile); | |
} | |
private void injectSPIManager(MWProject project, SPIManager spiManager) { | |
ClassTools.invokeMethod(project, "setSPIManagerForIOManager", SPIManager.class, spiManager); | |
} | |
private MWProject readLegacyProject() { | |
MWProject project; | |
if (this.schemaVersion.startsWith(SCHEMA_VERSION_6_0)) { | |
this.legacyProjectReadCallback.checkLegacyRead(this.schemaVersion); | |
project = LegacyIOFacade.read60Project(file, preferences); | |
} else { | |
// must be an unsupported version... | |
throw new IllegalStateException(this.schemaVersion); | |
} | |
// legacy projects are marked dirty, forcing the user to save it in a new location | |
project.markEntireBranchDirty(); | |
project.setIsLegacyProject(true); | |
return project; | |
} | |
/** | |
* this SPI manager is used for pre-6.0 projects; | |
* it ignores any user preferences | |
*/ | |
private SPIManager buildSimpleSPIManager() { | |
SimpleSPIManager mgr = new SimpleSPIManager(); | |
mgr.setExternalClassRepositoryFactory(CFExternalClassRepositoryFactory.instance()); | |
return mgr; | |
} | |
// ********** inner classes ********** | |
/** | |
* Delegate sub-component-related behavior to this class. | |
*/ | |
private class SubComponentReader { | |
/** the container that will hold the sub-components once they are read */ | |
private ProjectSubFileComponentContainer container; | |
SubComponentReader(ProjectSubFileComponentContainer container) { | |
this.container = container; | |
} | |
void read() { | |
String ext = ProjectReader.this.defaultFileNameExtension(); | |
// build the sub-directory that holds the sub-components | |
String subDirectoryName = ProjectReader.this.subDirectoryNameFor(this.container); | |
File subDirectory = new File(this.baseDirectory(), subDirectoryName); | |
// the sub-component names are set by TopLink when the project is read; | |
// and reset by ProjectWriter when the project is saved | |
Collection names = CollectionTools.set(this.container.originalProjectSubFileComponentNames()); | |
// now use the sub-component names to read in the actual sub-components | |
Collection subComponents = new Vector(names.size()); | |
for (Iterator stream = names.iterator(); stream.hasNext(); ) { | |
String name = (String) stream.next(); | |
String fileName = FileTools.FILE_NAME_ENCODER.encode(name); | |
File subFile = new File(subDirectory, fileName + ext); | |
MWModel subComponent = (MWModel) this.readObject(subFile); | |
if (subComponent == null) { | |
ProjectReader.this.fireFileNotFound(subFile); | |
stream.remove(); // keep the list of names in synch with the files | |
} else { | |
subComponent.setParent((org.eclipse.persistence.tools.workbench.utility.node.Node) this.container); | |
subComponents.add(subComponent); | |
} | |
} | |
// and finally, put the sub-components into the container | |
this.container.setProjectSubFileComponents(subComponents); | |
} | |
private File baseDirectory() { | |
return ProjectReader.this.baseDirectory(); | |
} | |
private Object readObject(File xmlFile) { | |
try { | |
return ProjectReader.this.readObject(xmlFile); | |
} catch (FileNotFoundException ex) { | |
return null; | |
} catch (IOException ex) { | |
throw new RuntimeException(ex); | |
} | |
} | |
} | |
} |