#! /usr/bin/env python
#*******************************************************************************
# ALMA - Atacama Large Millimiter Array
# (c) Associated Universities Inc., 2005
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# "@(#) $Id: Recorder.py,v 1.2 2010/10/01 17:20:48 javarias Exp $"
#
# who when what
# -------- ---------- ----------------------------------------------
# rhiriart 2006-12-06 created
#
import os
import pickle
import time
import inspect
from xml.dom.minidom import parseString
class Recorder:
'''
This class is used by the ACS IDL Simulator to record a simulation session
into a file. This file can be used afterwards by the Player class to run
again the session over the "real" component.
'''
def __init__(self, comp_name):
'''
Constructor. Checks if the environment variable ACSSIM_REC_DIR has
been set. This should be the directory where the Recorder will output
invokation information. If this env. variable is not set the Recorder
class does nothing.
Parameters:
comp_name - Component name.
'''
self.ACSSIM_REC_DIR = 'ACSSIM_REC_DIR'
self.rec_file_name = None
self.rec_file = None
if self.ACSSIM_REC_DIR in os.environ.keys():
if not os.path.exists(os.environ[self.ACSSIM_REC_DIR]):
os.mkdir(os.environ[self.ACSSIM_REC_DIR])
self.rec_file_name = os.environ[self.ACSSIM_REC_DIR]+'/'+\
comp_name.replace('/', '_')+".xml"
self.start_time = -1
self.comp_name = comp_name
def begin(self):
'''
Begin the recording session.
'''
self.start_time = time.time()
if self.rec_file_name:
self.rec_file = open(self.rec_file_name, 'w')
self.rec_file.write('\n')
self.rec_file.write('\n')
self.rec_file.flush()
def end(self):
'''
End the recording session.
'''
if self.rec_file:
self.rec_file.write('\n')
self.rec_file.close()
def replaceXMLEntities(self, s):
'''
Replaces special characters of its corresponding XML entities.
& -> &
' -> '
< -> <
> -> >
" -> "
'''
return s.replace('&', '&').replace('\'', ''').replace('<', '<').replace('>', '>').replace('"', '"')
def record(self, meth_name, args):
'''
Record a method invokation.
Parameters:
method_name - Method name.
args - Argument list.
'''
t = time.time() - self.start_time
s = ' \n'
for arg in args:
sarg = self.replaceXMLEntities(pickle.dumps(arg))
s += ' ' + sarg + '\n'
s += ' \n'
if self.rec_file:
self.rec_file.write(s)
class Call:
'''
Represents an operation call over a given component.
Used by the Player class.
'''
def __init__(self, meth_name, args, t):
'''
Constructor.
Parameters:
meth_name - Method name.
args - Tuple containing the operation arguments.
'''
self.meth_name = meth_name
self.args = args
self.time = t
self.return_value = None
def getMethodName(self):
'''
Gets the method's name.
'''
return self.meth_name
def getArgs(self):
'''
Gets the method's arguments.
'''
return self.args
def getTime(self):
return self.time
def getReturnValue(self):
'''
Get the return value. Calling this method only makes sense after
calling invoke().
'''
return self.return_value
def invoke(self, component):
'''
Invoke the call represented by an instance of this class over a
component. Of course, the component should support the operation.
Parameters:
component - Component reference.
'''
method = None
for m in inspect.getmembers(component):
if m[0] == self.meth_name:
method = m[1]
if method:
self.return_value = apply(method, self.args)
def setArgs(self, args):
'''
Sets the arguments for this call. This is useful for cases where it is needed
to replace arguments in order for an invokation to work. For example, if
the arguments are CORBA references or callbacks, which only are valid during
the recording session.
Parameters:
args - Arguments tuple.
'''
self.args = args
class Player:
'''
Plays a record file generated with the IDL Simulator.
'''
def __init__(self, file_name):
'''
Constructor.
Parameters:
file_name - File generated by the IDL simulator when the ACSSIM_REC_DIR
environment variable is set in the process running the simulated
components.
This file contains a description of the calls to a simulated component,
along with its arguments.
'''
f = open(file_name)
xml_doc = f.read()
root_dom = parseString(xml_doc)
self.calls = []
session_dom = root_dom.firstChild
self.startTime = float(session_dom.getAttribute('start-time'))
self.compName = session_dom.getAttribute('comp-name')
for node in session_dom.childNodes:
if node.nodeName == 'Call':
meth_name = node.getAttribute('meth-name')
rel_time = float(node.getAttribute('time'))
args = []
for arg_node in node.childNodes:
if arg_node.nodeName == 'arg':
sarg = self.restoreXMLEntities(str(arg_node.firstChild.nodeValue))
arg = pickle.loads(sarg)
self.fixCORBAEnums(arg)
args.append(arg)
self.calls.append(Call(str(meth_name), tuple(args), rel_time))
def __getitem__(self, i):
'''
Overrided indexing operation.
'''
return self.calls[i]
def __len__(self):
return len(self.calls)
def restoreXMLEntities(self, s):
'''
Utilitiy function. Replaces XML entities for its respective characters.
& -> &
&apos -> '
< -> <
> -> >
" -> "
'''
return s.replace('"', '"').replace('>', '>').replace('<', '<').replace(''', '\'').replace('&', '&')
def fixCORBAEnums(self, obj):
'''
Utility function.
Fixes a problem with CORBA Enums. For some reason that I don't quite understand yet,
CORBA enumerations are not pickled/unpickled correctly. Using them as they are after
unpickling will throw a CORBA.BAD_PARAM exception. One way to fix this is simply to
recreate them, which is what this function does. It goes through each one of obj
members, find the CORBA enums and recreates them. It works recursively for each one of
obj members.
Parameters:
obj - An arbitrary CORBA object.
'''
import omniORB
# If obj is a list, fix each one of the members recursively.
if isinstance(obj, list):
for i in range(len(obj)):
# self.fixCORBAEnums(o)
if isinstance(obj[i], omniORB.EnumItem):
enum = obj[i]
enumName = str(enum)
moduleName = enum._parent_id.split(':').pop(1).split('/')[1]
module = __import__(moduleName)
newEnum = module.__dict__[enumName]
obj[i] = newEnum
# If obj is not a class instance, then there's nothing to do.
# E.g., primitive types: float, str, etc., which doesn't have '__dict__'.
try:
getattr(obj, '__dict__')
except AttributeError:
return
# Fix the case of obj being an enumeration itself.
# TODO it won't fix, as argument is passed by value.
# Change this function to return the modified value.
if isinstance(obj, omniORB.EnumItem):
enum = obj
enumName = str(enum)
moduleName = enum._parent_id.split(':').pop(1).split('/')[1]
module = __import__(moduleName)
newEnum = module.__dict__[enumName]
# print 'Replacing enumeration', enumName
obj = newEnum
# Fix IDL structures with nested enumerations.
# Go through each of of obj members ...
for k in obj.__dict__.keys():
# ...verify if it is an enumeration and in this case fix it
if isinstance(obj.__dict__[k], omniORB.EnumItem):
enum = obj.__dict__[k]
enumName = str(enum)
# (For example, if enum._parent_id is 'IDL:alma/Correlator/eDataProducts:1.0'
# then in moduleName we get 'Correlator'. Of course, this will work only
# if _parent_id follows this format. This should be true for all ALMA enums.)
moduleName = enum._parent_id.split(':').pop(1).split('/')[1]
module = __import__(moduleName)
newEnum = module.__dict__[enumName]
# print 'Replacing enumeration', k
obj.__dict__[k] = newEnum
# ... or if it is a CORBA structure, in this case call recursively, or ...
elif isinstance(obj.__dict__[k], omniORB.StructBase):
self.fixCORBAEnums(obj.__dict__[k])
# ... if it is a nested list, in this case case recursively in each one of the
# members.
elif isinstance(obj.__dict__[k], list):
for o in obj.__dict__[k]:
self.fixCORBAEnums(o)
def replaceCall(self, i, call):
'''
Replaces a call object.
Parameters:
i - Call number
call - Call object
'''
self.calls[i] = call
def getStartTime(self):
return self.startTime
def getComponentName(self):
return self.compName
#
# __oOo__