/* * DiscovererMediator.java * * Created on July 2, 2001, 4:00 PM */ package context.arch.discoverer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import context.arch.BaseObject; import context.arch.comm.DataObject; import context.arch.comm.DataObjects; import context.arch.comm.RequestObject; import context.arch.comm.clients.IndependentCommunication; import context.arch.comm.language.MessageHandler; import context.arch.discoverer.component.dataModel.AbstractDataModel; import context.arch.discoverer.component.dataModel.DiscovererDataModel; import context.arch.discoverer.lease.Lease; import context.arch.discoverer.lease.LeasesKeeper; import context.arch.discoverer.query.AbstractQueryItem; import context.arch.subscriber.AbstractSubscriber; import context.arch.subscriber.Subscribers; import context.arch.util.Error; /** * This mediator allows to handle the dialog between the discoverer and * the DiscovererDataModel, the LeasesKeeper. * It is able to store components into the database, to update and remove them. * It handles queries and subscription queries. * It asks the discoverer to check the components' liveliness * * @author Agathe */ public class DiscovererMediator { private static final Logger LOGGER = Logger.getLogger(DiscovererMediator.class.getName()); static {LOGGER.setLevel(Level.WARNING);} // this should be set in a configuration file /** * The Discoverer object the DiscovererMediator works for */ private Discoverer discoverer; /** * The message handler (the Discoverer object) used to decode and encode * DataObject */ @SuppressWarnings("unused") private MessageHandler mh; /** * The database object where the DiscovererMediator stores the index tables */ private AbstractDataModel dataModel; /** * The object containing a timer that triggers the check of components' * liveliness */ private LeasesKeeper leasesKeeper; /** * Boolean to keep a logfile */ public boolean useLogFile = false; /** * Tags used for the log file */ private final static String ENTRY_STRING = "entry:"; private final static String ADD_COMP = "addComp:"; private final static String REMOVE_COMP = "removeComp:"; /** * The default name for the Discoverer database log file */ @SuppressWarnings("unused") private String filename = "discoverer-database.xml"; /** * Creates new DiscovererMediator * * @param discoverer The discoverer object * @param keppLogFile If true, the log file is updated */ public DiscovererMediator(Discoverer discoverer, boolean keepLogFile) { dataModel = new DiscovererDataModel(); this.discoverer = discoverer; mh = (MessageHandler) discoverer; leasesKeeper = new LeasesKeeper(this); useLogFile = keepLogFile; filename = Subscribers.WIDGET_SUBSCRIPTIONS_DIR + this.discoverer.getId () + "-database.xml"; setRegisteredComponents (); } /** * This method allows to register a ComponentDescription object * * @param comp the ComponentDescription object * @param lease The lease specified for this ComponentDescripion object * @return Error */ public Error add(ComponentDescription comp, Lease lease) { Error error = new Error(Error.NO_ERROR); // If it already exists : removes it ComponentDescription old = dataModel.remove(comp.id); if (old != null) { if (useLogFile) { removeFromLog(old); } leasesKeeper.removeLease(comp.id); } // Now adds it dataModel.add(comp); if (useLogFile) { addToLog(comp); } // Registers the lease lease.setComponentIndex(comp.id); leasesKeeper.addLease(lease); return error; } /** * Updates a lease for a registered component * * @param compId The id of the component * @param lease The new Lease object * @return Error */ public Error updateLease(String compId, Lease lease){ Error err = new Error(); lease.setComponentIndex(compId); // Updates the lease if (leasesKeeper.renewLease(lease)) { err.setError (Error.NO_ERROR); } else { err.setError (Lease.LEASE_ERROR); } return err; } /** * Take a query and returns the corresponding (multiple) components * * @param query * @return DataObject Contains the component descriptions of the component * matching the query */ public DataObject search(AbstractQueryItem<?,?> query) { Collection<ComponentDescription> components = findComponents(query); DataObjects content = new DataObjects(); DataObject result = null; for (ComponentDescription comp : components) { DataObject compDataObj = comp.getBasicDataObject(); DataObject newContent = new DataObject(Discoverer.DISCOVERER_QUERY_REPLY_CONTENT, compDataObj.getChildren()); LOGGER.info("\n\n a content = " + newContent); content.addElement(newContent); } result = new DataObject(Discoverer.DISCOVERER_QUERY_REPLY_CONTENTS, content); return result; } /** * Gets an AbstractQueryObject and returns a array with for each index * of registered components a value yes/no to indicate if this component * fits the query or not * * TODO : complete the search to remove the widget included in server: USEFUL?? --who? * * @param query The abstract query object * @return */ public Collection<ComponentDescription> findComponents(AbstractQueryItem<?,?> query) { // Result from the data Model // System.out.println("DiscovererMediator.findComponents query: " + query); // System.out.println("DiscovererMediator.findComponents dataModel: " + dataModel); Collection<ComponentDescription> components = query.search(dataModel); // TODO should not delegate to an external class that should not need to know about internal formats // Do some complementary process: for example, is a server has subscribed to a widget. return components; } /** * Returns a printable version of this object: the database content, the * leases. * * @return String */ public String toString(){ StringBuffer sb = new StringBuffer("DiscovererMediator"); sb.append (dataModel.toString ()); sb.append ("\n\n"); sb.append (leasesKeeper.toString ()); return sb.toString (); } /** * Removes a component from the discoverer base * * @param compId The ComponentDescription's id * @return Error */ public Error remove(String compId){ Error error = new Error(Error.NO_ERROR); ComponentDescription removed = dataModel.remove(compId); // remove it // Update the log file if (removed != null && useLogFile){ removeFromLog(removed); } // Remove the lease leasesKeeper.removeLease(compId); // Remove from the subscriber for (AbstractSubscriber sub : discoverer.subscribers.values()) { String subId = sub.getSubscriptionId(); if (subId.startsWith(compId) // && sub instanceof DiscovererSubscriber // I don't think this is required --Brian ) discoverer.subscribers.removeSubscriber(sub, true); } if (removed == null) { error.setError (Error.INVALID_REQUEST_ERROR); } return error; } /** * Update the description of a component and its lease * * @param component The component to update * @param lease The new Lease * @return Error */ public Error update(ComponentDescription component, Lease lease){ Error error = new Error(Error.NO_ERROR); String index = dataModel.update(component); // Update the log file if (useLogFile){ removeFromLog (component); addToLog (component); } if (index == null) error.setError (Error.ERROR_CODE); if (lease != null){ updateLease(index, lease); } return error; } /** * Returns the ComponentDescription of a registered component * * @param stringOrIndex The component index or id * @return ComponentDescription */ public ComponentDescription getComponentDescription(String componentIndex){ return dataModel.getComponent(componentIndex); } /** * This method allows to send a lease end notification to each * component whose lease ends. The reply received by the component * either renews the lease or confirms it. * The list of components is sent back to the discoverer that take * care of sending a message to them * * @listOfComponents The components index */ public void sendLeaseEndNotificationTo(ArrayList<String> listOfComponents) { discoverer.sendLeaseEndNotificationTo(listOfComponents); } /** * Return true if the component is registered by the discoverer * * @param componentIndex The component index or id * @return boolean */ public boolean exists(String componentIndex) { return dataModel.getComponent(componentIndex) != null; } /** * Adds a component description to the log file * * @param comp The component description */ public synchronized void addToLog(ComponentDescription comp) { writeLog(ENTRY_STRING+ADD_COMP, comp); } /** * Writes something to the log file * * TODO: currently disabled till a more stable log format replaces the flat file storage * * @param header The header to write * @param comp The component to write */ private void writeLog(String header, ComponentDescription comp) { //// synchronized (this) { // BufferedWriter writer = null; // try { // writer = new BufferedWriter(FileUtil.getWriter(filename, true)); // String out = new String(header+mh.encodeData(comp.toDataObject())+"\n"); // writer.write(out,0,out.length()); // writer.flush(); // } catch (IOException ioe) { // System.out.println("DiscovererDataModel writeLog() IO: " + ioe + ", filename=" + filename); // ioe.printStackTrace(System.err); // } catch (EncodeException ee) { // System.out.println("DiscovererDataModel writeLog() Encode: "+ee); // } catch (InvalidEncoderException iee) { // System.out.println("DiscovererDataModel writeLog() InvalidEncoder: "+iee); // } finally { // FileUtil.closeWriter(writer); // } //// } } /** * Adds the information that a component is removed from the database into * the log file * * @param comp The component description to remove */ public synchronized void removeFromLog(ComponentDescription comp){ removeFromLog(ENTRY_STRING+REMOVE_COMP, comp); } /** * Adds the remove information into the log file * TODO: currently disabled till a more stable log format replaces the flat file storage * * @param header The header information * @param comp The component description removed */ public void removeFromLog(String header, ComponentDescription comp){ //// synchronized (this) { // BufferedWriter writer = null; // try { // writer = new BufferedWriter(FileUtil.getWriter(filename, true)); // LOGGER.info("\n\nDiscoDataModel <removeFromLog> comp " + comp); // LOGGER.info("\nto dataObj " + comp.toDataObject ()); // String out = new String(header+mh.encodeData(comp.toDataObject())+"\n"); // writer.write(out,0,out.length()); // writer.flush(); // } catch (IOException ioe) { // System.out.println("DiscovererDataModel removeFromLog() IO: "+ioe); // } catch (EncodeException ee) { // System.out.println("DiscovererDataModel removeFromLog() Encode: "+ee); // } catch (InvalidEncoderException iee) { // System.out.println("DiscovererDataModel removeFromLog() InvalidEncoder: "+iee); // } finally { // FileUtil.closeWriter(writer); // } //// } } /** * Retrieve the information from the log file, check the liveliness of the * components and put them back into the database */ private void setRegisteredComponents(){ // Get the list of component to restart HashMap<String, ComponentDescription> uncheckedComps = restartRegistrations(); if (uncheckedComps != null){ // Check them to be sure they are still alive // int i = 0; // Uses an independent communication to send a PING for (ComponentDescription comp : uncheckedComps.values()) { IndependentCommunication indComm = new IndependentCommunication( new RequestObject(null,null, comp.hostname,comp.port,comp.id), true); indComm.setObjectToStore (comp); indComm.setSenderClassId (Discoverer.DISCOVERER+Discoverer.REGISTERER+BaseObject.PING); discoverer.pingComponent(indComm); // i++; } } } /** * Overrides the method that handle independent Reply. If the independent * communication has been sent by this class, this class handles it. Otherwise * the super class handleIndependeReply is called. * * Catches: * - Discoverer+Registerer+Ping message * * TO DO: restore the lease?? not useful because the next time the discoverer * will check the component, the component will send its lease * */ public void handleIndependentReply(IndependentCommunication independentCommunication){ LOGGER.info("\nThe discovererMediator gets the reply from the the element "); if (independentCommunication != null) { independentCommunication.decodeReply (discoverer); DataObject replyContent = independentCommunication.getDecodedReply (); LOGGER.info("\nDiscovererMediator <handleIndependentReply> Reply=" + replyContent + " - exceptions " + independentCommunication.getExceptions ()); // For RESTART REGISTRATION if (independentCommunication.getSenderClassId ().equals (Discoverer.DISCOVERER+Discoverer.REGISTERER+BaseObject.PING)){ if ( ! independentCommunication.getExceptions ().isEmpty () || replyContent == null){ LOGGER.info("DiscovererMediator <handleIndependentReply> comp does not answer " + ((ComponentDescription)(independentCommunication.getObjectToStore ())).id); } // Adds the comp into the database if the comp is alive else { LOGGER.info("DiscovererMediator <handleIndependentReply> add the comp" ); ComponentDescription comp = (ComponentDescription) independentCommunication.getObjectToStore (); this.add(comp, new Lease()); } } } } /** * Retrieve a list of component to restart from the log file * TODO: currently disabled till a more stable log format replaces the flat file storage * * @return Object contains a HashMap object */ private HashMap<String, ComponentDescription> restartRegistrations() { // String log = FileUtil.read(filename); // int index = log.indexOf(ENTRY_STRING); HashMap<String, ComponentDescription> hash = new HashMap<String, ComponentDescription>(); // // while (index != -1) { // String entry1 = null; // contains the command // int index2 = log.indexOf(ENTRY_STRING,index+1); // if (index2 == -1) { // entry1 = log.substring(index+ENTRY_STRING.length()); // } // else { // entry1 = log.substring(index+ENTRY_STRING.length(),index2); // } // try { // Test the message code : ADD, REMOVE and creates a new Component // // object based on the log file // if (entry1.indexOf(ADD_COMP) != -1) { // index = entry1.indexOf(">"); // String entry = entry1.substring(index+1); // ComponentDescription comp = ComponentDescription.fromDataObject (mh.decodeData(new StringReader(entry))); // if (hash.containsKey (comp.id)){ // hash.remove (comp.id); // } // hash.put (comp.id, comp); // } // else if (entry1.indexOf(REMOVE_COMP) != -1) { // index = entry1.indexOf(">"); // String entry = entry1.substring(index+1); // ComponentDescription comp = ComponentDescription.fromDataObject (mh.decodeData(new StringReader(entry))); // hash.remove (comp.id); // } // } catch (DecodeException de) { // System.out.println("DiscovererDataModel Decode: "+de); // } catch (InvalidDecoderException ide) { // System.out.println("DiscovererDataModel InvalidDecoder: "+ide); // } // index = index2; // } // FileWriter fw = null; // try { //// synchronized (this) { // // Clear the content of the file // fw = FileUtil.getWriter(filename, false); // fw.flush (); // fw.close (); //// } // } catch (IOException ioe) { // System.out.println("DiscovererDataModel writeLog() IO: "+ioe); // } finally { // FileUtil.closeWriter(fw); // } return hash; } }//class end