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