/* * @@COPYRIGHT@@ */ package com.cosylab.cdb.client; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.StringTokenizer; import java.util.logging.Logger; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.omg.CORBA.ORB; import org.omg.CosNaming.NameComponent; import org.omg.CosNaming.NamingContext; import org.omg.CosNaming.NamingContextHelper; import org.xml.sax.InputSource; import alma.acs.util.ACSPorts; import alma.acs.util.AcsLocations; import alma.cdbErrType.wrappers.AcsJCDBXMLErrorEx; import com.cosylab.CDB.DAL; import com.cosylab.CDB.DALChangeListener; import com.cosylab.CDB.DALChangeListenerOperations; import com.cosylab.CDB.DALChangeListenerPOA; import com.cosylab.CDB.DALHelper; import com.cosylab.CDB.DAOOperations; import com.cosylab.cdb.jdal.DAOImpl; import com.cosylab.cdb.jdal.XMLHandler; /** * Class managing CDB access (establishing connection to the CDB, * observing DAO changes, and providing accessor methods to the user). * <p> * It retrieves and caches XML data from the CDB unless property <code>DAO.remote</code> is <code>true</code>. * * @author Matej Sekoranja (matej.sekoranja@cosylab.com) * @version @@VERSION@@ */ public class CDBAccess { /** * The constant denoting the prefix of the DAL configuration keys. */ private static final String CDBDAL_PREFIX = "DAL"; /** * The constant denoting the prefix of the DAO configuration keys. */ private static final String CDBDAO_PREFIX = "DAO"; /** * The constant denoting the name of default DAL reference property key. */ private static final String CDBDAL_DEFAULT_REFERENCE = CDBDAL_PREFIX + ".defaultReference"; /** * The constant denoting the default DAL reference (if none is set). */ private static final String DEFAULT_REFERENCE = AcsLocations.convertToCdbLocation(ACSPorts.getIP(), ACSPorts.getCDBPort()); /** * The constant denoting the name of default DAO remote switch property key. */ private static final String CDBDAO_REMOTE = CDBDAO_PREFIX + ".remote"; /** * Switch to turn remote DAO on. */ private boolean remoteDAO = true; /** * Default DAL CORBA reference (if not authority is specified). */ private String defaultDAL = null; /** * DAL reference. */ private DAL dalReference = null; /** * CORBA Object Request Broker (ORB) reference. */ private ORB orb = null; /** * Logger. */ private final Logger logger; /** * Listener for CDB change. */ private ChangeListener changeListener = null; /** * Bypass 'resolve DAL from naming service' switch. */ private boolean bypassNameService = false; /** * This private class will handle CDB restart or data change in the CDB. * * @author Dragan Vitas (dragan.vitas@cosylab.com) * @version @@VERSION@@ */ private class ChangeListener extends DALChangeListenerPOA { /** * Map of monitored CURLs entities. */ private HashMap curlMap = new HashMap(); /** * Map of registered listeners on DALs. */ private HashMap dalMap = new HashMap(); /** * CORBA instance of <code>DALChangeListener</code> object. */ private DALChangeListener cl = null; /** * Reconnect connectable in separate thread to avoid blocking. */ private class ReconnectTask extends Thread { /** * Object to be reconnected. */ DAOProxy connectable; /** * Constructor of the class. * * @param connectable object to be reconnected. */ public ReconnectTask(DAOProxy connectable) { this.connectable = connectable; } /** * Thread worker implementation. */ public void run() { final int RETRIES = 3; for (int i = 0; i < RETRIES; i++) { try { internalConnect(connectable); break; } catch (Throwable th) { // if we cannot reestablish connection the exception will report elsewhere } } } } /** * Called from DAL server when curl changed or when DAL startups. * NOTE: reconnection does not work if DAL is running on Java (Sun) CORBA - server problem (DAL reference!) * * @param curl changed entity in CDB * @see DALChangeListenerOperations#object_changed(String) */ public void object_changed(String curl) { // get the connectable from cache and reconnect it in separate thread DAOProxy connectable = (DAOProxy) curlMap.get(curl); if (connectable != null) new ReconnectTask(connectable).start(); } /** * Add listener for curl on DAL server so the connectable object can be reconnected. * * @param dal the reference of the DAL server where the curl is obtained * @param curl the path for our DAO object * @param conn the object for which we made the DAO */ public void handle(DAL dal, String curl, DAOProxy conn) { // create CORBA instance if (cl == null) cl = changeListener._this(orb); // register DAL listener, if not already Integer listenerID = (Integer) dalMap.get(dal); if (listenerID == null) { int id = dal.add_change_listener(cl); listenerID = new Integer(id); dalMap.put(dal, listenerID); } // listen for <code>curl</code> changes dal.listen_for_changes(curl, listenerID.intValue()); // after all registrations are done successfully // remember that we are monitoring <code>curl</code> curlMap.put(curl, conn); } /** * Checks if object with the given CURL is already registered to this listener. * @param curl the path for our DAO object */ public boolean isRegistered(String curl) { return curlMap.containsKey(curl); } /** * Unregister this listener from DAL server(s). */ public void destroy() { DAL dal; Integer listenerID; Iterator iter = dalMap.keySet().iterator(); while (iter.hasNext()) { dal = (DAL) iter.next(); listenerID = (Integer) dalMap.get(dal); // do not bail out if derefistration of one listener fails try { dal.remove_change_listener(listenerID.intValue()); } catch (Exception ex) { // no-op. } } // clear caches dalMap.clear(); curlMap.clear(); } } /** * Constructor used only when {@link #setDAL(DAL)} is called afterwards. * @param logger logger. */ public CDBAccess(Logger logger) { this(null, logger); } /** * Constructor. * @param orb CORBA ORB. * @param logger logger. */ public CDBAccess(ORB orb, Logger logger) { this.orb = orb; this.logger = logger; readConfiguration(); changeListener = new ChangeListener(); } /** * Performs the connect of the specified DAO. * * @param curl DAO curl, non-<code>null</code> * @return DAO proxy. * @throws RuntimeException on failure */ public DAOProxy createDAO(String curl) { DAOProxy proxy = new DAOProxy(curl); internalConnect(proxy); return proxy; } /** * Performs the connect of the specified DAO. * * @param proxy the proxy to connect, non-<code>null</code> */ private void internalConnect(DAOProxy proxy) { String curl = null; try { checkDALConnection(); } catch (Throwable th) { // TODO @todo replace RuntimeException re = new RuntimeException("Failed to obtain DAO for proxy '" + proxy + "'.", th); throw re; } DAOOperations dao = null; try { curl = proxy.getCURL(); if (remoteDAO) { dao = dalReference.get_DAO_Servant(curl); } else { String xml = dalReference.get_DAO(curl); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser saxParser = factory.newSAXParser(); // use CDB XML handler which does not creates strings... XMLHandler xmlSolver = new XMLHandler(false, logger); saxParser.parse(new InputSource(new StringReader(xml)), xmlSolver); if (xmlSolver.m_errorString != null){ AcsJCDBXMLErrorEx e = new AcsJCDBXMLErrorEx(); e.setErrorString("XML parser error: " + xmlSolver.m_errorString); throw e; //throw new XMLerror("XML parser error: " + xmlSolver.m_errorString); } // create non-CORBA related, silent DAO dao = new DAOImpl(curl, xmlSolver.m_rootNode, null, logger, true); proxy.setElementName(xmlSolver.m_rootNode.getName()); } // register listener, if not already registered if (changeListener != null) { if (!changeListener.isRegistered(curl)) changeListener.handle(dalReference, curl, proxy); } } catch (Throwable th) { // TODO @todo replace RuntimeException re = new RuntimeException("Failed to obtain DAO object for proxy '" + proxy + "'.", th); throw re; } try { proxy.initialize(dao); } catch (Throwable th) { // TODO @todo replace RuntimeException re = new RuntimeException("The proxy '" + proxy + "' rejects the DAO.", th); throw re; } logger.config("Connected to DAO '" + proxy.getCURL() + "'."); } /** * @return the bypassNameService */ public boolean isBypassNameService() { return bypassNameService; } /** * @param bypassNameService the bypassNameService to set */ public void setBypassNameService(boolean bypassNameService) { this.bypassNameService = bypassNameService; } private DAL queryNSForDALReference() { try { org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef); NameComponent path[] = { new NameComponent("CDB", "") }; org.omg.CORBA.Object objectReference = ncRef.resolve(path); DAL dalRef = DALHelper.narrow(objectReference); return dalRef; } catch (Throwable th) { throw new RuntimeException("Failed to get DAL object from the naming service.", th); } } /** * Checks connection status (if already connected) and connects if necessary. */ private void checkDALConnection() { try { if (dalReference == null) { logger.info("Connecting to DAL '" + defaultDAL + "'..."); // connect to DAL if (bypassNameService) { dalReference = DALHelper.narrow(orb.string_to_object(defaultDAL)); if (dalReference == null) // TODO @todo replace throw new RuntimeException("Failed to connect to the DAL object with reference, got 'null' reference."); } else { dalReference = queryNSForDALReference(); } logger.info("Connected to DAL '" + defaultDAL + "'."); } } catch (Throwable th) { logger.info("Failed to connect to DAL '" + defaultDAL + "'."); // TODO @todo replace RuntimeException re = new RuntimeException("Failed to connect to the DAL object with reference '" + defaultDAL + "'.", th); throw re; } } /** * Sets the DAO of the proxy to <code>null</code> * * @param proxy the proxy to disconnect, non-<code>null</code> */ private void internalDisconnect(DAOProxy proxy) { assert (proxy != null); proxy.initialize(null); } /** * Helper method to get all subnodes of the current proxy, removes ".xml" element from the list. * @param proxy proxy whose subnodes to return. * @return array of subnodes. * @throws Throwable exception on failure (e.g. connection failure, etc.) */ public String[] getSubNodes(DAOProxy proxy) throws Throwable { return getSubNodes(proxy, null); } /** * Helper method to get all subnodes of the current proxy, removes ".xml" element from the list. * @param proxy proxy whose subnodes to return. * @param subnode proxy subnode to be queried * @return array of subnodes. * @throws Throwable exception on failure (e.g. connection failure, etc.) */ public String[] getSubNodes(DAOProxy proxy, String subnode) throws Throwable { assert (proxy != null); checkDALConnection(); ArrayList subnodes = new ArrayList(); LinkedList stack = new LinkedList(); if (subnode == null) stack.addLast(proxy.getCURL()); else stack.addLast(proxy.getCURL() + "/" + subnode); while (!stack.isEmpty()) { String parentNode = stack.removeLast().toString(); String nodes = dalReference.list_nodes(parentNode); if (nodes.length() > 0) { StringTokenizer tokenizer = new StringTokenizer(nodes); while (tokenizer.hasMoreTokens()) { String nodeName = tokenizer.nextToken(); if (nodeName.endsWith(".xml")) continue; String fullName = parentNode + "/" + nodeName; stack.addLast(fullName); // strip off relative path subnodes.add(fullName.substring(proxy.getCURL().length()+1)); } } } String[] retVal = new String[subnodes.size()]; subnodes.toArray(retVal); return retVal; } /** * Interprets the configuration delivered by System JVM properties. */ public void readConfiguration() { // system property overrides default configuration, DAL reference defaultDAL = System.getProperty(CDBDAL_DEFAULT_REFERENCE, DEFAULT_REFERENCE); // use remote DAO? remoteDAO = Boolean.valueOf(System.getProperty(CDBDAO_REMOTE, "false")).booleanValue(); } /** * Destroys. */ public void destroy() { if(changeListener != null) changeListener.destroy(); // TODO @todo should DAOProxies be destroyed too? } /** * DAL setter. * @param dal DAL reference. */ public void setDAL(DAL dal) { this.dalReference = dal; } /** * DAL accessor. * @return DAL reference. */ public DAL getDAL() { return dalReference; } /** * Active (connects if necessary) DAL accessor. * @return DAL reference, <code>null</code> if failed to connect. */ public DAL connectAndGetDAL() { try { checkDALConnection(); return dalReference; } catch (Throwable th) { return null; } } }