/*
* (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.FileNotFoundException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openflexo.foundation.FlexoException;
import org.openflexo.foundation.utils.FlexoProgress;
import org.openflexo.foundation.utils.ProjectLoadingCancelledException;
import org.openflexo.foundation.utils.ProjectLoadingHandler;
public abstract class FlexoStorageResource<SRD extends StorageResourceData> extends FlexoFileResource<SRD> {
private static final Logger logger = Logger.getLogger(FlexoStorageResource.class.getPackage().getName());
private static final class StorageResourceLoadResourceException extends LoadResourceException {
private final Exception cause;
private StorageResourceLoadResourceException(FlexoFileResource<?> fileResource, String message, Exception cause) {
super(fileResource, message);
this.cause = cause;
}
@Override
public Throwable getCause() {
return cause;
}
}
public static interface ResourceLoadingListener {
public void notifyResourceWillBeLoaded(FlexoStorageResource resource);
public void notifyResourceHasBeenLoaded(FlexoStorageResource resource);
}
private Vector<ResourceLoadingListener> resourceLoadingListeners = new Vector<ResourceLoadingListener>();
private Date lastKnownMemoryUpdate;
/**
* Constructor used for XML Serialization: never try to instanciate resource from this constructor
*
* @param builder
*/
public FlexoStorageResource(FlexoProjectBuilder builder) {
this(builder.project);
builder.notifyResourceLoading(this);
}
@Override
public synchronized void delete() {
resourceLoadingListeners.clear();
super.delete();
}
@Override
public synchronized void delete(boolean deleteFile) {
resourceLoadingListeners.clear();
super.delete(deleteFile);
}
public FlexoStorageResource(FlexoProject aProject) {
super(aProject);
}
public boolean needsSaving() {
return isModified();
}
/**
* This date is VERY IMPORTANT and CRITICAL since this is the date used by ResourceManager to compute dependancies between resources.
* This method returns the date that must be considered as last known update for this resource
*
* @return a Date object
*/
@Override
public final Date getLastUpdate() {
if (getLastKnownMemoryUpdate() != null) {
return getLastKnownMemoryUpdate();
}
return getDiskLastModifiedDate();
}
/**
* Returns the last known memory update date known by Flexo
*
* @return
*/
public Date getLastKnownMemoryUpdate() {
if (lastMemoryUpdate() != null) {
return lastMemoryUpdate();// We are loaded so we return the lastMemortUpdate()
}
if (isLoaded() && lastKnownMemoryUpdate == null) {
// last modified which is never null
lastKnownMemoryUpdate = getDiskLastModifiedDate();
}
return lastKnownMemoryUpdate;
}
public void setLastKnownMemoryUpdate(Date lastKnownMemoryUpdate) {
this.lastKnownMemoryUpdate = lastKnownMemoryUpdate;
}
/**
* Returns date when data related to this resource was last modified
*
* @return
*/
public Date lastMemoryUpdate() {
if (!isLoaded()) {
return null;
} else {
try {
return getResourceData().lastMemoryUpdate();
} catch (Exception e) {
e.printStackTrace();
return new Date();
}
}
}
/**
* Returns a flag indicating if data related to this resource has been modified. Returns false if data is not loaded
*
* @return
*/
public boolean isModified() {
if (!isLoaded()) {
return false;
} else {
try {
return !getFile().exists() || getResourceData().isModified();
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
/**
* Return a flag indicating if resource is loaded in memory
*
* @return
*/
public synchronized boolean isLoaded() {
return _resourceData != null;
}
public final void saveResourceData() throws SaveResourceException {
if (!isLoaded()) {
return;
}
getProject().saveResourceData(this, true);
}
protected abstract void saveResourceData(boolean clearIsModified) throws SaveResourceException;
/**
* Return data related to this resource, as an instance of an object implementing
*
* @see org.openflexo.foundation.rm.FlexoResourceData
* @return
*/
public SRD getResourceData() {
if (_resourceData == null && isLoadable()) {
try {
_resourceData = loadResourceData();
} catch (FlexoException e) {
// Warns about the exception
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Could not load resource data for resource " + getResourceIdentifier());
}
e.printStackTrace();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ResourceDependencyLoopException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return _resourceData;
}
/**
* Return data related to this resource, as an instance of an object implementing
*
* @see org.openflexo.foundation.rm.FlexoResourceData
* @return
*/
public SRD reloadResourceData() {
_resourceData = null;
return getResourceData();
}
/**
*
* Receive a notification that has been propagated by the ResourceManager scheme and coming from a modification on an other resource
*
* @see org.openflexo.foundation.rm.FlexoResource#notifyRM(org.openflexo.foundation.rm.RMNotification)
*/
@Override
public void receiveRMNotification(RMNotification notification) throws FlexoException {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Resource " + getResourceIdentifier() + " receive " + notification);
}
super.receiveRMNotification(notification);
if (!isLoaded()) {
if (notification.forceUpdateWhenUnload()) {
// Resource is not loaded but requires immediate update
// Load the resource and invoke synchronizing for this
// notification
if (logger.isLoggable(Level.FINE)) {
logger.fine("Resource " + getResourceIdentifier() + " receive " + notification);
logger.fine("Resource " + getResourceIdentifier() + " !isLoaded() && notification.forceUpdateWhenUnload()");
try {
logger.fine("getResourceData() = " + getResourceData());
} catch (Exception e) {
e.printStackTrace();
throw new FlexoException();
}
}
try {
if (getResourceData() != null) { // May happen in case of deleting
getResourceData().receiveRMNotification(notification);
}
} catch (Exception e) {
e.printStackTrace();
throw new FlexoException();
}
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Resource " + getResourceIdentifier() + " receive " + notification);
logger.fine("Resource " + getResourceIdentifier() + " !isLoaded() && !notification.forceUpdateWhenUnload()");
logger.fine("DO NOTHING MORE");
}
}
} else {
// Resource is loaded.
// Just invoke synchronizing for this notification
if (logger.isLoggable(Level.FINE)) {
logger.fine("Resource " + getResourceIdentifier() + " receive " + notification);
logger.fine("Resource " + getResourceIdentifier() + " isLoaded()");
try {
logger.fine("getResourceData() = " + getResourceData());
} catch (Exception e) {
e.printStackTrace();
throw new FlexoException();
}
}
try {
getResourceData().receiveRMNotification(notification);
} catch (Exception e) {
e.printStackTrace();
throw new FlexoException();
}
}
}
public void addResourceLoadingListener(ResourceLoadingListener listener) {
if (!resourceLoadingListeners.contains(listener)) {
resourceLoadingListeners.add(listener);
}
}
public void removeResourceLoadingListener(ResourceLoadingListener listener) {
resourceLoadingListeners.remove(listener);
}
protected void notifyListenersResourceWillBeLoaded() {
for (ResourceLoadingListener listener : resourceLoadingListeners) {
listener.notifyResourceWillBeLoaded(this);
}
}
protected void notifyListenersResourceHasBeenLoaded() {
for (ResourceLoadingListener listener : resourceLoadingListeners) {
listener.notifyResourceHasBeenLoaded(this);
}
}
protected boolean _resolveDependanciesSchemeRunningForThisResource = false;
protected abstract SRD performLoadResourceData(FlexoProgress progress, ProjectLoadingHandler loadingHandler)
throws LoadResourceException, FileNotFoundException, ProjectLoadingCancelledException;
protected boolean isLoadable() {
return getFile() != null && getFile().exists();
}
public final SRD loadResourceData(FlexoProgress progress, ProjectLoadingHandler loadingHandler) throws FlexoException,
FileNotFoundException, ResourceDependencyLoopException {
if (!isLoadable()) {
return null;
}
if (_resolveDependanciesSchemeRunningForThisResource) {
new Exception("Loop in dependencies").printStackTrace();
logger.warning("Found loop in dependancies. Automatic rebuild dependancies is required !");
getProject().setRebuildDependanciesIsRequired();
}
try {
update();
} catch (ResourceDependencyLoopException e1) {
if (logger.isLoggable(Level.SEVERE)) {
logger.log(Level.SEVERE, "Loop in dependant resources of " + this + "!", e1);
}
e1.printStackTrace();
// BMA VERY EXPERIMENTAL : ignore LOOP, just load the resource
if (!isLoaded()) {
performUpdating(null);
}
}
return _resourceData;
}
public final SRD loadResourceData() throws FlexoException, FileNotFoundException, ResourceDependencyLoopException {
return loadResourceData(null, getLoadingHandler());
}
/**
* Perform update of specified resource data from data read from a updated resource on disk
*/
@Override
public synchronized void performDiskUpdate() throws FlexoException {
reloadResourceData();
lastKnownMemoryUpdate = new Date(); // update of the last memory update to now
super.performDiskUpdate();
}
@Override
protected Date getRequestDateToBeUsedForOptimisticDependencyChecking(FlexoResource resource) {
Date returned = getLastSynchronizedWithResource(resource);
if (returned.getTime() == 0) {
// If never synchronized, consider last update
returned = getLastUpdate();
}
return returned;
}
@Override
protected boolean requireUpdateBecauseOf(FlexoResource resource) {
if (super.requireUpdateBecauseOf(resource)) {
// OK, at first sight 'resource' of which 'this' depends is newer than 'this' so it seems that we need to update 'this'. BUT,
// maybe
// the modifications of 'resource' does not affect 'this', therefore we will look at the lastSynchronizationDate
Date lastBackSynchronizationDate = getLastSynchronizedWithResource(resource);
if (!project.getTimestampsHaveBeenLoaded()) {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Time stamps not loaded yet!!! Impossible to say if we have to do something or not");
}
}
if (lastBackSynchronizationDate == null || resource.getLastUpdate().after(lastBackSynchronizationDate)) {
if (lastBackSynchronizationDate != null) {
if (logger.isLoggable(Level.FINER)) {
logger.finer("Resource " + this + " is to be backward synchronized for " + resource + " because "
+ new SimpleDateFormat("dd/MM HH:mm:ss SSS").format(resource.getLastUpdate()) + " is more recent than "
+ new SimpleDateFormat("dd/MM HH:mm:ss SSS").format(lastBackSynchronizationDate));
}
}
return true;
} else {
if (logger.isLoggable(Level.FINER)) {
logger.finer("NOT: Resource " + this + " must NOT be BS with " + resource + " because "
+ new SimpleDateFormat("dd/MM HH:mm:ss SSS").format(resource.getLastUpdate()) + " is more anterior than "
+ new SimpleDateFormat("dd/MM HH:mm:ss SSS").format(lastBackSynchronizationDate));
}
}
}
return false;
}
public final void backwardSynchronizeWith(FlexoResourceTree updatedResources) throws ResourceDependencyLoopException, FlexoException,
FileNotFoundException {
if (!updatedResources.isEmpty()) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Resource " + this + " requires backward synchronization for " + updatedResources.getChildNodes().size()
+ " resources :");
}
for (FlexoResourceTree resourceTrees : updatedResources.getChildNodes()) {
FlexoResource resource = resourceTrees.getRootNode();
resource.update();
if (FlexoResourceManager.getBackwardSynchronizationHook() != null) {
FlexoResourceManager.getBackwardSynchronizationHook().notifyBackwardSynchronization(this, resource);
}
backwardSynchronizeWith(resource);
}
}
}
public void backwardSynchronizeWith(FlexoResource aResource) throws FlexoException /*throws FlexoException*/
{
// Must be called sub sub-classes implementation: must be overriden in subclasses !
// At this level, only set last synchronized date
if (logger.isLoggable(Level.FINER)) {
logger.finer("Backsynchronizing " + this + " with " + aResource);
}
setLastSynchronizedWithResource(aResource, new Date());
getProject().notifyResourceHasBeenBackwardSynchronized(this);
}
@Override
protected void performUpdating(FlexoResourceTree updatedResources) throws ResourceDependencyLoopException, FlexoException,
LoadResourceException, FileNotFoundException, ProjectLoadingCancelledException {
if (!isLoaded()) {
_resourceData = performLoadResourceData(null, getLoadingHandler());
if (_resourceData != null) {
_resourceData.setFlexoResource(this);
}
}
if (updatedResources != null) {
backwardSynchronizeWith(updatedResources);
}
}
}