001    package org.hackystat.telemetry.analyzer.function;
002    
003    import java.io.InputStream;
004    import java.util.TreeMap;
005    import java.util.logging.Logger;
006    
007    import javax.xml.bind.JAXBContext;
008    import javax.xml.bind.Unmarshaller;
009    
010    import org.hackystat.telemetry.analyzer.function.jaxb.FunctionDefinition;
011    import org.hackystat.telemetry.analyzer.function.jaxb.FunctionDefinitions;
012    import org.hackystat.telemetry.analyzer.model.TelemetryStreamCollection;
013    import org.hackystat.utilities.logger.HackystatLogger;
014    import org.hackystat.utilities.stacktrace.StackTrace;
015    
016    /**
017     * Implements a global singleton for managing telemetry function instances. It serves two purposes:
018     * <ul>
019     *   <li>Provides a central repository for all available telemetry functions.
020     *   <li>Provides a interface for invoking those functions.
021     * </ul>
022     * 
023     * All function names are case-insensitive.
024     * 
025     * @author (Cedric) Qin ZHANG
026     */
027    public class TelemetryFunctionManager {
028    
029      /**
030       * Built-in functions for telemetry language and evaluator.
031       * Key is function name (lower case), value is TelemetryFunctionInfo instance. 
032       */
033      private TreeMap<String, TelemetryFunctionInfo> functionMap = 
034        new TreeMap<String, TelemetryFunctionInfo>();
035      
036      /** The global instance of this manager. */
037      private static TelemetryFunctionManager theInstance = new TelemetryFunctionManager();
038      
039      /**
040       * Gets the global instance.
041       * 
042       * @return The global instance.
043       */
044      public static TelemetryFunctionManager getInstance() {
045        return theInstance;
046      }
047    
048      /**
049       * Defines "built-in" telemetry functions. 
050       */
051      private TelemetryFunctionManager() {
052        Logger logger = HackystatLogger.getLogger("org.hackystat.telemetry");
053        FunctionDefinitions definitions = null;
054        
055        try {
056          logger.info("Loading built-in telemetry function definitions.");
057          //InputStream defStream = getClass().getResourceAsStream("impl/function.definitions.xml");
058          InputStream defStream = 
059            TelemetryFunctionManager.class.getResourceAsStream("impl/function.definitions.xml");
060          JAXBContext jaxbContext = JAXBContext
061          .newInstance(org.hackystat.telemetry.analyzer.function.jaxb.ObjectFactory.class);
062          Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
063          definitions = (FunctionDefinitions) unmarshaller.unmarshal(defStream);
064        } 
065        catch (Exception e) {
066          logger.severe("Could not find built-in telemetry function definitions! " 
067              + StackTrace.toString(e));
068          return;
069        }
070    
071        for (FunctionDefinition definition : definitions.getFunctionDefinition()) {
072          String name = definition.getName();
073          try {
074            logger.info("Defining built-in telemetry function " + name);
075            Class<?> clazz = Class.forName(definition.getClassName());
076            TelemetryFunction telemetryFunc = (TelemetryFunction) clazz.newInstance();
077            TelemetryFunctionInfo funcInfo =  new TelemetryFunctionInfo(telemetryFunc, definition);
078            this.functionMap.put(name.toLowerCase(), funcInfo);
079          }
080          catch (Exception classEx) {
081            logger.severe("Unable to define " 
082                + definition.getClassName() + ". Entry ignored. " + classEx.getMessage());
083            continue;
084          }
085        }
086      }
087      
088      /**
089       * Determines whether a particular telemetry function is available.
090       * 
091       * @param functionName Telemetry function name.
092       * @return True if the specified telemetry function is available.
093       */
094      public boolean isFunction(String functionName) {
095        return this.functionMap.containsKey(functionName.toLowerCase());
096      }
097    
098      /**
099       * Gets telemetry function information by name.
100       * 
101       * @param functionName The name of the function.
102       * @return The telemetry function information, or null if the function is not defined.
103       */
104      public TelemetryFunctionInfo getFunctionInfo(String functionName) {
105        return this.functionMap.get(functionName.toLowerCase());
106      }
107      
108      /**
109       * Invokes telemetry function to perform computation.
110       * 
111       * @param functionName The name of the telemetry function.
112       * @param parameters An array of objects of type either <code>String</code>,
113       *        <code>Number</code>, and/or <code>TelemetryStreamCollection</code>. 
114       * 
115       * @return Either an instance of <code>Number</code> or <code>TelemetryStreamCollection</code>. 
116       * 
117       * @throws TelemetryFunctionException If anything is wrong.
118       */
119      public Object compute(String functionName, Object[] parameters) 
120          throws TelemetryFunctionException {
121    
122        TelemetryFunctionInfo functionInfo = this.getFunctionInfo(functionName.toLowerCase());
123        if (functionInfo == null) {
124          throw new TelemetryFunctionException(
125              "Telemetry function " + functionName + " does not exist.");
126        }
127        
128        //check input types
129        for (int i = 0; i < parameters.length; i++) {
130          Object object = parameters[i];
131          if (! (object instanceof String || object instanceof Number
132               || object instanceof TelemetryStreamCollection)) {
133            throw new TelemetryFunctionException("Telemetry function parameter should be of type "
134                + "either 'String', 'Number', and/or 'TelemetryStreamCollection'.");
135          }
136        }
137        //delegate computation
138        Object result = functionInfo.getFunction().compute(parameters);
139        //check output types
140        if (! (result instanceof Number || result instanceof TelemetryStreamCollection)) {
141          throw new TelemetryFunctionException("Telemetry function " + functionName 
142              + " implementation error. It returns a result of unexpected type.");
143        }
144        return result;
145      }
146    }