/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo 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. * * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.foundation.rm; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Enumeration; import java.util.List; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import org.jdom2.JDOMException; import org.openflexo.foundation.FlexoException; import org.openflexo.foundation.FlexoModelObject; import org.openflexo.foundation.FlexoXMLSerializableObject; import org.openflexo.foundation.utils.FlexoProgress; import org.openflexo.foundation.utils.ProjectLoadingCancelledException; import org.openflexo.foundation.utils.ProjectLoadingHandler; import org.openflexo.foundation.xml.FlexoXMLMappings; import org.openflexo.localization.FlexoLocalization; import org.openflexo.toolbox.FileUtils; import org.openflexo.toolbox.FlexoVersion; import org.openflexo.xmlcode.AccessorInvocationException; import org.openflexo.xmlcode.DuplicateSerializationIdentifierException; import org.openflexo.xmlcode.InvalidModelException; import org.openflexo.xmlcode.InvalidObjectSpecificationException; import org.openflexo.xmlcode.ModelEntity; import org.openflexo.xmlcode.ModelProperty; import org.openflexo.xmlcode.SerializationHandler; import org.openflexo.xmlcode.StringEncoder; import org.openflexo.xmlcode.XMLCoder; import org.openflexo.xmlcode.XMLDecoder; import org.openflexo.xmlcode.XMLMapping; import org.openflexo.xmlcode.XMLSerializable; /** * This class represents a File Flexo resource, where related file is an XML file A File FlexoResource represent an object handled by Flexo * Application Suite (all concerned modules), which could be stored in an XML-File, generally located in related {@link FlexoProject} * project directory. * * @author sguerin */ public abstract class FlexoXMLStorageResource<XMLRD extends XMLStorageResourceData> extends FlexoStorageResource<XMLRD> { private static final Logger logger = Logger.getLogger(FlexoXMLStorageResource.class.getPackage().getName()); protected boolean performLoadWithPreviousVersion = true; private boolean _isLoading = false; private boolean isConverting = false; /** * Stores the version which one this resource has been loaded */ private FlexoVersion _currentVersion = latestVersion(); /** * Constructor used for XML Serialization: never try to instanciate resource from this constructor * * @param builder */ public FlexoXMLStorageResource(FlexoProjectBuilder builder) { this(builder.project); builder.notifyResourceLoading(this); } public FlexoXMLStorageResource(FlexoProject aProject) { super(aProject); } @Override public synchronized boolean hasMoreRecentThanExpectedDiskUpdate() { if (_isLoading) { return false; } return super.hasMoreRecentThanExpectedDiskUpdate(); } @Override public XMLRD getResourceData() { return getResourceData(null); } /** * Return data related to this resource, as an instance of an object implementing * * @param loadingHandler * TODO * * @see org.openflexo.foundation.rm.FlexoResourceData * @return * @throws Exception */ public XMLRD getResourceData(FlexoProgress progress) { if (_resourceData == null) { try { _resourceData = loadResourceData(progress, getLoadingHandler()); // Now that the resource is loaded, we try to resolve pending EP refs getProject().resolvePendingEditionPatternReferences(); } catch (LoadXMLResourceException e) { // Warns about the exception if (logger.isLoggable(Level.WARNING)) { logger.warning("Could not load resource data for resource " + getResourceIdentifier() + " message: " + e.getMessage()); } if (logger.isLoggable(Level.FINE)) { logger.fine(e.getExtendedMessage()); e.printStackTrace(); } } catch (FlexoException e) { // Warns about the exception if (logger.isLoggable(Level.WARNING)) { logger.warning("Could not load resource data for resource " + getResourceIdentifier() + " message: " + e.getMessage()); } e.printStackTrace(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ResourceDependencyLoopException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (_resourceData == null) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Resource data for resource " + getResourceIdentifier() + " is null !"); } } return _resourceData; } /** * Load resource data by applying a special scheme handling XML versionning, ie to find right XML version of current resource file.<br> * If version of stored file is not conform to latest declared version, convert resource file and update it to latest version. * * @throws ProjectLoadingCancelledException * @throws MalformedXMLException * * @see org.openflexo.foundation.rm.FlexoResource#loadResourceData() */ @Override public XMLRD performLoadResourceData(FlexoProgress progress, ProjectLoadingHandler loadingHandler) throws LoadXMLResourceException, FlexoFileNotFoundException, ProjectLoadingCancelledException, MalformedXMLException { if (_resourceData != null) { // already loaded return _resourceData; } if (!isLoadable()) { return null; } _isLoading = true; if (progress != null) { progress.setProgress(FlexoLocalization.localizedForKey("loading") + " " + this.getName()); progress.resetSecondaryProgress(4); progress.setProgress(FlexoLocalization.localizedForKey("loading_from_disk")); } notifyResourceStatusChanged(); boolean requiresRMFileSaving = false; LoadXMLResourceException exception = null; if (logger.isLoggable(Level.FINE)) { logger.fine("Load resource data for " + getResourceIdentifier()); } if (!getFile().exists()) { recoverFile(); if (!getFile().exists()) { if (logger.isLoggable(Level.SEVERE)) { logger.severe("File " + getFile().getAbsolutePath() + " does not exist, throwing exception now!"); } throw new FlexoFileNotFoundException(this); } } XMLRD returned = null; boolean projectWasHoldingObjectRegistration = project != null && project.isHoldingProjectRegistration(); FlexoVersion[] availableVersions = getXmlMappings().getAvailableVersionsForClass(getResourceDataClass()); FlexoVersion[] availableVersionsNew = new FlexoVersion[availableVersions.length + 1]; for (int k = 0; k < availableVersions.length; k++) { availableVersionsNew[k] = availableVersions[k]; } availableVersionsNew[availableVersions.length] = getXmlVersion(); // int i = availableVersions.length-1; int i = availableVersionsNew.length - 1; boolean notCorrectelyDeserialized = true; FlexoVersion triedVersion = null; if (projectWasHoldingObjectRegistration) { project.unholdObjectRegistration(); } try { while (notCorrectelyDeserialized && (i >= 0 && performLoadWithPreviousVersion || i == availableVersionsNew.length - 1 && !performLoadWithPreviousVersion)) { // triedVersion = availableVersions[i]; triedVersion = availableVersionsNew[i]; i--; if (logger.isLoggable(Level.FINE)) { logger.fine("tried version = " + triedVersion.toString()); } FlexoXMLMappings.ClassModelVersion cmv = getXmlMappings().getClassModelVersion(getResourceDataClass(), triedVersion); if (cmv == null) { throw new LoadXMLResourceException(this, "Class model version could not be found for class '" + getResourceDataClass().getName() + "' and version " + triedVersion); } if (cmv.needsManualConversion && !(this instanceof FlexoRMResource) && !(this instanceof FlexoWorkflowResource) && !(this instanceof FlexoComponentLibraryResource) /* * Little hack because GPO used it differently with conversion * from 3.5 to 4.0 in RM */) { if (logger.isLoggable(Level.INFO)) { logger.info("This resource " + getResourceIdentifier() + " must be converted from " + triedVersion + " into " + cmv.toVersion); } if (progress != null) { progress.setProgress(FlexoLocalization.localizedForKey("converting from version ") + triedVersion + " " + FlexoLocalization.localizedForKey("to") + " " + cmv.toVersion); } isConverting = true; if (convertResourceFileFromVersionToVersion(triedVersion, cmv.toVersion)) { if (logger.isLoggable(Level.INFO)) { logger.info("Conversion from " + triedVersion + " into " + cmv.toVersion + " was successfull."); } // Load again with the new version ! try { if (project != null) { project.holdObjectRegistration(); // Anyway, we will reload it later } // TODO: project should not be used for this but rather the resource. If by any chances, the loading of resource // initiates the conversion of another one, that other resource will unset this property on the project and // there is // a good chance that objects that should not be registered will be registered in the project anyway. returned = tryToLoadResourceDataWithVersion(cmv.toVersion); requiresRMFileSaving = true; } catch (JDOMException e) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Malformed XML File: " + e.getMessage()); } return null; } catch (XMLOperationException e) { e.printStackTrace(); if (e.getCause() != null) { e.getCause().printStackTrace(); } if (exception == null) { exception = new LoadXMLResourceException(this, e.getCause() != null ? e.getCause().getMessage() : e.getMessage()); } exception.addLoadException(e); if (logger.isLoggable(Level.SEVERE)) { logger.severe("Could not load Resource " + getResourceIdentifier() + ": failed to reload after conversion!"); } throw exception; } finally { if (project != null) { project.unholdObjectRegistration(); } } triedVersion = cmv.toVersion; } else { if (logger.isLoggable(Level.WARNING)) { logger.warning("Conversion FAILED: succeeding to load Resource " + getResourceIdentifier() + " with model version " + triedVersion + " but this requires a conversion from version " + triedVersion + " to version " + cmv.toVersion + " which seem to be not implemented."); } /* * try { backwardSynchronizeWith(newerResourcesToSynchronizeWith); } catch (FlexoException e1) { if (exception == * null) { exception = new LoadXMLResourceException(this); } exception.addLoadException(new * XMLOperationException(e1, triedVersion)); } */ return returned; } } if (!triedVersion.equals(latestVersion())) { isConverting = true; if (project != null) { project.holdObjectRegistration(); } } else { isConverting = false; } try { returned = tryToLoadResourceDataWithVersion(triedVersion); } catch (JDOMException e) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Malformed XML File: " + e.getMessage()); } _isLoading = false; throw new MalformedXMLException(this, e); } catch (XMLOperationException e) { if (logger.isLoggable(Level.INFO)) { logger.info("ERROR:" + "tried version = " + triedVersion.toString()); e.getException().printStackTrace(); if (e.getException() instanceof AccessorInvocationException) { ((AccessorInvocationException) e.getException()).getTargetException().printStackTrace(); if (((AccessorInvocationException) e.getException()).getTargetException() instanceof Error) { throw new LoadXMLResourceException(this, e.getCause() != null ? e.getCause().getMessage() : e.getMessage()); } } } if (exception == null) { exception = new LoadXMLResourceException(this, e.getCause() != null ? e.getCause().getMessage() : e.getMessage()); } exception.addLoadException(e); } finally { if (!triedVersion.equals(latestVersion()) && project != null) { project.unholdObjectRegistration(); } } if (returned != null) { notCorrectelyDeserialized = false; } else { if (!loadingHandler.useOlderMappingWhenLoadingFailure(this)) { throw new LoadXMLResourceException(this, "Mapping used '" + cmv + "' could not load this resource."); } } } if (isDeleted()) { return null; } if (notCorrectelyDeserialized) { if (logger.isLoggable(Level.SEVERE)) { logger.severe("Could not load Resource " + getResourceIdentifier() + ": no valid XML model found !"); } _isLoading = false; throw exception; } else { if (logger.isLoggable(Level.FINE)) { logger.fine("Found a version to load resource " + getResourceIdentifier()); } _currentVersion = triedVersion; FlexoXMLMappings.ClassModelVersion cmv = getXmlMappings().getClassModelVersion(getResourceDataClass(), triedVersion); boolean convertToLatestVersion = false; if (cmv.needsManualConversion || !triedVersion.equals(latestVersion())) { convertToLatestVersion = loadingHandler.upgradeResourceToLatestVersion(this); } if (cmv.needsManualConversion && convertToLatestVersion) { if (logger.isLoggable(Level.INFO)) { logger.info("This resource " + getResourceIdentifier() + "must be converted from " + triedVersion + " into " + cmv.toVersion); } _resourceData = returned; if (progress != null) { progress.setProgress(FlexoLocalization.localizedForKey("converting from version ") + triedVersion + " " + FlexoLocalization.localizedForKey("to") + " " + cmv.toVersion); } isConverting = true; if (convertResourceFileFromVersionToVersion(triedVersion, cmv.toVersion)) { if (logger.isLoggable(Level.INFO)) { logger.info("Conversion from " + triedVersion + " into " + cmv.toVersion + " was successfull."); } // Load again with the new version ! try { if (!cmv.toVersion.equals(latestVersion())) { isConverting = true; if (project != null) { project.holdObjectRegistration(); } } else { isConverting = false; if (project != null) { project.unholdObjectRegistration(); } } returned = tryToLoadResourceDataWithVersion(cmv.toVersion); requiresRMFileSaving = true; } catch (JDOMException e) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Malformed XML File: " + e.getMessage()); } return null; } catch (XMLOperationException e) { e.printStackTrace(); if (e.getCause() != null) { e.getCause().printStackTrace(); } if (exception == null) { exception = new LoadXMLResourceException(this, e.getCause() != null ? e.getCause().getMessage() : e.getMessage()); } exception.addLoadException(e); if (logger.isLoggable(Level.SEVERE)) { logger.severe("Could not load Resource " + getResourceIdentifier() + ": failed to reload after conversion!"); } throw exception; } finally { if (!cmv.toVersion.equals(latestVersion()) && project != null) { project.unholdObjectRegistration(); } } triedVersion = cmv.toVersion; } else { if (logger.isLoggable(Level.WARNING)) { logger.warning("Conversion FAILED: succeeding to load Resource " + getResourceIdentifier() + " with model version " + triedVersion + " but this requires a conversion from version " + triedVersion + " to version " + cmv.toVersion + " which seem to be not implemented."); } /* * try { backwardSynchronizeWith(newerResourcesToSynchronizeWith); } catch (FlexoException e1) { if (exception == * null) { exception = new LoadXMLResourceException(this); } exception.addLoadException(new * XMLOperationException(e1, triedVersion)); } */ return returned; } } if (logger.isLoggable(Level.FINE)) { logger.fine("Succeeding to load Resource " + getResourceIdentifier() + " with model version " + triedVersion); } _resourceData = returned; if (!triedVersion.equals(latestVersion()) && convertToLatestVersion) { if (logger.isLoggable(Level.INFO)) { logger.info("Converting Resource " + getResourceIdentifier() + " to latest version " + latestVersion()); } if (progress != null) { progress.setProgress(FlexoLocalization.localizedForKey("converting from version ") + triedVersion + " " + FlexoLocalization.localizedForKey("to") + " " + latestVersion()); } isConverting = true; if (project != null) { project.holdObjectRegistration(); } if (incrementalConversionFromVersionToVersion(triedVersion, latestVersion())) { if (logger.isLoggable(Level.INFO)) { logger.info("Conversion from " + triedVersion + " into latest version :" + latestVersion() + " was successful."); } try { isConverting = false; if (project != null) { project.unholdObjectRegistration(); } returned = tryToLoadResourceDataWithVersion(latestVersion()); } catch (Exception exce) { exce.printStackTrace(); } _resourceData = returned; _isLoading = false; _currentVersion = latestVersion(); } else { if (project != null) { project.unholdObjectRegistration(); } if (logger.isLoggable(Level.WARNING)) { logger.warning("Conversion from " + triedVersion + " into latest version : " + latestVersion() + " FAILED."); } } try { if (progress != null) { progress.setProgress(FlexoLocalization.localizedForKey("saving") + " " + getName()); } saveResourceData(); requiresRMFileSaving = true; } catch (SaveXMLResourceException e) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, "Could not load Resource " + getResourceIdentifier() + ": failed to convert to new version !", e); } throw new LoadXMLResourceException(this, e.getMessage()); } catch (SaveResourcePermissionDeniedException e) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, "Could not load Resource " + getResourceIdentifier() + ": failed to convert to new version because file is read-only !", e); } throw new LoadXMLResourceException(this, e.getMessage()); } catch (SaveResourceException e) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, "Could not load Resource " + getResourceIdentifier() + ": failed to convert to new version !", e); } throw new LoadXMLResourceException(this, e.getMessage()); } } else { // _currentVersion has normally been set while converting during saveResourceData() _currentVersion = triedVersion; } // _currentVersion = triedVersion; if (requiresRMFileSaving) { try { if (logger.isLoggable(Level.FINE)) { logger.fine("Save RM file."); } if (this instanceof FlexoRMResource) { this.saveResourceData(true); } else { getProject().getFlexoXMLFileResource().saveResourceData(true); } if (logger.isLoggable(Level.FINE)) { logger.fine("RM file saving succeeded."); } } catch (SaveResourceException e1) { // Warns about the exception if (logger.isLoggable(Level.WARNING)) { logger.warning("Could not save RM file: see logs for details."); } e1.printStackTrace(); throw new LoadXMLResourceException(this, e1.getMessage()); } } } _resourceData = returned; _isLoading = false; try { _resourceData.setFlexoResource(this); } catch (DuplicateResourceException e) { // Warns about the exception if (logger.isLoggable(Level.WARNING)) { logger.warning("Exception raised: " + e.getClass().getName() + ". See console for details."); } e.printStackTrace(); } /* * try { backwardSynchronizeWith(newerResourcesToSynchronizeWith); } catch (FlexoException e1) { if (exception == null) { * exception = new LoadXMLResourceException(this); } exception.addLoadException(new XMLOperationException(e1, _currentVersion)); } */ return returned; } finally { _isLoading = false; isConverting = false; if (project != null && projectWasHoldingObjectRegistration) { project.holdObjectRegistration(); } } } /** * Converts incrementally this resource from fromVersion to toVersion. Everytime the ClassModelVersion requires a manual conversion, the * converter will call the convert method * * @param fromVersion * @param toVersion * @return */ private boolean incrementalConversionFromVersionToVersion(FlexoVersion fromVersion, FlexoVersion toVersion) { FlexoVersion[] v = getXmlMappings().getAvailableVersionsForClass(getResourceDataClass()); int i = 0; // Let's find the index of the version in the array for (; i < v.length; i++) { FlexoVersion version = v[i]; if (version.equals(fromVersion)) { break; } } // We try to convert until toVersion for (; i < v.length; i++) { if (!v[i].isGreaterThan(toVersion)) {// As long as the current version is smaller than toVersion FlexoXMLMappings.ClassModelVersion cmv = getXmlMappings().getClassModelVersion(getResourceDataClass(), v[i]); if (cmv.needsManualConversion) { if (convertResourceFileFromVersionToVersion(v[i], cmv.toVersion)) { _currentVersion = cmv.toVersion; if (logger.isLoggable(Level.INFO)) { logger.info("Successfully converted resource " + getResourceIdentifier() + " from " + v[i] + " to " + cmv.toVersion); } while (i + 1 < v.length && !v[i + 1].equals(cmv.toVersion)) { i++; } if (i + 1 == v.length || !v[i + 1].equals(cmv.toVersion)) { if (logger.isLoggable(Level.WARNING)) { logger.warning("This is weird. I tried to convert from " + cmv.version + " to " + cmv.toVersion + " but I can't find that version in the mapping."); } return false; } } else { if (logger.isLoggable(Level.WARNING)) { logger.warning("FAILED to convert resource " + getResourceIdentifier() + " from " + v[i] + " to " + cmv.toVersion); } } } else { _currentVersion = cmv.version; try { logger.info("Trying to save resource " + getResourceIdentifier() + " with model version " + _currentVersion); _saveResourceData(_currentVersion, true); } catch (SaveXMLResourceException e) { logger.warning("Cound not save with version " + _currentVersion + " " + e); e.printStackTrace(); } } } else { break;// We are done here } } if (logger.isLoggable(Level.INFO)) { logger.info("Incremental conversion from " + fromVersion + " to " + toVersion + " performed successfully"); } return true; } /** * @return */ protected FlexoXMLMappings getXmlMappings() { return getProject().getXmlMappings(); } /** * Save current resource data to current XML resource file.<br> * Forces XML version to be the latest one. * * @return */ @Override protected void saveResourceData(boolean clearIsModified) throws SaveXMLResourceException, SaveResourcePermissionDeniedException { if (!hasWritePermission()) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Permission denied : " + getFile().getAbsolutePath()); } throw new SaveResourcePermissionDeniedException(this); } if (_resourceData != null) { _saveResourceData(latestVersion(), clearIsModified); if (logger.isLoggable(Level.INFO)) { logger.info("Succeeding to save Resource " + getResourceIdentifier() + " : " + getFile().getName() + " with date " + FileUtils.getDiskLastModifiedDate(getFile())); } } if (clearIsModified) { try { getResourceData().clearIsModified(false);// No need to reset the last memory update since it is valid notifyResourceStatusChanged(); } catch (Exception e) { e.printStackTrace(); } } } public synchronized void revertToReleaseVersion(FlexoVersion releaseVersion) throws SaveXMLResourceException, SaveResourcePermissionDeniedException { // 1st, be sure that data are loaded getResourceData(); // Then find version final FlexoVersion version = getXmlMappings().getVersionForClassAndRelease(getResourceDataClass(), releaseVersion); // And save to this version if (version != null) { logger.info("Trying to convert " + getResourceIdentifier() + " from " + latestVersion() + " to " + version); _currentVersion = version; if (!hasWritePermission()) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Permission denied : " + getFile().getAbsolutePath()); } throw new SaveResourcePermissionDeniedException(this); } if (_resourceData != null) { final XMLMapping currentMapping = getXmlMappings().getMappingForClassAndVersion(getResourceDataClass(), latestVersion()); final XMLMapping revertedMapping = getXmlMappings().getMappingForClassAndVersion(getResourceDataClass(), version); _saveResourceData(version, new SerializationHandler() { @Override public void objectWillBeSerialized(XMLSerializable object) { if (object instanceof FlexoModelObject) { fillInUnmappedAttributesAsDynamicProperties((FlexoModelObject) object, currentMapping, revertedMapping); ((FlexoModelObject) object).initializeSerialization(); } } @Override public void objectHasBeenSerialized(XMLSerializable object) { if (object instanceof FlexoModelObject) { ((FlexoModelObject) object).finalizeSerialization(); } } }, true); if (logger.isLoggable(Level.INFO)) { logger.info("Succeeding to save Resource " + getResourceIdentifier() + " : " + getFile().getName()); } } _currentVersion = version; getResourceData().clearIsModified(false); } } void fillInUnmappedAttributesAsDynamicProperties(FlexoModelObject object, XMLMapping currentMapping, XMLMapping revertedMapping) { ModelEntity currentEntity = currentMapping.entityForClass(object.getClass()); ModelEntity revertedEntity = revertedMapping.entityForClass(object.getClass()); for (Enumeration<ModelProperty> en = currentEntity.getModelProperties(); en.hasMoreElements();) { ModelProperty p = en.nextElement(); if (p.getIsAttribute()) { if (revertedEntity.getModelPropertyWithName(p.getName()) == null) { // Found unmapped property String value = object.valueForKey(p.getName()); if (value != null) { // logger.info("Object "+object+" found unmapped non-null attribute "+p.getName()+" value="+value); object.setDynamicPropertiesForKey(value, p.getName()); } } } } } protected synchronized void saveResourceDataWithVersion(FlexoVersion version) throws SaveXMLResourceException, SaveResourcePermissionDeniedException { if (!hasWritePermission()) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Permission denied : " + getFile().getAbsolutePath()); } throw new SaveResourcePermissionDeniedException(this); } if (_resourceData != null) { _saveResourceData(version, true); if (logger.isLoggable(Level.INFO)) { logger.info("Succeeding to save Resource " + getResourceIdentifier() + " : " + getFile().getName()); } } try { _currentVersion = version; getResourceData().clearIsModified(false); } catch (Exception e) { e.printStackTrace(); } } private void _saveResourceData(FlexoVersion version, boolean clearIsModified) throws SaveXMLResourceException { _saveResourceData(version, new SerializationHandler() { @Override public void objectWillBeSerialized(XMLSerializable object) { if (object instanceof FlexoModelObject) { ((FlexoModelObject) object).initializeSerialization(); } } @Override public void objectHasBeenSerialized(XMLSerializable object) { if (object instanceof FlexoModelObject) { ((FlexoModelObject) object).finalizeSerialization(); } } }, clearIsModified); } private void _saveResourceData(FlexoVersion version, SerializationHandler handler, boolean clearIsModified) throws SaveXMLResourceException { File temporaryFile = null; FileWritingLock lock = willWriteOnDisk(); try { File dir = getFile().getParentFile(); if (!dir.exists()) { dir.mkdirs(); } // Make local copy makeLocalCopy(); // Using temporary file temporaryFile = File.createTempFile("temp", ".xml", dir); if (logger.isLoggable(Level.FINE)) { logger.finer("Creating temp file " + temporaryFile.getAbsolutePath()); } try { performXMLSerialization(version, handler, temporaryFile); // Renaming temporary file if (logger.isLoggable(Level.FINE)) { logger.finer("Renaming temp file " + temporaryFile.getAbsolutePath() + " to " + getFile().getAbsolutePath()); } // temporaryFile.renameTo(getFile()); postXMLSerialization(version, temporaryFile, lock, clearIsModified); } catch (DuplicateSerializationIdentifierException e) { if (logger.isLoggable(Level.SEVERE)) { logger.log(Level.SEVERE, "Duplicate serialization identifier: " + e.getMessage(), e); } if (isDuplicateSerializationIdentifierRepairable()) { if (repairDuplicateSerializationIdentifier()) { performXMLSerialization(version, handler, temporaryFile); postXMLSerialization(version, temporaryFile, lock, clearIsModified); return; } } hasWrittenOnDisk(lock); ((FlexoXMLSerializableObject) getResourceData()).finalizeSerialization(); throw new SaveXMLResourceException(this, e, version); } } catch (Exception e) { e.printStackTrace(); if (temporaryFile != null) { temporaryFile.delete(); } if (logger.isLoggable(Level.WARNING)) { logger.warning("Failed to save resource " + getResourceIdentifier() + " with model version " + version); } hasWrittenOnDisk(lock); ((FlexoXMLSerializableObject) getResourceData()).finalizeSerialization(); throw new SaveXMLResourceException(this, e, version); } } /** * @param version * @param temporaryFile * @param lock * @param clearIsModified * @throws IOException */ private void postXMLSerialization(FlexoVersion version, File temporaryFile, FileWritingLock lock, boolean clearIsModified) throws IOException { FileUtils.rename(temporaryFile, getFile()); hasWrittenOnDisk(lock); ((FlexoXMLSerializableObject) getResourceData()).finalizeSerialization(); _currentVersion = version; if (clearIsModified) { notifyResourceStatusChanged(); } } /** * @param version * @param handler * @param temporaryFile * @throws FileNotFoundException * @throws InvalidObjectSpecificationException * @throws InvalidModelException * @throws AccessorInvocationException * @throws DuplicateSerializationIdentifierException * @throws IOException */ private void performXMLSerialization(FlexoVersion version, SerializationHandler handler, File temporaryFile) throws FileNotFoundException, InvalidObjectSpecificationException, InvalidModelException, AccessorInvocationException, DuplicateSerializationIdentifierException, IOException { FileOutputStream out = null; try { out = new FileOutputStream(temporaryFile); FlexoXMLSerializableObject dataToSerialize = (FlexoXMLSerializableObject) getResourceData(); dataToSerialize.initializeSerialization(); XMLCoder.encodeObjectWithMapping(dataToSerialize, getXmlMappings() .getMappingForClassAndVersion(getResourceDataClass(), version), out, getStringEncoder(), handler); dataToSerialize.finalizeSerialization(); out.flush(); out.close(); out = null; } finally { if (out != null) { out.close(); } out = null; } } @Override public StringEncoder getStringEncoder() { return getProject().getStringEncoder(); } protected abstract boolean isDuplicateSerializationIdentifierRepairable(); protected abstract boolean repairDuplicateSerializationIdentifier(); private void makeLocalCopy() throws IOException { if (getFile() != null && getFile().exists()) { String localCopyName = getFile().getName() + "~"; File localCopy = new File(getFile().getParentFile(), localCopyName); FileUtils.copyFileToFile(getFile(), localCopy); } } protected XMLRD tryToLoadResourceDataWithVersion(FlexoVersion version) throws XMLOperationException, JDOMException { XMLRD returned = null; try { if (logger.isLoggable(Level.FINE)) { logger.fine("Trying to load " + getResourceDataClass().getName() + " with model version " + version); } XMLMapping mapping = getXmlMappings().getMappingForClassAndVersion(getResourceDataClass(), version); if (logger.isLoggable(Level.FINE)) { logger.fine("Model version " + version + " has been loaded."); } if (hasBuilder() && mapping.hasBuilderClass()) { if (getProject() != null) { returned = (XMLRD) XMLDecoder.decodeObjectWithMapping(new FileInputStream(getFile()), mapping, instanciateNewBuilder(), getStringEncoder()); } else { if (!(this instanceof FlexoRMResource)) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Project is not set on " + this.getFullyQualifiedName()); } } returned = (XMLRD) XMLDecoder.decodeObjectWithMapping(new FileInputStream(getFile()), mapping, instanciateNewBuilder()); } } else { if (getProject() != null) { returned = (XMLRD) XMLDecoder .decodeObjectWithMapping(new FileInputStream(getFile()), mapping, null, getStringEncoder()); } else { if (!(this instanceof FlexoRMResource)) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Project is not set on " + this.getFullyQualifiedName()); } } returned = (XMLRD) XMLDecoder .decodeObjectWithMapping(new FileInputStream(getFile()), mapping, null, getStringEncoder()); } } if (logger.isLoggable(Level.FINE)) { logger.fine("Succeeded loading " + getResourceDataClass().getName() + " with model version " + version); } if (returned != null) { returned.setFlexoResource(this); } return returned; } catch (AccessorInvocationException e) { if (logger.isLoggable(Level.FINE)) { if (logger.isLoggable(Level.FINE)) { logger.fine("FAILED loading " + getResourceDataClass().getName() + " with model version " + version + " Exception: " + e.getTargetException().getMessage()); } } if (logger.isLoggable(Level.FINER)) { e.getTargetException().printStackTrace(); } e.printStackTrace(); throw new XMLOperationException(e, version); } catch (Exception e) { if (logger.isLoggable(Level.FINE)) { if (logger.isLoggable(Level.FINE)) { logger.fine("FAILED loading " + getResourceDataClass().getName() + " with model version " + version + " Exception: " + e.getMessage()); } } if (logger.isLoggable(Level.FINEST)) { e.printStackTrace(); } // e.printStackTrace(); throw new XMLOperationException(e, version); } } public FlexoVersion latestVersion() { return getXmlMappings().getLatestVersionForClass(getResourceDataClass()); } public FlexoVersion getXmlVersion() { return _currentVersion; } public void setXmlVersion(FlexoVersion version) { _currentVersion = version; } public XMLMapping getCurrentMapping() { return getXmlMappings().getMappingForClassAndVersion(getResourceDataClass(), _currentVersion); } public String getResourceXMLRepresentation() throws XMLOperationException { try { return XMLCoder.encodeObjectWithMapping(getXMLResourceData(), getCurrentMapping(), getStringEncoder(), null); } catch (Exception e) { e.printStackTrace(); throw new XMLOperationException(e, _currentVersion); } } /** * Return data related to this resource, as an instance of an object implementing * * @see org.openflexo.foundation.rm.XMLStorageResourceData * @return */ public XMLStorageResourceData getXMLResourceData() { try { return getResourceData(); } catch (Exception e) { e.printStackTrace(); return null; } } /** * Manually converts resource file from version v1 to version v2. This methods only warns and does nothing, and must be overriden in * subclasses ! * * @param v1 * @param v2 * @return boolean indicating if conversion was sucessfull */ protected boolean convertResourceFileFromVersionToVersion(FlexoVersion v1, FlexoVersion v2) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Unable to find converter for resource " + getResourceIdentifier() + " from version " + v1 + " to version " + v2); } return false; } public abstract Class<XMLRD> getResourceDataClass(); /** * Returns a boolean indicating if this resource needs a builder to be loaded Returns false and thus must be overriden in subclasses * * @return boolean */ public boolean hasBuilder() { return false; } /** * Returns the required newly instancied builder if this resource needs a builder to be loaded * * @return boolean */ public abstract Object instanciateNewBuilder(); /** * Thrown when an exception was raised during XML operation try with a specified version * * @author sguerin * */ public static class XMLOperationException extends FlexoException { private Exception exception; private FlexoVersion version; public XMLOperationException(Exception exception, FlexoVersion version) { super(); this.exception = exception; this.version = version; } public FlexoVersion getVersion() { return version; } public Exception getException() { return exception; } @Override public String getMessage() { return "XMLOperationException caused by " + exception.getClass().getName() + " : " + exception.getMessage(); } } /** * Thrown when an exception was raised during saving * * @author sguerin * */ public static class SaveXMLResourceException extends SaveResourceException { private XMLOperationException exception; public SaveXMLResourceException(FlexoXMLStorageResource thisResource, Exception exception, FlexoVersion version) { super(thisResource); this.exception = new XMLOperationException(exception, version); } @Override public String getMessage() { return "SaveXMLResourceException caused by : " + exception.getMessage(); } } /** * Thrown when an exception was raised during loading * * @author sguerin * */ public static class LoadXMLResourceException extends LoadResourceException { /** * Vector of LoadResourceWithVersionException */ private List<XMLOperationException> loadResourceExceptions; public LoadXMLResourceException(FlexoXMLStorageResource thisResource, String message) { super(thisResource, message); loadResourceExceptions = new Vector<FlexoXMLStorageResource.XMLOperationException>(); } @Deprecated public LoadXMLResourceException(FlexoXMLStorageResource thisResource) { this(thisResource, (String) null); } @Deprecated public LoadXMLResourceException(FlexoXMLStorageResource thisResource, LoadResourceException exception) { this(thisResource); } public void addLoadException(XMLOperationException exception) { loadResourceExceptions.add(exception); } @Override public String getMessage() { if (loadResourceExceptions.size() == 0) { return super.getMessage(); } StringBuilder sb = new StringBuilder("LoadXMLResourceException caused by multiple exceptions:\n"); for (XMLOperationException temp : loadResourceExceptions) { sb.append("Trying to load with version ").append(temp.getVersion()).append(" exception raised: ").append(temp.getMessage()) .append('\n'); temp.exception.printStackTrace(); } return sb.toString(); } public String getExtendedMessage() { StringBuilder sb = new StringBuilder("LoadXMLResourceException caused by multiple exceptions:\n"); for (XMLOperationException temp : loadResourceExceptions) { sb.append("Trying to load with version ").append(temp.getVersion()).append(" exception raised: ").append(temp.getMessage()) .append('\n'); sb.append("StackTrace:\n"); if (temp.getException().getStackTrace() != null) { for (int i = 0; i < temp.getException().getStackTrace().length; i++) { sb.append("\tat ").append(temp.getException().getStackTrace()[i]).append('\n'); } } if (temp.getException() instanceof AccessorInvocationException) { sb.append("Caused by :\n"); for (int i = 0; i < ((AccessorInvocationException) temp.getException()).getTargetException().getStackTrace().length; i++) { sb.append("\tat " + ((AccessorInvocationException) temp.getException()).getTargetException().getStackTrace()[i]) .append('\n'); } } } return sb.toString(); } } /** * Thrown when an exception was raised during loading * * @author sguerin * */ public static class MalformedXMLException extends LoadResourceException { private JDOMException jDOMException; public MalformedXMLException(FlexoXMLStorageResource thisResource, JDOMException exception) { super(thisResource, null); jDOMException = exception; } @Override public String getMessage() { return jDOMException.getMessage(); } } public boolean getIsLoading() { return _isLoading; } public boolean isConverting() { return isConverting; } }