/* license-start * * Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>. * * This program 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 version 3. * * 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 General Public License for more details, at <http://www.gnu.org/licenses/>. * * Contributors: * Crispico - Initial API and implementation * * license-end */ package org.flowerplatform.communication.tree; 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 java.util.concurrent.locks.ReentrantReadWriteLock; import org.flowerplatform.communication.channel.CommunicationChannel; import org.flowerplatform.communication.tree.remote.GenericTreeStatefulService; import org.flowerplatform.communication.tree.remote.PathFragment; /** * Holds information on server regarding a tree node. * * <p> * It's build as a tree structure. * * @author Cristi * @author Cristina * */ public class NodeInfo { /** * @see Getter doc. * */ private Object node; /** * @see Getter doc. * */ private NodeInfo parent; /** * @see Getter doc. * */ private PathFragment pathFragment; /** * @see Getter doc. * */ private List<NodeInfo> children = new ArrayList<NodeInfo>(); /** * @see Getter doc. * */ private List<NodeInfoClient> clients = new ArrayList<NodeInfoClient>(); /** * */ private List<NodeInfoClient> clientsReadOnly = Collections.unmodifiableList(clients); private Map<String, Object> customData; /** * The server object representing the node on client. * */ public Object getNode() { return node; } /** * */ public void setNode(Object node) { this.node = node; } /** * The child nodes of the current node. Needed * for cleanup, when a node is removed from the server. * * @see GenericTreeStatefulService#dispatchContentUpdate() * */ public List<NodeInfo> getChildren() { return children; } /** * */ public void setChildren(List<NodeInfo> children) { this.children = children; } /** * The parent {@link NodeInfo}. * Used to create this node's structure. * */ public NodeInfo getParent() { return parent; } /** * */ public void setParent(NodeInfo parent) { this.parent = parent; } /** * The path for the node. * Used to communicate with clients. * */ public PathFragment getPathFragment() { return pathFragment; } /** * */ public void setPathFragment(PathFragment pathFragment) { this.pathFragment = pathFragment; } /** * */ public List<NodeInfoClient> getClients() { return clientsReadOnly; } /** * */ private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); /** * */ public ReentrantReadWriteLock getReadWriteLock() { return readWriteLock; } /** * */ public NodeInfoClient getNodeInfoClientByCommunicationChannelThreadSafe(CommunicationChannel communicationChannel, String statefulClientId, GenericTreeStatefulService service) { readWriteLock.readLock().lock(); try { // we use iteration with for instead of for each, because new elements might // be added to the list, and the iterator would complain. Cf. this method comment, // this kind of concurrent read/write access is valid. However, we wouldn't have liked // the opposite: to have elements removed. That's why we use the read/write lock for (int i = 0; i < clients.size(); i++) { NodeInfoClient client = clients.get(i); if (client.getCommunicationChannel().equals(communicationChannel) && (statefulClientId == null || client.getStatefulClientId(service).equals(statefulClientId))) { return client; } } // no client found return null; } finally { readWriteLock.readLock().unlock(); } } /** * */ public NodeInfoClient removeNodeInfoClientByCommunicationChannel(CommunicationChannel communicationChannel, String statefulClientId, GenericTreeStatefulService service) { readWriteLock.writeLock().lock(); try { for (Iterator<NodeInfoClient> iter = clients.iterator(); iter.hasNext(); ) { NodeInfoClient client = iter.next(); if (client.getCommunicationChannel().equals(communicationChannel) && (statefulClientId == null || client.getStatefulClientId(service).equals(statefulClientId))) { // found iter.remove(); if (statefulClientId != null) { // only this node must be removed, so return return client; } } } return null; } finally { readWriteLock.writeLock().unlock(); } } /** * */ public void addNodeInfoClient(NodeInfoClient client) { clients.add(client); } /** * */ public String toString() { return String.format("%s[path=%s]", getClass().getSimpleName(), getPathFragment()); } @Override public boolean equals(Object obj) { if (!(obj instanceof NodeInfo)) { return false; } return node.equals(((NodeInfo) obj).node); } public Map<String, Object> getCustomData() { if (customData == null) { customData = new HashMap<String, Object>(); } return customData; } }