/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.project.impl; import java.lang.reflect.Method; import java.util.Arrays; import java.util.LinkedList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.WriteOnlyGraph; import org.simantics.db.common.processor.MergingGraphRequestProcessor; import org.simantics.db.common.request.WriteOnlyRequest; import org.simantics.db.exception.CancelTransactionException; import org.simantics.db.exception.DatabaseException; import org.simantics.project.IProject; import org.simantics.project.exception.ProjectException; import org.simantics.project.features.IProjectFeature; import org.simantics.utils.datastructures.disposable.DisposeState; import org.simantics.utils.datastructures.disposable.IDisposeListener; import org.simantics.utils.datastructures.hints.HintContext; import org.simantics.utils.threads.IThreadWorkQueue; import org.simantics.utils.threads.SyncListenerList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Tuukka Lehtonen */ public class Project extends HintContext implements IProject { private static final Logger LOGGER = LoggerFactory.getLogger(Project.class); private static IProjectFeature[] NONE = {}; //private static final String TAG_PROJECTS = "projects"; private IProjectFeature[] features = NONE; private final LinkedList featureSet = new LinkedList(); private final LinkedList inactiveFeatures = new LinkedList(); private final LinkedList activeFeatures = new LinkedList(); private final Lock lock = new ReentrantLock(); private final AtomicBoolean active = new AtomicBoolean(false); protected Session session; protected Resource resource; public Project(Session session, Resource resource) { if (session == null) throw new NullPointerException("null session"); if (resource == null) throw new NullPointerException("null resource"); this.session = session; this.resource = resource; //System.out.println("NEW PROJECT [" + resource.getResourceId() + "] (" + System.identityHashCode(this) + ")"); } public void doDispose() { //System.out.println("DISPOSE: " + this + " (" + System.identityHashCode(this) + ")"); lock.lock(); try { deactivate(); featureSet.clear(); features = NONE; } catch (ProjectException e1) { // TODO: do something more sensible than print the possible exception! e1.printStackTrace(); } finally { lock.unlock(); } } @Override public IProjectFeature[] getFeatures() { assertNotDisposed(); // Defensive copy. IProjectFeature[] features = this.features; return Arrays.copyOf(features, features.length); } public void addFeature(IProjectFeature feature) { assertNotDisposed(); lock.lock(); try { if (!featureSet.contains(feature)) { featureSet.add(feature); inactiveFeatures.add(feature); features = featureSet.toArray(new IProjectFeature[featureSet.size()]); } } finally { lock.unlock(); } } @Override public void activate() throws ProjectException { if (isDisposed()) throw new IllegalStateException("project is disposed, cannot activate " + this + " (" + System.identityHashCode(this) + ")"); lock.lock(); try { if (active.get() == true) return; while (!inactiveFeatures.isEmpty()) { IProjectFeature feature = inactiveFeatures.getFirst(); boolean success = false; try { LOGGER.info("Configuring project feature {}", feature); feature.setProjectElement(this); feature.configure(); inactiveFeatures.removeFirst(); activeFeatures.addLast(feature); success = true; } finally { if (!success) feature.setProjectElement(null); } } try { transactionBarrier(); } finally { active.set(true); } } finally { lock.unlock(); } } @Override public void deactivate() throws ProjectException { if (isDisposed()) throw new IllegalStateException("project is disposed, cannot deactivate " + this + " (" + System.identityHashCode(this) + ")"); lock.lock(); try { if (active.get() == false) return; while (!activeFeatures.isEmpty()) { IProjectFeature feature = activeFeatures.getLast(); boolean success = false; try { feature.deconfigure(); activeFeatures.removeLast(); inactiveFeatures.addFirst(feature); success = true; } finally { if (success) feature.setProjectElement(null); } } try { transactionBarrier(); } finally { active.set(false); } } finally { lock.unlock(); } } private void transactionBarrier() throws ProjectException { // IMPORTANT: ensure that all database requests have been // completed before returning from deactivation. try { session.syncRequest(new WriteOnlyRequest() { @Override public void perform(WriteOnlyGraph graph) throws DatabaseException { throw new CancelTransactionException("barrier"); } }); } catch (CancelTransactionException e) { } catch (DatabaseException e) { throw new ProjectException(e); } } @Override public String toString() { return getClass().getSimpleName() + " [resource=" + get().getResourceId() + "]"; } @Override public int hashCode() { return get().hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Project)) return false; final Project other = (Project) obj; // Need to make sure that the resources are from the same session. if (!session.equals(other.session)) return false; Resource r1 = get(); Resource r2 = other.get(); if (r1 == null) { if (r2 != null) return false; } else if (!r1.equals(r2)) return false; return true; } @Override public Session getSession() { return session; } @Override public Resource get() { return resource; } public RequestProcessor getGraphRequestProcessor() { MergingGraphRequestProcessor mgrp = session.peekService(MergingGraphRequestProcessor.class); return mgrp != null ? mgrp : session; } // IDisposable implementation (copied from AbstractDisposable) SyncListenerList disposeListeners = null; private volatile DisposeState disposeStatus = DisposeState.Alive; protected void assertNotDisposed() { if (isDisposed()) throw new IllegalStateException(this + " is disposed"); } @Override public DisposeState getDisposeState() { return disposeStatus; } @Override public boolean isDisposed() { return disposeStatus == DisposeState.Disposed; } public boolean isAlive() { return disposeStatus == DisposeState.Alive; } @Override public void dispose() { try { lock.lock(); try { if (disposeStatus == DisposeState.Disposing) return; assertNotDisposed(); disposeStatus = DisposeState.Disposing; } finally { lock.unlock(); } try { fireDisposed(); } finally { doDispose(); } } finally { disposeStatus = DisposeState.Disposed; } } /** * Disposes if not disposed */ @Override public void safeDispose() { try { lock.lock(); try { if (disposeStatus != DisposeState.Alive) return; disposeStatus = DisposeState.Disposing; } finally { lock.unlock(); } try { fireDisposed(); } finally { doDispose(); } } finally { disposeStatus = DisposeState.Disposed; } } protected boolean hasDisposeListeners() { return disposeListeners!=null && !disposeListeners.isEmpty(); } private final static Method onDisposed = SyncListenerList.getMethod(IDisposeListener.class, "onDisposed"); private void fireDisposed() { if (disposeListeners==null) return; disposeListeners.fireEventSync(onDisposed, this); } @SuppressWarnings("unused") private void fireDisposedAsync() { if (disposeListeners==null) return; disposeListeners.fireEventAsync(onDisposed, this); } @Override public void addDisposeListener(IDisposeListener listener) { lazyGetListenerList().add(listener); } @Override public void addDisposeListener(IDisposeListener listener, IThreadWorkQueue thread) { lazyGetListenerList().add(thread, listener); } @Override public void removeDisposeListener(IDisposeListener listener) { if (disposeListeners==null) return; disposeListeners.remove(listener); } @Override public void removeDisposeListener(IDisposeListener listener, IThreadWorkQueue thread) { if (disposeListeners==null) return; disposeListeners.remove(thread, listener); } private synchronized SyncListenerList lazyGetListenerList() { if (disposeListeners==null) disposeListeners = new SyncListenerList(IDisposeListener.class); return disposeListeners; } }