001    package org.hackystat.sensorbase.resource.sensordatatypes;
002    
003    import static org.hackystat.sensorbase.server.ServerProperties.XML_DIR_KEY;
004    
005    import java.io.File;
006    import java.io.StringReader;
007    import java.io.StringWriter;
008    import java.util.HashMap;
009    import java.util.Map;
010    
011    import javax.xml.bind.JAXBContext;
012    import javax.xml.bind.Marshaller;
013    import javax.xml.bind.Unmarshaller;
014    import javax.xml.parsers.DocumentBuilder;
015    import javax.xml.parsers.DocumentBuilderFactory;
016    import javax.xml.transform.Transformer;
017    import javax.xml.transform.TransformerFactory;
018    import javax.xml.transform.dom.DOMSource;
019    import javax.xml.transform.stream.StreamResult;
020    
021    import org.hackystat.sensorbase.resource.sensordatatypes.jaxb.SensorDataType;
022    import org.hackystat.sensorbase.resource.sensordatatypes.jaxb.SensorDataTypeIndex;
023    import org.hackystat.sensorbase.resource.sensordatatypes.jaxb.SensorDataTypeRef;
024    import org.hackystat.sensorbase.resource.sensordatatypes.jaxb.SensorDataTypes;
025    import org.hackystat.sensorbase.server.Server;
026    import org.hackystat.sensorbase.db.DbManager;
027    import org.hackystat.utilities.stacktrace.StackTrace;
028    import org.hackystat.utilities.tstamp.Tstamp;
029    import org.w3c.dom.Document;
030    
031    /**
032     * Provides a manager for the SensorDataType resource.
033     * As with all of the Resource managers the methods in this class can be grouped into 
034     * three general categories, of which this Manager uses the following:
035     * <ul>
036     * <li> Database access methods (get*, has*, put*, delete*):  these methods communicate with the 
037     * underlying storage system (or cache). 
038     * <li> XML/Java translation methods (make*): these methods translate between the XML String
039     * representation of a resource and its Java class instance representation. 
040     * </ul>
041     * <p>  
042     * See https://jaxb.dev.java.net/guide/Performance_and_thread_safety.html for info 
043     * on JAXB performance and thread safety.
044     * <p>
045     * All public methods of this class are synchronized so that we can maintain the cache along with
046     * the underlying persistent store in a thread-safe fashion.
047     * 
048     * @author Philip Johnson
049     *
050     */
051    public class SdtManager {
052      
053      /** Holds the class-wide JAXBContext, which is thread-safe. */
054      private JAXBContext jaxbContext;
055      
056      /** The Server associated with this SdtManager. */
057      Server server; 
058      
059      /** The DbManager associated with this server. */
060      DbManager dbManager;
061      
062      /** The SensorDataTypeIndex open tag. */
063      public static final String sensorDataTypeIndexOpenTag = "<SensorDataTypeIndex>";
064      
065      /** The SensorDataTypeIndex close tag. */
066      public static final String sensorDataTypeIndexCloseTag = "</SensorDataTypeIndex>";
067      
068      /** An in-memory cache mapping SDT names to their corresponding instance. */
069      private Map<String, SensorDataType> name2sdt = new HashMap<String, SensorDataType>();
070      
071      /** An in-memory cache mapping a SensorDataType instance to its SensorDataType XML string. */
072      private Map<SensorDataType, String> sdt2xml = new HashMap<SensorDataType, String>();
073      
074      /** An in-memory cache mapping a SensorDataType instance to its SensorDataTypeRef XML string. */
075      private Map<SensorDataType, String> sdt2ref = new HashMap<SensorDataType, String>();
076      
077      
078      /** 
079       * The constructor for SdtManagers. Loads in default data and sets up the in-memory caches. 
080       * @param server The Server instance associated with this SdtManager. 
081       */
082      public SdtManager(Server server) {
083        this.server = server;
084        this.dbManager = (DbManager)this.server.getContext().getAttributes().get("DbManager");
085        try {
086          this.jaxbContext = 
087            JAXBContext.newInstance(
088                org.hackystat.sensorbase.resource.sensordatatypes.jaxb.ObjectFactory.class);
089          loadDefaultSensorDataTypes(); //NOPMD it's throwing a false warning. 
090          initializeCache();            //NOPMD 
091        }
092        catch (Exception e) {
093          String msg = "Exception during SdtManager initialization processing";
094          server.getLogger().warning(msg + "\n" + StackTrace.toString(e));
095          throw new RuntimeException(msg, e);
096        }
097      }
098    
099      /**
100       * Loads the default SensorDataTypes from the defaults file. 
101       * @throws Exception If problems occur. 
102       */
103      private void loadDefaultSensorDataTypes() throws Exception {
104        // Get the default SDT definitions from the XML defaults file. 
105        File defaultsFile = findDefaultsFile();
106        // Initialize the SDTs if we've found a default file. 
107        if (defaultsFile.exists()) {
108          server.getLogger().info("Loading SDT defaults from " + defaultsFile.getPath()); 
109          Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
110          SensorDataTypes sensorDataTypes = (SensorDataTypes) unmarshaller.unmarshal(defaultsFile);
111          for (SensorDataType sdt : sensorDataTypes.getSensorDataType()) {
112            sdt.setLastMod(Tstamp.makeTimestamp());
113            this.dbManager.storeSensorDataType(sdt, this.makeSensorDataType(sdt), 
114                this.makeSensorDataTypeRefString(sdt));
115          }
116        }
117      }
118      
119      /**
120       * Read the SDTs from the underlying database and initialize the in-memory cache.
121       */
122      private void initializeCache() {
123        try {
124          SensorDataTypeIndex index = makeSensorDataTypeIndex(this.dbManager.getSensorDataTypeIndex());
125          for (SensorDataTypeRef ref : index.getSensorDataTypeRef()) {
126            String sdtName = ref.getName();
127            String sdtString = this.dbManager.getSensorDataType(sdtName);
128            SensorDataType sdt = makeSensorDataType(sdtString);
129            String sdtRef = this.makeSensorDataTypeRefString(sdt);
130            this.name2sdt.put(sdtName, sdt);
131            this.sdt2ref.put(sdt, sdtRef);
132            this.sdt2xml.put(sdt, sdtString);
133          }
134        }
135        catch (Exception e) {
136          server.getLogger().warning("Failed to initialize SDTs " + StackTrace.toString(e));
137        }
138      }
139      
140      /**
141       * Checks the ServerProperties for the XML_DIR property.
142       * If this property is null, returns the File for ./xml/defaults/sensordatatypes.defaults.xml.
143       * @return The File instance (which might not point to an existing file.)
144       */
145      private File findDefaultsFile() {
146        String defaultsPath = "/defaults/sensordatatypes.defaults.xml";
147        String xmlDir = server.getServerProperties().get(XML_DIR_KEY);
148        return (xmlDir == null) ?
149            new File (System.getProperty("user.dir") + "/xml" + defaultsPath) :
150              new File (xmlDir + defaultsPath);
151      }
152    
153    
154      /**
155       * Returns the XML string containing the SensorDataTypeIndex with all defined SDTs.
156       * Uses the in-memory cache of SensorDataTypeRef strings.  
157       * @return The XML string providing an index to all current SDTs.
158       */
159      public synchronized String getSensorDataTypeIndex() {
160        StringBuilder builder = new StringBuilder(512);
161        builder.append(sensorDataTypeIndexOpenTag);
162        for (String ref : this.sdt2ref.values()) {
163          builder.append(ref);
164        }
165        builder.append(sensorDataTypeIndexCloseTag);
166        return builder.toString();
167      }
168      
169      /**
170       * Returns the XML String representation of a SensorDataType, given its name.  
171       * @param sdtName The name of the SDT. 
172       * @return Its string representation, or null if not found. 
173       */
174      public synchronized String getSensorDataTypeString(String sdtName) {
175        SensorDataType sdt = this.name2sdt.get(sdtName);
176        return (sdt == null) ? null : this.sdt2xml.get(sdt);
177      }
178      
179      /**
180       * Updates the Manager with this SDT. Any old definition with this name is overwritten.
181       * Updates in-memory caches and persistent store. 
182       * @param sdt The SensorDataType.
183       */
184      public synchronized void putSdt(SensorDataType sdt) {
185        try {
186          sdt.setLastMod(Tstamp.makeTimestamp());
187          String sdtString = this.makeSensorDataType(sdt);
188          String sdtRefString = this.makeSensorDataTypeRefString(sdt);
189          this.dbManager.storeSensorDataType(sdt, sdtString, sdtRefString);
190          this.name2sdt.put(sdt.getName(), sdt);
191          this.sdt2ref.put(sdt, sdtRefString);
192          this.sdt2xml.put(sdt, sdtString);
193        }
194        catch (Exception e) {
195          server.getLogger().warning("Failed to put SDT" + StackTrace.toString(e));
196        }
197      }
198      
199      /**
200       * Returns true if the passed SDT name is defined. 
201       * @param sdtName A SensorDataType name
202       * @return True if a SensorDataType with that name is already known to this SdtManager.
203       */
204      public synchronized boolean hasSdt(String sdtName) {
205        return this.name2sdt.containsKey(sdtName);
206      }
207      
208      /**
209       * Ensures that the passed sdtName is no longer present in this Manager. 
210       * @param sdtName The name of the SDT to remove if currently present.
211       */
212      public synchronized void deleteSdt(String sdtName) {
213        SensorDataType sdt = this.name2sdt.get(sdtName);
214        if (sdt != null) {
215          this.name2sdt.remove(sdtName);
216          this.sdt2ref.remove(sdt);
217          this.sdt2xml.remove(sdt);
218        }
219        this.dbManager.deleteSensorDataType(sdtName);
220      }
221      
222      /**
223       * Takes a String encoding of a SensorDataType in XML format and converts it to an instance. 
224       * 
225       * @param xmlString The XML string representing a SensorDataType
226       * @return The corresponding SensorDataType instance. 
227       * @throws Exception If problems occur during unmarshalling.
228       */
229      public final synchronized SensorDataType makeSensorDataType(String xmlString) throws Exception {
230        Unmarshaller unmarshaller = this.jaxbContext.createUnmarshaller();
231        return (SensorDataType)unmarshaller.unmarshal(new StringReader(xmlString));
232      }
233      
234      /**
235       * Takes a String encoding of a SensorDataTypeIndex in XML format and converts it to an instance. 
236       * Note that this does not affect the state of any SdtManager instance. 
237       * 
238       * @param xmlString The XML string representing a SensorDataTypeIndex.
239       * @return The corresponding SensorDataTypeIndex instance. 
240       * @throws Exception If problems occur during unmarshalling.
241       */
242      public final synchronized SensorDataTypeIndex makeSensorDataTypeIndex(String xmlString) 
243      throws Exception {
244        Unmarshaller unmarshaller = this.jaxbContext.createUnmarshaller();
245        return (SensorDataTypeIndex)unmarshaller.unmarshal(new StringReader(xmlString));
246      }
247      
248      /**
249       * Returns the passed SensorDataType instance as a String encoding of its XML representation.
250       * Final because it's called in constructor.
251       * @param sdt The SensorDataType instance. 
252       * @return The XML String representation.
253       * @throws Exception If problems occur during translation. 
254       */
255      public final synchronized String makeSensorDataType (SensorDataType sdt) throws Exception {
256        Marshaller marshaller = jaxbContext.createMarshaller(); 
257        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
258        dbf.setNamespaceAware(true);
259        DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
260        Document doc = documentBuilder.newDocument();
261        marshaller.marshal(sdt, doc);
262        DOMSource domSource = new DOMSource(doc);
263        StringWriter writer = new StringWriter();
264        StreamResult result = new StreamResult(writer);
265        TransformerFactory tf = TransformerFactory.newInstance();
266        Transformer transformer = tf.newTransformer();
267        transformer.transform(domSource, result);
268        String xmlString = writer.toString();
269        // Now remove the processing instruction.  This approach seems like a total hack.
270        xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
271        return xmlString;
272      }
273    
274      /**
275       * Returns the passed SensorDataType instance as a String encoding of its XML representation 
276       * as a SensorDataTypeRef object.
277       * Final because it's called in constructor.
278       * @param sdt The SensorDataType instance. 
279       * @return The XML String representation of it as a SensorDataTypeRef
280       * @throws Exception If problems occur during translation. 
281       */
282      public final synchronized String makeSensorDataTypeRefString (SensorDataType sdt) 
283      throws Exception {
284        SensorDataTypeRef ref = makeSensorDataTypeRef(sdt);
285        Marshaller marshaller = jaxbContext.createMarshaller(); 
286        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
287        dbf.setNamespaceAware(true);
288        DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
289        Document doc = documentBuilder.newDocument();
290        marshaller.marshal(ref, doc);
291        DOMSource domSource = new DOMSource(doc);
292        StringWriter writer = new StringWriter();
293        StreamResult result = new StreamResult(writer);
294        TransformerFactory tf = TransformerFactory.newInstance();
295        Transformer transformer = tf.newTransformer();
296        transformer.transform(domSource, result);
297        String xmlString = writer.toString();
298        // Now remove the processing instruction.  This approach seems like a total hack.
299        xmlString = xmlString.substring(xmlString.indexOf('>') + 1);
300        return xmlString;
301      }
302      
303      /**
304       * Returns a SensorDataTypeRef instance constructed from a SensorDataType instance.
305       * @param sdt The SensorDataType instance. 
306       * @return A SensorDataTypeRef instance. 
307       */
308      public synchronized SensorDataTypeRef makeSensorDataTypeRef(SensorDataType sdt) {
309        SensorDataTypeRef ref = new SensorDataTypeRef();
310        ref.setName(sdt.getName());
311        ref.setHref(this.server.getHostName() + "sensordatatypes/" + sdt.getName()); 
312        return ref;
313      }
314      
315    }
316