Package occi :: Package protocol :: Module occi_rendering
[hide private]
[frames] | no frames]

Source Code for Module occi.protocol.occi_rendering

  1  # coding=utf-8 
  2  # 
  3  # Copyright (C) 2010-2012 Platform Computing 
  4  # 
  5  # This library is free software; you can redistribute it and/or 
  6  # modify it under the terms of the GNU Lesser General Public 
  7  # License as published by the Free Software Foundation; either 
  8  # version 2.1 of the License, or (at your option) any later version. 
  9  # 
 10  # This library is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 13  # Lesser General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU Lesser General Public 
 16  # License along with this library; if not, write to the Free Software 
 17  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA 
 18  # 
 19  ''' 
 20  Actual mime-type renderings. 
 21   
 22  Created on Jun 28, 2011 
 23   
 24  @author: tmetsch 
 25  ''' 
 26   
 27  from occi.core_model import Resource, Link 
 28  from occi.handlers import CATEGORY, ATTRIBUTE, LOCATION, LINK, CONTENT_TYPE 
 29  from occi.protocol.rendering import Rendering 
 30  import occi.protocol.occi_parser as parser 
 31  import shlex 
 32   
 33   
34 -class HTTPData(object):
35 ''' 36 Simple class which functions as an adapter between the OCCI model and the 37 HTTP rendering. Holds all information in the way an entity is rendered. 38 ''' 39 40 # disabling 'Too few public methods' pylint check (just a data model) 41 # pylint: disable=R0903 42
43 - def __init__(self):
44 self.categories = [] 45 self.links = [] 46 self.attributes = [] 47 self.locations = []
48 49 #============================================================================== 50 # text/occi rendering 51 #============================================================================== 52 53
54 -def _to_entity(data, def_kind, registry, extras):
55 ''' 56 Extract an entity from the HTTP data object. 57 58 data -- The http data object 59 def_kind -- A given kind definition. 60 registry -- The registry. 61 extras -- Passed on extra object. 62 ''' 63 64 # disable 'Too many local vars' pylint check (It's a bit ugly but will do) 65 # disable 'Too many branches' pylint check (Needs to be improved) 66 # pylint: disable=R0914,R0912 67 68 kind = None 69 mixins = [] 70 71 # first kind & mixins 72 kind_found = False 73 for category_string in data.categories: 74 category = parser.get_category(category_string.strip(), registry, 75 extras) 76 if repr(category) == 'kind' and not kind_found: 77 kind = category 78 kind_found = True 79 else: 80 mixins.append(category) 81 82 # the attributes 83 attributes = {} 84 for attr_string in data.attributes: 85 key, value = parser.get_attributes(attr_string) 86 attributes[key] = value 87 88 # now create the entity 89 if kind_found is False and def_kind is None: 90 raise AttributeError('Could not find a valid kind.') 91 elif def_kind is not None: 92 kind = def_kind 93 94 if Resource.kind in kind.related: 95 # links 96 entity = Resource(None, kind, mixins, []) 97 for link_string in data.links: 98 entity.links.append(parser.get_link(link_string.strip(), 99 entity, 100 registry, extras)) 101 elif Link.kind in kind.related: 102 try: 103 source_attr = attributes['occi.core.source'] 104 target_attr = attributes['occi.core.target'] 105 106 if not source_attr.find(registry.get_hostname()): 107 source_attr = source_attr.replace(registry.get_hostname(), '') 108 if not target_attr.find(registry.get_hostname()): 109 target_attr = target_attr.replace(registry.get_hostname(), '') 110 111 source = registry.get_resource(source_attr, extras) 112 # FUTURE_IMPROVEMENT: string links 113 target = registry.get_resource(target_attr, extras) 114 except KeyError: 115 raise AttributeError('Both occi.core.[source, target]' 116 + ' attributes need to be resources.') 117 entity = Link(None, kind, mixins, source, target) 118 else: 119 raise AttributeError('This kind seems not to be related to either' 120 + ' resource or link.') 121 122 entity.attributes = attributes 123 return entity
124 125
126 -def _from_entity(entity, registry):
127 ''' 128 Create a HTTP data object from an entity. 129 130 entity -- The entity to render. 131 registry -- Registry. 132 ''' 133 data = HTTPData() 134 135 # categories 136 cat_str_list = [parser.get_category_str(entity.kind, registry)] 137 138 for category in entity.mixins: 139 cat_str_list.append(parser.get_category_str(category, registry)) 140 141 data.categories = cat_str_list 142 143 attributes = [] 144 if 'occi.core.id' not in entity.attributes: 145 entity.attributes['occi.core.id'] = entity.identifier 146 147 link_str_list = [] 148 # actions 149 for action in entity.actions: 150 act = '<' + entity.identifier + '?action=' + action.term + '>' 151 act = act + '; rel="' + str(action) + '"' 152 link_str_list.append(act) 153 154 # links 155 if isinstance(entity, Resource): 156 # links 157 for link in entity.links: 158 link_str_list.append(parser.get_link_str(link)) 159 160 elif isinstance(entity, Link): 161 entity.attributes['occi.core.source'] = entity.source.identifier 162 # FUTURE_IMPROVEMENT: string links 163 entity.attributes['occi.core.target'] = entity.target.identifier 164 165 data.links = link_str_list 166 167 # attributes 168 for attr in entity.attributes: 169 attributes.append(attr + '="' 170 + entity.attributes[attr] + '"') 171 172 data.attributes = attributes 173 174 return data
175 176
177 -def _to_entities(data, registry, extras):
178 ''' 179 Extract a set of (in the service existing) entities from a request. 180 181 data -- the HTTP data. 182 registry -- The registry used for this call. 183 extras -- Passed on extra object. 184 ''' 185 result = [] 186 for item in data.locations: 187 try: 188 if not item.find(registry.get_hostname()): 189 item = item.replace(registry.get_hostname(), '') 190 191 result.append(registry.get_resource(item.strip(), extras)) 192 except KeyError: 193 raise AttributeError('Could not find the resource with id: ' 194 + str(item)) 195 196 return result
197 198
199 -def _from_entities(entity_list, registry):
200 ''' 201 Return a list of entities using the X-OCCI-Location attribute. 202 203 entity_list -- list of entities. 204 registry -- The registry used for this call.registry 205 ''' 206 data = HTTPData() 207 for entity in entity_list: 208 data.locations.append(registry.get_hostname() + entity.identifier) 209 210 return data
211 212
213 -def _from_categories(categories, registry):
214 ''' 215 Create a HTTP data object from a set of categories. 216 217 categories -- list of categories. 218 registry -- needed to retrieve hostname info. 219 ''' 220 data = HTTPData() 221 222 for cat in categories: 223 data.categories.append(parser.get_category_str(cat, registry)) 224 225 return data
226 227
228 -def _to_action(data, registry, extras):
229 ''' 230 Create an action from an HTTP data object. 231 232 data -- the HTTP data. 233 registry -- The registry used for this call. 234 extras -- Passed on extra object. 235 ''' 236 action = parser.get_category(data.categories[0].strip(), registry, extras) 237 238 attributes = {} 239 for attr in data.attributes: 240 key, val = parser.get_attributes(attr) 241 attributes[key] = val 242 243 return action, attributes
244 245
246 -def _to_mixins(data, registry, extras):
247 ''' 248 Create a Mixin from an HTTP data object. 249 250 data -- the HTTP data. 251 registry -- The registry used for this call. 252 extras -- Passed on extra object. 253 ''' 254 result = [] 255 for cat_str in data.categories: 256 result.append(parser.get_category(cat_str, registry, extras, 257 is_mixin=True)) 258 return result
259 260
261 -def _get_filter(data, registry, extras):
262 ''' 263 Parse categories and attributes from the request. 264 265 data -- the HTTP data. 266 registry -- The registry used for this call. 267 extras -- Passed on extra object. 268 ''' 269 categories = [] 270 attributes = {} 271 272 for cat in data.categories: 273 categories.append(parser.get_category(cat, registry, extras)) 274 275 for attr in data.attributes: 276 key, value = parser.get_attributes(attr) 277 attributes[key] = value 278 279 return categories, attributes
280 281
282 -def _extract_data_from_headers(headers):
283 ''' 284 Simple method to split out the information from the HTTP headers. 285 286 headers -- The HTTP headers. 287 ''' 288 # split out the information 289 data = HTTPData() 290 if CATEGORY in headers.keys(): 291 data.categories = headers[CATEGORY].split(',') 292 if ATTRIBUTE in headers.keys(): 293 split = shlex.shlex(headers[ATTRIBUTE], posix=True) 294 split.whitespace = ',' 295 split.whitespace_split = True 296 data.attributes = list(split) 297 if LOCATION in headers.keys(): 298 data.locations = headers[LOCATION].split(',') 299 if LINK in headers.keys(): 300 data.links = headers[LINK].split(',') 301 return data
302 303
304 -def _set_data_to_headers(data, mime_type):
305 ''' 306 Simple method to set all information in the HTTP header. 307 308 data -- The data to set. 309 mime_type -- The content type to set. 310 ''' 311 headers = {} 312 body = 'OK' 313 314 # We're using different header names here - WSGI will take care of correct 315 # 'translation' 316 317 if len(data.categories) > 0: 318 headers[CATEGORY] = ', '.join(data.categories) 319 if len(data.links) > 0: 320 headers[LINK] = ', '.join(data.links) 321 if len(data.locations) > 0: 322 headers[LOCATION] = ', '.join(data.locations) 323 if len(data.attributes) > 0: 324 headers[ATTRIBUTE] = ', '.join(data.attributes) 325 headers[CONTENT_TYPE] = mime_type 326 327 return headers, body
328 329
330 -class TextOcciRendering(Rendering):
331 ''' 332 This is a rendering which will use the HTTP header to place the information 333 in an syntax and semantics as defined in the OCCI specification. 334 ''' 335 336 mime_type = 'text/occi' 337 338 # disabling 'Method could be...' pylint check (want them to be overwritten) 339 # disabling 'Unused argument' pylint check (text/plain will use it :-)) 340 # pylint: disable=R0201,W0613 341
342 - def get_data(self, headers, body):
343 ''' 344 Mainly here so TextPlainRendering can reuse. 345 346 headers -- The headers of the request. 347 body -- The body of the request. 348 ''' 349 return _extract_data_from_headers(headers)
350
351 - def set_data(self, data):
352 ''' 353 Mainly here so TextPlainRendering can reuse. 354 355 data -- An HTTPData object. 356 ''' 357 return _set_data_to_headers(data, self.mime_type)
358
359 - def to_entity(self, headers, body, def_kind, extras):
360 data = self.get_data(headers, body) 361 entity = _to_entity(data, def_kind, self.registry, extras) 362 return entity
363
364 - def from_entity(self, entity):
365 data = _from_entity(entity, self.registry) 366 headers, body = self.set_data(data) 367 return headers, body
368
369 - def to_entities(self, headers, body, extras):
370 data = self.get_data(headers, body) 371 entities = _to_entities(data, self.registry, extras) 372 return entities
373
374 - def from_entities(self, entities, key):
375 data = _from_entities(entities, self.registry) 376 headers, body = self.set_data(data) 377 return headers, body
378
379 - def from_categories(self, categories):
380 data = _from_categories(categories, self.registry) 381 headers, body = self.set_data(data) 382 return headers, body
383
384 - def to_action(self, headers, body, extras):
385 data = self.get_data(headers, body) 386 action = _to_action(data, self.registry, extras) 387 return action
388
389 - def to_mixins(self, headers, body, extras):
390 data = self.get_data(headers, body) 391 mixin = _to_mixins(data, self.registry, extras) 392 return mixin
393
394 - def get_filters(self, headers, body, extras):
395 data = self.get_data(headers, body) 396 categories, attributes = _get_filter(data, self.registry, extras) 397 return categories, attributes
398 399
400 -def _extract_data_from_body(body):
401 ''' 402 Simple method to split out the information from the HTTP body. 403 404 body -- The HTTP body. 405 ''' 406 data = HTTPData() 407 for entry in body.split('\n'): 408 if entry.find(CATEGORY + ':') > -1: 409 data.categories.extend(_extract_values(entry, CATEGORY + ':')) 410 if entry.find(ATTRIBUTE + ':') > -1: 411 data.attributes.extend(_extract_values(entry, ATTRIBUTE + ':')) 412 if entry.find(LINK + ':') > -1: 413 data.links.extend(_extract_values(entry, LINK + ':')) 414 if entry.find(LOCATION + ':') > -1: 415 data.locations.extend(_extract_values(entry, LOCATION + ':')) 416 return data
417 418
419 -def _extract_values(entry, key):
420 ''' 421 In HTTP body OCCI renderings can either be in new lines or separated by ,. 422 423 entry -- The text line to look into. 424 key -- The key to look for and strip away. 425 ''' 426 items = [] 427 tmp = entry[entry.find(key) + len(key) + 1:] 428 if tmp.find(',') == -1: 429 items.append(tmp) 430 else: 431 split = shlex.shlex(tmp, posix=True) 432 split.whitespace = ',' 433 split.whitespace_split = True 434 items.extend(list(split)) 435 return items
436 437
438 -def _set_data_to_body(data, mime_type):
439 ''' 440 Simple method to set all information in the HTTP body. 441 442 data -- The data to set. 443 mime_type -- The content type to set. 444 ''' 445 body = '' 446 if len(data.categories) > 0: 447 for cat in data.categories: 448 body += '\n' + CATEGORY + ': ' + cat 449 450 if len(data.links) > 0: 451 for link in data.links: 452 body += '\n' + LINK + ': ' + link 453 454 if len(data.attributes) > 0: 455 for attr in data.attributes: 456 body += '\n' + ATTRIBUTE + ': ' + attr 457 458 if len(data.locations) > 0: 459 for loc in data.locations: 460 body += '\n' + LOCATION + ': ' + loc 461 462 return {CONTENT_TYPE: mime_type}, body
463 464
465 -class TextPlainRendering(TextOcciRendering):
466 ''' 467 This is a rendering which will use the HTTP body to place the information 468 in an syntax and semantics as defined in the OCCI specification. 469 ''' 470 471 mime_type = 'text/plain' 472
473 - def set_data(self, data):
474 return _set_data_to_body(data, self.mime_type)
475
476 - def get_data(self, headers, body):
477 return _extract_data_from_body(body)
478 479
480 -class TextUriListRendering(Rendering):
481 ''' 482 This is a rendering which can handle URI lists. 483 ''' 484 485 mime_type = 'text/uri-list' 486 error = 'Unable to handle this request with the text/uri-list' \ 487 ' rendering.' 488
489 - def to_entity(self, headers, body, def_kind, extras):
490 raise AttributeError(self.error)
491
492 - def from_entity(self, entity):
493 raise AttributeError(self.error)
494
495 - def to_entities(self, headers, body, extras):
496 raise AttributeError(self.error)
497
498 - def from_entities(self, entities, key):
499 body = '# uri:' + str(key) 500 for entity in entities: 501 body += '\n' + self.registry.get_hostname() + entity.identifier 502 return {CONTENT_TYPE: self.mime_type}, body
503
504 - def from_categories(self, categories):
505 raise AttributeError(self.error)
506
507 - def to_action(self, headers, body, extras):
508 raise AttributeError(self.error)
509
510 - def to_mixins(self, headers, body, extras):
511 raise AttributeError(self.error)
512
513 - def get_filters(self, headers, body, extras):
514 raise AttributeError(self.error)
515