/* * Copyright (c) 2009 The Jackson Laboratory * * This software was developed by Gary Churchill's Lab at The Jackson * Laboratory (see http://research.jax.org/faculty/churchill). * * This is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software. If not, see <http://www.gnu.org/licenses/>. */ package org.jax.qtl.project; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import javax.swing.filechooser.FileFilter; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.jax.qtl.jaxbgenerated.JQtlProjectMetadata; import org.jax.r.CleanEnvironmentCommand; import org.jax.r.RUtilities; import org.jax.r.jriutilities.RInterface; import org.jax.r.jriutilities.RInterfaceFactory; import org.jax.r.jriutilities.SilentRCommand; import org.jax.util.ConfigurationUtilities; import org.jax.util.io.FileChooserExtensionFilter; import org.jax.util.io.FileUtilities; import org.jax.util.project.Project; import org.jax.util.project.ProjectManager; /** * The QLT project manager * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A> */ public class QtlProjectManager extends ProjectManager { /** * our logger */ private static final Logger LOG = Logger.getLogger( QtlProjectManager.class.getName()); /** * the file name that is used for project metadata */ private static final String PROJECT_METADATA_FILENAME_1_0_0 = "project-metadata.xml"; /** * the file name that is used for project metadata */ private static final String PROJECT_METADATA_FILENAME_1_2_0 = "project-metadata-1.2.0.xml"; /** * the file name that is used for R data */ private static final String PROJECT_R_DATA_FILENAME = "qtl-data.RData"; /** * XSLT document resource for transforming the old 1.0.0 project metadata * to the new 1.2.0 format */ private static final String PROJECT_METADATA_1_0_0_TO_1_2_0_XSLT_RESOURCE = "/xml-transformation/jqtl-project-metadata_1.0.0_to_1.2.0.xslt"; /** * the temporary directory name that we use for short-term storage of * project data (in the long term, project data is stored in a * zip file... usually with a .jqtl extension) */ private static final String TEMP_PROJECT_DIR_NAME = "temp-proj"; /** * the singleton instance of project manager */ private static final QtlProjectManager instance = new QtlProjectManager(); /** * the extension that we expect J/qtl project files to end with */ public static final String JQTL_PROJECT_EXTENSION = "jqtl"; /** * some user level text for the filter */ public static final String FILTER_DESCRIPTION = "J/qtl Project (*.jqtl)"; private static final FileChooserExtensionFilter QTL_PROJECT_FILE_FILTER = new FileChooserExtensionFilter( JQTL_PROJECT_EXTENSION, FILTER_DESCRIPTION); /** * Get the file filter for j/qtl projects * @return * the file filter */ @Override public FileFilter getProjectFileFilter() { return QTL_PROJECT_FILE_FILTER; } /** * Getter for the singleton instance of project manager * @return * the singleton */ public static QtlProjectManager getInstance() { return QtlProjectManager.instance; } /** * The R interface that we issue commands to */ private final RInterface rInterface; /** * the jaxb context for marshalling and unmarshalling */ private JAXBContext jaxbContext; /** * Private constructor. Use {@link #getInstance()} to get a handle * on the singleton instance of this class */ private QtlProjectManager() { this.rInterface = RInterfaceFactory.getRInterfaceInstance(); try { this.jaxbContext = JAXBContext.newInstance( JQtlProjectMetadata.class); } catch(JAXBException ex) { LOG.log(Level.SEVERE, "failed to initialize project manager", ex); } this.createNewActiveProject(); } /** * {@inheritDoc} */ @Override public Project createNewActiveProject() { // clear the current r data this.rInterface.evaluateCommand(new SilentRCommand( "rm(list=ls())")); this.setActiveProjectFile(null); this.setActiveProjectModified(false); QtlProject newProject = new QtlProject(this.rInterface); this.setActiveProject(newProject); return newProject; } /** * {@inheritDoc} */ @Override public boolean loadActiveProject(File projectFile) { try { File tempProjDir = this.getCleanedTempProjectDir(); if(tempProjDir == null) { return false; } else { try { // expand project file to temp dir ZipInputStream zipIn = new ZipInputStream( new FileInputStream(projectFile)); FileUtilities.unzipToDirectory( zipIn, tempProjDir); // clear the current r data this.rInterface.evaluateCommand(new SilentRCommand( "rm(list=ls())")); // load the r data File rDataFile = new File(tempProjDir, PROJECT_R_DATA_FILENAME); this.rInterface.evaluateCommandNoReturn(new SilentRCommand( new CleanEnvironmentCommand())); String loadDataCommandString = "load(" + RUtilities.javaStringToRString(rDataFile.getAbsolutePath()) + ")"; this.rInterface.evaluateCommand(new SilentRCommand( loadDataCommandString)); // load the meta data InputStream configFileIn = this.getProjectMetadataInputStreamFromDir( tempProjDir); Unmarshaller unmarshaller = this.jaxbContext.createUnmarshaller(); JQtlProjectMetadata jaxbProjectMetatata = (JQtlProjectMetadata)unmarshaller.unmarshal(configFileIn); // create the project QtlProject newProject = new QtlProject( this.rInterface, jaxbProjectMetatata); // update and notify this.setActiveProjectFile(projectFile); this.setActiveProjectModified(false); this.setActiveProject(newProject); } finally { // blow away the temp dir FileUtilities.recursiveDelete(tempProjDir); } return true; } } catch(Exception ex) { LOG.log(Level.SEVERE, "caught exception loading project data", ex); return false; } } private InputStream getProjectMetadataInputStreamFromDir(File projDir) throws IOException, TransformerFactoryConfigurationError, TransformerException { File projMetadataFile_1_2_0 = new File( projDir, PROJECT_METADATA_FILENAME_1_2_0); if(projMetadataFile_1_2_0.exists()) { LOG.fine("Found 1.2.0 project metadata"); return new FileInputStream(projMetadataFile_1_2_0); } else { LOG.fine("Transforming 1.0.0 project metadata"); File projMetadataFile_1_0_0 = new File( projDir, PROJECT_METADATA_FILENAME_1_0_0); StreamSource xsltSource = new StreamSource( QtlProjectManager.class.getResourceAsStream( PROJECT_METADATA_1_0_0_TO_1_2_0_XSLT_RESOURCE)); Transformer transformer = TransformerFactory.newInstance().newTransformer(xsltSource); ByteArrayOutputStream transformedOutput = new ByteArrayOutputStream(); transformer.transform( new StreamSource(projMetadataFile_1_0_0), new StreamResult(transformedOutput)); return new ByteArrayInputStream(transformedOutput.toByteArray()); } } /** * {@inheritDoc} */ @Override public boolean saveActiveProject(File projectFile) { try { File tempProjDir = this.getCleanedTempProjectDir(); if(tempProjDir == null) { return false; } else { try { // create temp r data file File rDataFile = new File(tempProjDir, PROJECT_R_DATA_FILENAME); String saveDataCommandString = "save(list = ls(), file = " + RUtilities.javaStringToRString(rDataFile.getAbsolutePath()) + ")"; this.rInterface.evaluateCommand( new SilentRCommand(saveDataCommandString)); // create temp metadata file FileOutputStream configFileOut = new FileOutputStream( new File(tempProjDir, PROJECT_METADATA_FILENAME_1_2_0)); Marshaller marshaller = this.jaxbContext.createMarshaller(); marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); marshaller.marshal( this.getActiveProject().getMetadata(), configFileOut); configFileOut.close(); // zip up the directory and save it to the file ZipOutputStream zipOut = new ZipOutputStream( new FileOutputStream(projectFile)); FileUtilities.compressDirectoryToZip( tempProjDir, zipOut); zipOut.close(); // update and notify this.setActiveProjectFile(projectFile); this.setActiveProjectModified(false); } finally { // blow away the temp dir FileUtilities.recursiveDelete(tempProjDir); } return true; } } catch(Exception ex) { LOG.log(Level.SEVERE, "caught exception saving project data", ex); return false; } } /** * Get a clean version of the temporary project directory. * @return * return the project directory */ private File getCleanedTempProjectDir() { try { ConfigurationUtilities configurationUtilities = new ConfigurationUtilities(); File configDir = configurationUtilities.getBaseDirectory(); File tempProjDir = new File(configDir, TEMP_PROJECT_DIR_NAME); if(tempProjDir.exists()) { if(LOG.isLoggable(Level.FINE)) { LOG.fine( "Temporary project directory already exists: " + tempProjDir); } if(!FileUtilities.recursiveDelete(tempProjDir)) { return null; } } if(tempProjDir.mkdir()) { return tempProjDir; } else { LOG.warning( "Failed to create temporary project directory"); return null; } } catch(Exception ex) { LOG.log(Level.SEVERE, "failed to clean temporary project directory", ex); return null; } } /** * {@inheritDoc} */ @Override public QtlProject getActiveProject() { return (QtlProject)super.getActiveProject(); } /** * {@inheritDoc} */ @Override public void refreshProjectDataStructures() { this.getActiveProject().getDataModel().updateAll(); } }