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