Package org.nimbustools.ctxbroker.blackboard

Source Code of org.nimbustools.ctxbroker.blackboard.Blackboard

/*
* Copyright 1999-2008 University of Chicago
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package org.nimbustools.ctxbroker.blackboard;

import org.nimbustools.ctxbroker.Identity;
import org.nimbustools.ctxbroker.ContextBrokerException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

/**
* Each resource gets one blackboard. Setting this up to keep its own
* Hashtable of all instances to leave room for persistence in the future.
*/
public class Blackboard {

    // -------------------------------------------------------------------------
    // STATIC VARIABLES
    // -------------------------------------------------------------------------

    private static final Log logger =
            LogFactory.getLog(Blackboard.class.getName());

    // The "database" of Blackboard objects
    // String ID --> Blackboard object
    private static final Hashtable<String,Blackboard> all = new Hashtable<String, Blackboard>(8);

    // -------------------------------------------------------------------------
    // INSTANCE VARIABLES
    // -------------------------------------------------------------------------

    private Identity[] allIdentityCache = null;


    // All nodes this blackboard knows about.
    // Integer workspaceID --> Node object
    private final Hashtable<Integer, Node> allNodes =
            new Hashtable<Integer, Node>(64);

    // All provided roles this blackboard knows about.
    // String roleName --> ProvidedRole object
    private final Hashtable<String, ProvidedRole> allProvidedRoles =
            new Hashtable<String, ProvidedRole>(16);

    // All data this blackboard knows about.
    // String roleName --> RequiredData object
    private final Hashtable<String, RequiredData> allRequiredDatas =
            new Hashtable<String, RequiredData>(16);

    // All RequiredRole objects this blackboard knows about.
    // No key to use for a hashtable because each is not only the
    // name but host and key requirements also.  See RequiredRole
    // equals/hashCode method.  No need for a set lock because set is
    // always accessed and mutated under this.dbLock.
    private final Set<RequiredRole> allRequiredRoles =
            new HashSet<RequiredRole>(16);

    private boolean needsRefresh = true;

    // One lock per blackboard.  Locking "up high" for now, optimize later
    private final Object dbLock = new Object();

    // Mainly for the future DB implementation (and logging)
    private final String id;

    // flips once and only once to true when all report OK
    private boolean allOK = false;

    // flips once and only once to true when a node reports an error
    private boolean oneErrorOccured = false;

    // stopping condition + for resource prop
    private int numNodes = 0;
    private int totalNodes = 0;


    // -------------------------------------------------------------------------
    // CONSTRUCTOR
    // -------------------------------------------------------------------------

    Blackboard(String id) {
        if (id == null) {
            throw new IllegalArgumentException("id cannot be null");
        }
        this.id = id;
    }

   
    // -------------------------------------------------------------------------
    // FACTORY
    // -------------------------------------------------------------------------

    /**
     * Get or create a blackboard for a contextualization context.
     *
     * @param id may not be null
     * @return Blackboard
     */
    public synchronized static Blackboard createOrGetBlackboard(String id) {

        if (id == null) {
            throw new IllegalArgumentException("id cannot be null");
        }

        Blackboard bb = all.get(id);
        if (bb == null) {
            bb = new Blackboard(id);
            all.put(id, bb);
            return bb;
        } else {
            return bb;
        }
    }

    public CtxStatus getStatus() {
        synchronized (this.dbLock) {
            CtxStatus status = new CtxStatus();

            status.setAllOk(this.allOK);
            status.setComplete(isComplete());
            status.setErrorOccurred(this.oneErrorOccured);

            status.setPresentNodeCount(this.numNodes);
            status.setTotalNodeCount(this.totalNodes);

            return status;
        }
    }

    boolean isComplete() {
        synchronized (this.dbLock) {
            boolean complete;
            if (this.needsRefresh()) {
                this.refresh();
                complete = !this.needsRefresh();
            } else {
                complete = true;
            }

            // maybe we haven't heard from all the agents yet
            if (this.totalNodes <= 0 ||
                    this.totalNodes != this.numNodes) {
                complete = false;
            }

            return complete;
        }
    }

   
    // -------------------------------------------------------------------------
    // MATCHING
    // -------------------------------------------------------------------------

    // assumed under lock
    private void refreshNowNeeded() {
        this.needsRefresh = true;
    }

    // assumed under lock
    private void refreshNotNeeded() {
        this.needsRefresh = false;
    }

    private boolean needsRefresh() {
        return this.needsRefresh;
    }

    // assumed under lock
    private void refresh() {

        if (!this.needsRefresh()) {
            return;
        }

        logger.debug("Beginning refresh of blackboard '" + this.id + "'");

        StringBuffer tracebuf = null;
        if (logger.isDebugEnabled()) {
            tracebuf = new StringBuffer();
            tracebuf.append("\n==========================================\n");
        }

        boolean stillNeedsRefresh = false;

        // look for provided roles not matched yet with required roles

        for (RequiredRole requiredRole : this.allRequiredRoles) {

            if (tracebuf != null) {
                tracebuf.append("Required role: ")
                        .append(requiredRole)
                        .append("\n");
            }

            // delete old list and start over:
            requiredRole.clearProviders();

            final ProvidedRole providedRole = this.allProvidedRoles.get(requiredRole.getName());

            if (providedRole == null) {
                if (tracebuf != null) {
                    tracebuf.append("  - Found no providers\n");
                }
                stillNeedsRefresh = true;
                continue;
            }

            int count = 0;
            final Iterator<Identity> iter2 = providedRole.getProviders();
            while (iter2.hasNext()) {
                requiredRole.addProvider(iter2.next());
                count += 1;
            }

            if (tracebuf != null) {
                tracebuf.append("  - Added ")
                        .append(count)
                        .append(" providers\n");
            }
        }


        // check the current node count against the expected node count

        if (this.totalNodes > 0 &&
                this.totalNodes == this.numNodes) {

            ArrayList<Identity> allIdentities =
                    new ArrayList<Identity>();
            Enumeration<Node> nodes = this.allNodes.elements();
            while (nodes.hasMoreElements()) {
                Node node = nodes.nextElement();
                Enumeration<Identity> ids = node.getIdentities();
                while (ids.hasMoreElements()) {
                    allIdentities.add(ids.nextElement());
                }
            }

            this.allIdentityCache =
                    allIdentities.toArray(
                            new Identity[allIdentities.size()]);

            if (tracebuf != null) {
                tracebuf.append("  - Instantiated, number of identities: ")
                        .append(this.allIdentityCache.length)
                        .append("\n");
            }

        } else {

            if (this.allIdentityCache != null) {

                this.allIdentityCache = null;
                if (tracebuf != null) {
                    tracebuf.append("  - Invalidated because of missing ")
                            .append("IP or hostname\n");
                }

            } else {

                if (tracebuf != null) {
                    tracebuf.append("  - Remains invalid because of ")
                            .append("missing nodes\n");
                }
            }

            stillNeedsRefresh = true;

        }


        String tail = "\nFurther refresh";
        if (stillNeedsRefresh) {
            this.refreshNowNeeded();
        } else {
            this.refreshNotNeeded();
            tail += " not";
        }
        tail += " needed currently.\n";
       
        if (tracebuf != null) {
            logger.trace("\n\nRefreshed blackboard '" + this.id + "'" +
                             tracebuf.toString() + tail);
        } else {
            logger.debug("Refreshed blackboard '" + this.id + "'" + tail);
        }
    }


    // -------------------------------------------------------------------------
    // DATA ADDITIONS
    // -------------------------------------------------------------------------

    public void injectData(String dataName, String value)
            throws ContextBrokerException {

        if (dataName == null || dataName.trim().length() == 0) {
            // does not happen when object is created via XML (which is usual)
            throw new ContextBrokerException("Empty data element name (?)");
        }

        synchronized (this.dbLock) {
            this._newData(dataName, value);
        }
    }
   
    // -------------------------------------------------------------------------
    // NODE ADDITIONS
    // -------------------------------------------------------------------------

    /**
     * Creates new Node, treats provides and requires documents in the
     * empty, provided interpretation.
     *
     * @param workspaceID workspace ID
     * @param nodeIdentities identity objects filled by factory/service.
     *        What 'is' already based on creation request or initialization.
     *        Once passed to this method, caller must discard pointers
     *        (avoids needing to clone it).
     * @param allIdentitiesRequired This node expects identity info for all
     * nodes, not just those that fill a required role
     * @param requiredRoles The roles required by this node
     * @param requiredData The data required by this node
     * @param providedRoles the Roles provided by this node
     * @param totalNodesFromAgent total number of nodes reported by ctx agent
     * @throws ContextBrokerException illegalities
     */
    public void addWorkspace(Integer workspaceID,
                             Identity[] nodeIdentities,
                             boolean allIdentitiesRequired,
                             RequiredRole[] requiredRoles,
                             DataPair[] requiredData,
                             ProvidedRoleDescription[] providedRoles,
                             int totalNodesFromAgent)
            throws ContextBrokerException {

        if (workspaceID == null) {
            throw new IllegalArgumentException("workspaceID cannot be null");
        }
        if (nodeIdentities == null || nodeIdentities.length == 0) {
            throw new IllegalArgumentException("'real' nodeIdentities cannot be " +
                        "null or empty, contextualization is not possible. " +
                        "If a workspace has no NICs, requires and provides " +
                        "documents should not have been given.  If they " +
                        "existed, the factory/service should have rejected " +
                        "the request. " +
                        "However this happened it is a programming error.");
        }

        synchronized (this.dbLock) {

            if (this.totalNodes > 0) {
                if (this.totalNodes != totalNodesFromAgent) {
                    throw new ContextBrokerException("Context '" +
                        this.id + "' has received a conflicting " +
                        "total node count.  Was previously " + this.totalNodes +
                        "but has received a cluster definition with a total " +
                        "of " + totalNodesFromAgent);
                }
            } else {
                this.totalNodes = totalNodesFromAgent;
            }

            this.numNodes += 1;

            if (this.numNodes > this.totalNodes) {
                throw new ContextBrokerException("Context '" +
                        this.id + "' has heard from a new agent which " +
                        "makes the total node count exceed the theoretical" +
                        "maximum from the cluster definitions (" +
                        this.totalNodes + ").");
            }

            // invalidate cache if it existed
            this.allIdentityCache = null;

            Node node = this.allNodes.get(workspaceID);
            if (node != null) {
                throw new ContextBrokerException("Blackboard has " +
                        "already added node with ID #" + workspaceID);
            }

            String[] requiredDataNames = null;
            if (requiredData != null && requiredData.length > 0) {

                // set up names of data this node needs

                requiredDataNames = new String[requiredData.length];
                for (int i = 0; i < requiredData.length; i++) {
                    requiredDataNames[i] = requiredData[i].getName();
                }

                // If the contextualization definition included a value for
                // the data already, register it into the known-data store.
                // _intakeData also creates new RequiredData objects for any
                // newly seen data name (no matter if the value is present
                // or not).
                _intakeData(requiredData);
            }

            node = new Node(workspaceID, requiredDataNames);
            for (Identity identity : nodeIdentities) {
                node.addIdentity(identity);
            }

            if (providedRoles != null && providedRoles.length > 0) {
                this.handleProvidesRoles(node, providedRoles);
            }

            handleNewRequires(node, allIdentitiesRequired, requiredRoles);

            this.allNodes.put(workspaceID, node);
            this.refreshNowNeeded();
        }
    }

    // no args are null and roles.length > 0
    private void handleProvidesRoles(Node node,
                                     ProvidedRoleDescription[] roles)
            throws ContextBrokerException {

        for (ProvidedRoleDescription roleDesc : roles) {

            final String roleName = roleDesc.getRoleName();

            // we are still under this.dbLock lock, check then act here is ok
            ProvidedRole role =
                    this.allProvidedRoles.get(roleName);
            if (role == null) {
                role = new ProvidedRole(roleName);
                this.allProvidedRoles.put(roleName, role);
            }

            final String iface = roleDesc.getIface();
            if (iface != null) {

                // only this specific interface provides the role

                final Identity identity = node.getParticularIdentity(iface);
                if (identity == null) {
                    throw new ContextBrokerException("There is an " +
                            "interface specification ('" + iface + "') in " +
                            "the provides section for role '" + roleName +
                            "' that does not have matching identity element " +
                            "with that interface name.  Cannot " +
                            "contextualize #" + node.getId());
                }
                role.addProvider(identity);

            } else {

                // each identity provides this role

                final Enumeration<Identity> identities = node.getIdentities();
                while (identities.hasMoreElements()) {
                    role.addProvider(identities.nextElement());
                }
            }
        }
    }

    // no args are null
    private void handleNewRequires(Node node, boolean allIdentitiesRequired,
                                   RequiredRole[] requiredRoles)
            throws ContextBrokerException {

        final int workspaceID = node.getId();

        node.setAllIdentitiesRequired(allIdentitiesRequired);

        if (requiredRoles != null && requiredRoles.length > 0) {
           
            this.handleRequiredRoles(node, requiredRoles);

        } else if (logger.isTraceEnabled()) {
           
            logger.trace("Requires section for #" + workspaceID + " has " +
                    "no role-required elements." +
                    "  Allowing, perhaps the only thing required by " +
                    "this node is the contextualization context's " +
                    "all-identity list and/or just data elements.");
        }
    }

    // no args are null and datas.length > 0, always call under this.dbLock
    private void _intakeData(DataPair[] datas) {

        for (DataPair data : datas) {
            final String dataName = data.getName();
            this._newData(dataName, data.getValue());
        }
    }

    private void _newData(String name, String value) {

        if (name == null) {
            throw new IllegalArgumentException("name may not be null");
        }

        // we are under this.dbLock lock, check then act here is ok
        RequiredData data = this.allRequiredDatas.get(name);
       
        if (data == null) {
            data = new RequiredData(name);
            this.allRequiredDatas.put(name, data);
        }

        if (value != null) {
            data.addNewValue(value);
        }
    }

    private boolean isOneDataValuePresent(String dataname) {
        final RequiredData data =
                this.allRequiredDatas.get(dataname);
        return data != null && data.numValues() > 0;
    }

    private List<DataPair> getDataValues(String dataname) {
        final RequiredData data =
                this.allRequiredDatas.get(dataname);
        if (data != null) {
            return data.getDataList();
        } else {
            return null;
        }
    }

    // no args are null and roles.length > 0
    private void handleRequiredRoles(Node node,
                                     RequiredRole[] roles)
            throws ContextBrokerException {


        for (RequiredRole role : roles) {

            // We expect a lot of duplicates, HashSet does not add if exists
            // in list already.
            boolean didNotExist = this.allRequiredRoles.add(role);
            if (didNotExist && logger.isTraceEnabled()) {
                logger.trace("Found new RequiredRole for blackboard '" +
                        this.id + "': " + role + " -- cardinality now " +
                        this.allRequiredRoles.size());
            }

            // Copy reference to node specific list.
            // There is a possibility client provided requires schema with
            // multiple duplicate elements for some reason.  Just silently
            // allowing that (i.e. with server side log message and not
            // exception client will see).

            if (!didNotExist) {
                // It existed already.  We need to add the exact object to the
                // node's required role's, not the duplicate we created above.
                boolean found = false;
                for (RequiredRole aRole : this.allRequiredRoles) {
                    if (aRole.equals(role)) {
                        role = aRole;
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    throw new ContextBrokerException("Set contained a " +
                            "required role already but we cannot find the " +
                            "object in the set's iterator (?).  Role: " + role);
                }
            }

            didNotExist = node.addRequiredRole(role);
            if (!didNotExist) {
                logger.warn("Client provided requires document with " +
                        "duplicate required role elements.  That is not " +
                        "expected but ignoring and handling it. Required " +
                        "role that trigged this: " + role + ".  Node #" +
                        node.getId());
            }
        }
    }


    // -------------------------------------------------------------------------
    // NODE RETRIEVAL/UPDATES
    // -------------------------------------------------------------------------

    // (no service or client updates, VM assertions allowed)
   
    public NodeManifest retrieve(Integer workspaceID)

                throws ContextBrokerException {

        if (workspaceID == null) {
            throw new IllegalArgumentException("workspaceID cannot be null");
        }
       
        final Node node = this.allNodes.get(workspaceID);
        if (node == null) {
            throw new ContextBrokerException("Blackboard is not aware " +
                    "of node with ID #" + workspaceID);
        }

        synchronized(this.dbLock) {

            this.refresh();


            // Check if identities are available

            final List<Identity> identities;
            final boolean allIdentities = node.isAllIdentitiesRequired();
            if (allIdentities) {

                if (this.allIdentityCache == null) {

                    return null;

                } else {
                    identities = Arrays.asList(this.allIdentityCache);
                }
            } else {
                identities = new ArrayList<Identity>();
            }

            // Check if all data is available.  At least one value of each
            // data requirement constitutes "available"

            final ArrayList<DataPair> data = new ArrayList<DataPair>();

            for (String reqData : node.getRequiredDataNames()) {
                if (this.isOneDataValuePresent(reqData)) {
                    data.addAll(this.getDataValues(reqData));
                } else {

                    if (logger.isTraceEnabled()) {
                        logger.trace("Not constructing node manifest because " +
                                "suppressIncomplete is true, and a required " +
                                "data item for this node (#" +
                                node.getId() + ") is not present: ");
                    }

                    return null; // *** EARLY RETURN ***
                }
            }

            final ArrayList<RoleIdentityPair> roles = new ArrayList<RoleIdentityPair>();

            final Iterator<RequiredRole> iter = node.getRequiredRoles();
            while (iter.hasNext()) {
                final RequiredRole aRole = iter.next();
                for (Identity provider : aRole.getProviders()) {
                    roles.add(new RoleIdentityPair(aRole.getName(), provider));

                    if (!allIdentities) {
                        identities.add(provider);
                    }
                }
            }

            return new NodeManifest(identities, data, roles);
        }
    }


    // -------------------------------------------------------------------------
    // NODE STATUS
    // -------------------------------------------------------------------------

    public void okExit(Integer workspaceID)
            throws ContextBrokerException {

        final Node node = this.allNodes.get(workspaceID);
        if (node == null) {
            throw new ContextBrokerException("unknown workspace #" + workspaceID);
        }
        synchronized (this.dbLock) {
            CtxResult result = node.getCtxResult();

            if (result.hasOkOccurred() || result.hasErrorOccurred()) {
                throw new ContextBrokerException("already received " +
                        "exiting report from workspace #" + workspaceID);
            }

            result.setOkOccurred(true);

            // check if all are now OK
            for (Enumeration<Node> e = this.allNodes.elements(); e.hasMoreElements();) {
                final Node aNode = e.nextElement();
                if (!aNode.getCtxResult().hasOkOccurred()) {
                    return;
                }
            }
            this.allOK = true;
        }
    }

    public void errorExit(Integer workspaceID,
                          short exitCode,
                          String errorMessage)
            throws ContextBrokerException {

        final Node node = this.allNodes.get(workspaceID);
        if (node == null) {
            throw new ContextBrokerException("unknown workspace #" + workspaceID);
        }
        synchronized (this.dbLock) {
            CtxResult result = node.getCtxResult();

            if (result.hasOkOccurred() || result.hasErrorOccurred()) {
                throw new ContextBrokerException("already received " +
                        "exiting report from workspace #" + workspaceID);
            }

            result.setErrorOccurred(true);
            result.setErrorCode(exitCode);
            result.setErrorMessage(errorMessage);

            this.oneErrorOccured = true;
        }
    }
   

    public List<NodeStatus> identities(boolean allNodes, String host, String ip) {

        synchronized (this.dbLock) {
            if (allNodes) {
                final List<NodeStatus> list = new ArrayList<NodeStatus>();
                for (Enumeration<Node> e = this.allNodes.elements(); e.hasMoreElements();) {
                    list.add(new NodeStatus(e.nextElement()));
                }
                return list;

            } else {
                final Node node = this.findNode(host, ip);
                if (node != null) {
                    return Collections.singletonList(new NodeStatus(node));
                }
                return Collections.emptyList();
            }
        }
    }

    private Node findNode(String host, String ip) {

        if (host == null && ip == null) {
            return null;
        }

        for (Enumeration<Node> e = this.allNodes.elements(); e.hasMoreElements();) {

            final Node node = e.nextElement();

            for (Enumeration e2 = node.getIdentities(); e2.hasMoreElements();) {
                final Identity ident = (Identity) e2.nextElement();

                if (host != null) {
                    if (host.equals(ident.getHostname())) {
                        return node;
                    }
                } else {
                    if (ip.equals(ident.getIp())) {
                        return node;
                    }
                }
            }
        }

        return null;
    }

}
TOP

Related Classes of org.nimbustools.ctxbroker.blackboard.Blackboard

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.