Package omero :: Module config
[hide private]
[frames] | no frames]

Source Code for Module omero.config

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  """ 
  4  :: 
  5   
  6     Copyright 2010 Glencoe Software, Inc. All rights reserved. 
  7     Use is subject to license terms supplied in LICENSE.txt 
  8   
  9  """ 
 10   
 11  """ 
 12  Module which parses an icegrid XML file for configuration settings. 
 13   
 14  see ticket:800 
 15  see ticket:2213 - Replacing Java Preferences API 
 16  """ 
 17   
 18  import re 
 19  import os 
 20  import path 
 21  import time 
 22  import logging 
 23  import portalocker 
 24   
 25  import xml.dom.minidom 
 26   
 27  try: 
 28      from xml.etree.ElementTree import XML, Element, SubElement, Comment, ElementTree, tostring 
 29  except ImportError: 
 30      from elementtree.ElementTree import XML, Element, SubElement, Comment, ElementTree, tostring 
 31   
 32   
33 -class Environment(object):
34 """ 35 Object to record all the various locations 36 that the active configuration can come from. 37 """ 38
39 - def __init__(self, user_specified = None):
40 self.fallback = "default" 41 self.user_specified = user_specified 42 self.from_os_environ = os.environ.get("OMERO_CONFIG", None)
43
44 - def is_non_default(self):
45 if self.user_specified is not None: 46 return self.user_specified 47 elif self.from_os_environ is not None: 48 return self.from_os_environ 49 return None
50
51 - def set_by_user(self, value):
52 self.user_specified = value
53
54 - def internal_value(self, config):
55 props = config.props_to_dict(config.internal()) 56 return props.get(config.DEFAULT, self.fallback)
57
58 - def for_save(self, config):
59 """ 60 In some cases the environment chosen 61 should not be persisted. 62 """ 63 if self.user_specified: 64 return self.user_specified 65 else: 66 return self.internal_value(config)
67
68 - def for_default(self, config):
69 if self.user_specified: 70 return self.user_specified 71 elif self.from_os_environ: 72 return self.from_os_environ 73 else: 74 return self.internal_value(config)
75 76
77 -class ConfigXml(object):
78 """ 79 dict-like wrapper around the config.xml file usually stored 80 in etc/grid. For a copy of the dict, use "as_map" 81 """ 82 KEY = "omero.config.version" 83 VERSION = "4.2.1" 84 INTERNAL = "__ACTIVE__" 85 DEFAULT = "omero.config.profile" 86 IGNORE = (KEY, DEFAULT) 87
88 - def __init__(self, filename, env_config = None, exclusive = True):
89 self.logger = logging.getLogger(self.__class__.__name__) #: Logs to the class name 90 self.XML = None #: Parsed XML Element 91 self.filename = filename #: Path to the file to be read and written 92 self.env_config = Environment(env_config) #: Environment override 93 self.exclusive = exclusive #: Whether or not an exclusive lock should be acquired 94 self.save_on_close = True 95 96 try: 97 # Try to open the file for modification 98 # If this fails, then the file is readonly 99 self.source = open(filename, "a+") #: Open file handle 100 self.lock = self._open_lock() #: Open file handle for lock 101 except IOError: 102 self.logger.debug("open('%s', 'a+') failed" % filename) 103 self.lock = None 104 self.exclusive = False 105 self.save_on_close = False 106 107 # Before we open the file read-only we need to check 108 # that no other configuration has been requested because 109 # it will not be possible to modify the __ACTIVE__ setting 110 # once it's read-only 111 val = self.env_config.is_non_default() 112 if val is not None: 113 raise Exception("Non-default OMERO_CONFIG on read-only: %s" % val) 114 115 self.source = open(filename, "r") #: Open file handle read-only 116 117 if self.exclusive: # must be "a+" 118 try: 119 portalocker.lock(self.lock, portalocker.LOCK_NB|portalocker.LOCK_EX) 120 except portalocker.LockException, le: 121 self.lock = None # Prevent deleting of the file 122 self.close() 123 raise 124 125 self.source.seek(0) 126 text = self.source.read() 127 128 if text: 129 self.XML = XML(text) 130 try: 131 self.version_check() 132 except: 133 self.close() 134 raise 135 136 # Nothing defined, so create a new tree 137 if self.XML is None: 138 default = self.default() 139 self.XML = Element("icegrid") 140 properties = SubElement(self.XML, "properties", id=self.INTERNAL) 141 _ = SubElement(properties, "property", name=self.DEFAULT, value=default) 142 _ = SubElement(properties, "property", name=self.KEY, value=self.VERSION) 143 properties = SubElement(self.XML, "properties", id=default) 144 _ = SubElement(properties, "property", name=self.KEY, value=self.VERSION)
145
146 - def _open_lock(self):
147 return open("%s.lock" % self.filename, "a+")
148
149 - def _close_lock(self):
150 if self.lock is not None: 151 self.lock.close() 152 self.lock = None 153 try: 154 os.remove("%s.lock" % self.filename) 155 except: 156 # On windows a WindowsError 32 can happen (file opened by another process), ignoring 157 self.logger.error("Failed to removed lock file, ignoring", exc_info=True) 158 pass
159
160 - def version(self, id = None):
161 if id is None: 162 id = self.default() 163 properties = self.properties(id) 164 if properties is not None: 165 for x in properties.getchildren(): 166 if x.get("name") == self.KEY: 167 return x.get("value")
168
169 - def version_check(self):
170 for k, v in self.properties(None, True): 171 version = self.version(k) 172 if version != self.VERSION: 173 self.version_fix(v, version)
174
175 - def version_fix(self, props, version):
176 """ 177 Currently we are assuming that all blocks without a 4.2.0 version 178 are bogus. The configuration script when it generates an initial 179 config.xml will use prefs.class to parse the existing values and 180 immediately do the upgrade. 181 """ 182 if version == "4.2.0": 183 # http://trac.openmicroscopy.org.uk/ome/ticket/2613 184 # Remove any reference to the ${omero.dollar} workaround 185 # then map anything of the form: ${...} to @{...} 186 if props: 187 for x in props.getchildren(): 188 if x.get("name", "").startswith("omero.ldap"): 189 orig = x.get("value", "") 190 val = orig.replace("${omero.dollar}", "") 191 val = val.replace("${", "@{") 192 x.set("value", val) 193 self.logger.info("Upgraded 4.2.0 property: %s => %s", orig, val) 194 else: 195 raise Exception("Version mismatch: %s has %s" % (props.get("id"), version))
196
197 - def internal(self):
198 return self.properties(self.INTERNAL)
199
200 - def properties(self, id = None, filter_internal = False):
201 202 if self.XML is None: 203 return None 204 205 props = self.XML.findall("./properties") 206 if id is None: 207 rv = list() 208 for x in props: 209 id = x.attrib["id"] 210 if filter_internal: 211 if id == self.INTERNAL: 212 continue 213 rv.append((id, x)) 214 return rv 215 for p in props: 216 if "id" in p.attrib and p.attrib["id"] == id: 217 return p
218
219 - def remove(self, id = None):
220 if id is None: 221 id = self.default() 222 properties = self.properties(id) 223 if properties is None: 224 raise KeyError("No such configuration: %s" % id) 225 self.XML.remove(properties)
226
227 - def default(self, value = None):
228 if value: 229 self.env_config.set_by_user(value) 230 231 return self.env_config.for_default(self)
232
233 - def dump(self):
234 prop_list = self.properties() 235 for id, p in prop_list: 236 props = self.props_to_dict(p) 237 print "# ===> %s <===" % id 238 print self.dict_to_text(props)
239
240 - def save(self):
241 """ 242 Creates a fresh <icegrid> block (removing any unwanted 243 intra-element whitespace) and overwrites the file on disk. 244 """ 245 icegrid = Element("icegrid") 246 comment = Comment("\n".join(["\n", 247 "\tThis file was generated at %s by the OmeroConfig system.", 248 "\tDo not edit directly but see bin/omero config for details.", 249 "\tThis file may be included into your IceGrid application.", 250 "\n"]) % time.ctime()) 251 icegrid.append(comment) 252 # First step is to add a new self.INTERNAL block to it 253 # which has self.DEFAULT set to the current default, 254 # and then copies all the values from that profile. 255 default = self.env_config.for_save(self) 256 internal = SubElement(icegrid, "properties", id=self.INTERNAL) 257 SubElement(internal, "property", name=self.DEFAULT, value=default) 258 SubElement(internal, "property", name=self.KEY, value=self.VERSION) 259 to_copy = self.properties(default) 260 if to_copy is not None: 261 for x in to_copy.getchildren(): 262 if x.get("name") != self.DEFAULT and x.get("name") != self.KEY: 263 SubElement(internal, "property", x.attrib) 264 else: 265 # Doesn't exist, create it 266 properties = SubElement(icegrid, "properties", id=default) 267 SubElement(properties, "property", name=self.KEY, value=self.VERSION) 268 # Now we simply reproduce all the other blocks 269 prop_list = self.properties(None, True) 270 for k, p in prop_list: 271 self.clear_text(p) 272 icegrid.append(p) 273 self.source.seek(0) 274 self.source.truncate() 275 self.source.write(self.element_to_xml(icegrid)) 276 self.source.flush()
277
278 - def close(self):
279 try: 280 # If we didn't get an XML instance, then something has gone wrong 281 # and we should exit. Similarly, if save_on_close is False, then we 282 # couldn't open the file "a+" 283 if self.XML is not None and self.save_on_close: 284 self.save() 285 self.XML = None 286 finally: 287 try: 288 if self.source is not None: 289 self.source.close() 290 self.source = None 291 finally: 292 self._close_lock()
293
294 - def props_to_dict(self, c):
295 296 if c is None: 297 return {} 298 299 rv = dict() 300 props = c.findall("./property") 301 for p in props: 302 if "name" in p.attrib: 303 rv[p.attrib["name"]] = p.attrib.get("value", "") 304 return rv
305
306 - def dict_to_text(self, parsed = None):
307 308 if parsed is None: 309 return 310 311 rv = "" 312 for k, v in parsed.items(): 313 rv += "%s=%s" % (k, v) 314 return rv
315
316 - def element_to_xml(self, elem):
317 string = tostring(elem, 'utf-8') 318 return xml.dom.minidom.parseString(string).toprettyxml(" ", "\n", None)
319
320 - def clear_text(self, p):
321 """ 322 To prevent the accumulation of text outside of elements (including whitespace) 323 we walk the given element and remove tail from it and it's children. 324 """ 325 p.tail = "" 326 p.text = "" 327 for p2 in p.getchildren(): 328 self.clear_text(p2)
329 330 # 331 # Map interface on the default properties element 332 #
333 - def as_map(self):
334 return self.props_to_dict(self.properties(self.default()))
335
336 - def keys(self):
337 return self.as_map().keys()
338
339 - def __getitem__(self, key):
340 return self.props_to_dict(self.properties(self.default()))[key]
341
342 - def __setitem__(self, key, value):
343 default = self.default() 344 props = self.properties(default) 345 346 if props == None: 347 props = SubElement(self.XML, "properties", {"id":default}) 348 SubElement(props, "property", name=self.KEY, value=self.VERSION) 349 350 for x in props.findall("./property"): 351 if x.attrib["name"] == key: 352 x.attrib["value"] = value 353 return 354 SubElement(props, "property", {"name":key, "value":value})
355
356 - def __delitem__(self, key):
357 default = self.default() 358 props = self.properties(default) 359 to_remove = [] 360 for p in props.getchildren(): 361 if p.get("name") == key: 362 to_remove.append(p) 363 for x in to_remove: 364 props.remove(x)
365