/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.persistence; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.servoy.j2db.util.UUID; import com.servoy.j2db.util.Utils; /** * Reposository tree root object * * @author sebster */ public abstract class AbstractRootObject extends AbstractBase implements IRootObject { private static final long serialVersionUID = 1L; private transient IRepository repository; // Because this map can be accessed from multiple places at once and changed from multiple places at once, // modifying it's contents and iterating over the contents are syncronized (mutual exclusion); otherwise we would end up with concurrent modification exceptions. // For example: a solution A has module B, one client opens solution A and another client opens solution B. // The initial fix was using ConcurrentHashMap, but there is a bug in Terracotta with ConcurrentHashMap and serialization, so we can't use that for now. See http://jira.terracotta.org/jira/browse/CDV-1377 private Map<String, IServer> serverProxies; //name -> Server private RootObjectMetaData metaData; private int releaseNumber; private List<IPersist> newObjects; private List<IPersist> removedObjects; /** * Connection handling, only for developer,therefore transient */ private transient ChangeHandler changeHandler = null; public AbstractRootObject(IRepository repository, RootObjectMetaData metaData) { super(metaData.getObjectTypeId(), null, metaData.getRootObjectId(), metaData.getRootObjectUuid()); this.repository = repository; this.metaData = metaData; } void setMetaData(RootObjectMetaData metaData) { this.metaData = metaData; } @Override public MetaData getMetaData() { return getRootObjectMetaData(); } public RootObjectMetaData getRootObjectMetaData() { return metaData; } public String getName() { return getRootObjectMetaData().getName(); } public int getReleaseNumber() { return releaseNumber; } // for setting after load public void setReleaseNumber(int releaseNumber) { this.releaseNumber = releaseNumber; } public IRepository getRepository() { return repository; } // for setting after load public void setRepository(IRepository repository) { this.repository = repository; } // for setting after load. public void setChangeHandler(ChangeHandler connectionHandler) { this.changeHandler = connectionHandler; connectionHandler.setRootObject(this); } public ChangeHandler getChangeHandler() { return changeHandler; } @Override protected void fillClone(AbstractBase cloned) { // clear changeHandler, the original change handler should not listen to changes in clone ((AbstractRootObject)cloned).changeHandler = null; super.fillClone(cloned); } @Override public IRootObject getRootObject() { return this; } /** * This method should not be called by developer only code * * @param name * @throws RepositoryException */ public IServer getServer(String name) throws RepositoryException { if (name == null) return null; String lowerCaseBame = Utils.toEnglishLocaleLowerCase(name); try { IServer server = null; Map<String, IServer> proxies = getServerProxies(); server = proxies.get(lowerCaseBame); if (server == null) { // fetch a server that is not pre-loaded (could be referred to in custom query) server = getRepository().getServer(lowerCaseBame); if (server != null && !(server instanceof IServerInternal)) { //wrap server = new ServerProxy(server); synchronized (proxies) { proxies.put(lowerCaseBame, server); } } } return server; } catch (RemoteException e) { throw new RepositoryException(e); } } public Map<String, IServer> getServerProxies() { if (serverProxies == null) serverProxies = new HashMap<String, IServer>(); return serverProxies; } // for setting after load public void setServerProxies(Map<String, IServer> serverProxies) { this.serverProxies = serverProxies; } @Override public int hashCode() { return getTypeID() ^ getName().hashCode() ^ releaseNumber; } @Override public String toString() { return getRootObjectMetaData().getName(); } @Override public boolean equals(Object other) { if (other instanceof IRootObject) { final IRootObject otherRootObject = (IRootObject)other; return getTypeID() == otherRootObject.getTypeID() && releaseNumber == otherRootObject.getReleaseNumber() && getName().equals(otherRootObject.getName()); } return false; } private transient long lastModifiedTime = System.currentTimeMillis(); public synchronized void updateLastModifiedTime() { lastModifiedTime = System.currentTimeMillis(); } // Must be synchronized to avoid read/write conflicts. public synchronized long getLastModifiedTime() { return lastModifiedTime; } public void registerNewObject(IPersist persist) { if (removedObjects != null && removedObjects.contains(persist)) { removedObjects.remove(persist); } else { persist.flagChanged(); doGetRegisteredNewObjects().add(persist); } } public boolean unregisterNewObject(IPersist persist) { if (newObjects != null) { return newObjects.remove(persist); } return false; } public boolean unregisterRemovedObject(IPersist persist) { if (removedObjects != null) { return removedObjects.remove(persist); } return false; } public void registerRemovedObject(IPersist persist) { if (newObjects != null && newObjects.contains(persist)) { newObjects.remove(persist); } else { doGetRegisteredRemovedObjects().add(persist); } } public boolean isRegisteredNew(IPersist o) { return newObjects != null && newObjects.contains(o); } public boolean isRegisteredRemoved(IPersist o) { return removedObjects != null && removedObjects.contains(o); } public Iterator<IPersist> getRegisteredNewObjects() { return Collections.<IPersist> unmodifiableList(doGetRegisteredNewObjects()).iterator(); } public Iterator<IPersist> getRegisteredRemovedObjects() { return Collections.<IPersist> unmodifiableList(doGetRegisteredRemovedObjects()).iterator(); } protected List<IPersist> doGetRegisteredNewObjects() { if (newObjects == null) newObjects = new ArrayList<IPersist>(); return newObjects; } protected List<IPersist> doGetRegisteredRemovedObjects() { if (removedObjects == null) removedObjects = new ArrayList<IPersist>(); return removedObjects; } public void clearRegisteredRemovedObjects() { removedObjects = null; } public void clearRegisteredNewObjects() { newObjects = null; } public void clearEditingState(IPersist persist) { persist.clearChanged(); UUID persistUuid = persist.getUUID(); removeFromList(removedObjects, persistUuid); removeFromList(newObjects, persistUuid); } private static void removeFromList(List<IPersist> list, UUID persistUuid) { if (list == null) { return; } Iterator<IPersist> it = list.iterator(); while (it.hasNext()) { if (persistUuid.equals(it.next().getUUID())) { it.remove(); } } } }