You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@oodt.apache.org by ke...@apache.org on 2010/03/21 23:08:02 UTC

svn commit: r925913 [1/2] - in /incubator/oodt/agility/agile-oodt/trunk: ./ docs/ oodt/ oodt/tests/

Author: kelly
Date: Sun Mar 21 22:08:01 2010
New Revision: 925913

URL: http://svn.apache.org/viewvc?rev=925913&view=rev
Log:
Initial Apache drop of Agile OODT with test cases refactored, Python 2.6 compatibility, and ASF headers/license/notice.

Added:
    incubator/oodt/agility/agile-oodt/trunk/NOTICE.txt
    incubator/oodt/agility/agile-oodt/trunk/README.txt
    incubator/oodt/agility/agile-oodt/trunk/distribute_setup.py
    incubator/oodt/agility/agile-oodt/trunk/docs/
    incubator/oodt/agility/agile-oodt/trunk/docs/HISTORY.txt
    incubator/oodt/agility/agile-oodt/trunk/docs/INSTALL.txt
    incubator/oodt/agility/agile-oodt/trunk/docs/LICENSE.txt
    incubator/oodt/agility/agile-oodt/trunk/oodt/   (with props)
    incubator/oodt/agility/agile-oodt/trunk/oodt/__init__.py
    incubator/oodt/agility/agile-oodt/trunk/oodt/oodterrors.py
    incubator/oodt/agility/agile-oodt/trunk/oodt/profile.py
    incubator/oodt/agility/agile-oodt/trunk/oodt/query.py
    incubator/oodt/agility/agile-oodt/trunk/oodt/tests/   (with props)
    incubator/oodt/agility/agile-oodt/trunk/oodt/tests/__init__.py
    incubator/oodt/agility/agile-oodt/trunk/oodt/tests/profileTest.py
    incubator/oodt/agility/agile-oodt/trunk/oodt/tests/queryTest.py
    incubator/oodt/agility/agile-oodt/trunk/oodt/tests/xmlutilsTest.py
    incubator/oodt/agility/agile-oodt/trunk/oodt/webgrid.py
    incubator/oodt/agility/agile-oodt/trunk/oodt/xmlutils.py
    incubator/oodt/agility/agile-oodt/trunk/setup.cfg
    incubator/oodt/agility/agile-oodt/trunk/setup.py
Modified:
    incubator/oodt/agility/agile-oodt/trunk/   (props changed)

Propchange: incubator/oodt/agility/agile-oodt/trunk/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Sun Mar 21 22:08:01 2010
@@ -0,0 +1,11 @@
+._*
+.DS_Store
+*.log
+*.pyc
+*.pyo
+*.egg-info
+dist
+build
+distribute-*.tar.gz
+distribute-*.egg
+doc.txt

Added: incubator/oodt/agility/agile-oodt/trunk/NOTICE.txt
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/NOTICE.txt?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/NOTICE.txt (added)
+++ incubator/oodt/agility/agile-oodt/trunk/NOTICE.txt Sun Mar 21 22:08:01 2010
@@ -0,0 +1,7 @@
+Agile Object Oriented Data Technology (OODT)
+Copyright 2010 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This is licensed software; see the docs/LICENSE.txt file.

Added: incubator/oodt/agility/agile-oodt/trunk/README.txt
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/README.txt?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/README.txt (added)
+++ incubator/oodt/agility/agile-oodt/trunk/README.txt Sun Mar 21 22:08:01 2010
@@ -0,0 +1,17 @@
+Agile OODT is a new version of `Object Oriented Data Technology`_.  It
+improves upon the previous version by being easier to develop, maintain, and
+extend; simpler to integrate by; and faster to use.
+
+.. This is licensed software; see the files NOTICE.txt and docs/LICENSE.txt.
+
+.. For installation instructions, see docs/INSTALL.txt.
+
+.. References:
+.. _`Object Oriented Data Technology`: http://incubator.apache.org/projects/oodt.html
+
+.. meta::
+    :keywords: OODT, data, object, OO, discovery, metadata,
+        transfer, transformation, query, search, retrieval
+    :description lang=en: Agile OODT, the nimble version of
+        Object Oriented Data Technology
+

Added: incubator/oodt/agility/agile-oodt/trunk/distribute_setup.py
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/distribute_setup.py?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/distribute_setup.py (added)
+++ incubator/oodt/agility/agile-oodt/trunk/distribute_setup.py Sun Mar 21 22:08:01 2010
@@ -0,0 +1,477 @@
+#!python
+"""Bootstrap distribute installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+    from distribute_setup import use_setuptools
+    use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import os
+import sys
+import time
+import fnmatch
+import tempfile
+import tarfile
+from distutils import log
+
+try:
+    from site import USER_SITE
+except ImportError:
+    USER_SITE = None
+
+try:
+    import subprocess
+
+    def _python_cmd(*args):
+        args = (sys.executable,) + args
+        return subprocess.call(args) == 0
+
+except ImportError:
+    # will be used for python 2.3
+    def _python_cmd(*args):
+        args = (sys.executable,) + args
+        # quoting arguments if windows
+        if sys.platform == 'win32':
+            def quote(arg):
+                if ' ' in arg:
+                    return '"%s"' % arg
+                return arg
+            args = [quote(arg) for arg in args]
+        return os.spawnl(os.P_WAIT, sys.executable, *args) == 0
+
+DEFAULT_VERSION = "0.6.10"
+DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/"
+SETUPTOOLS_FAKED_VERSION = "0.6c11"
+
+SETUPTOOLS_PKG_INFO = """\
+Metadata-Version: 1.0
+Name: setuptools
+Version: %s
+Summary: xxxx
+Home-page: xxx
+Author: xxx
+Author-email: xxx
+License: xxx
+Description: xxx
+""" % SETUPTOOLS_FAKED_VERSION
+
+
+def _install(tarball):
+    # extracting the tarball
+    tmpdir = tempfile.mkdtemp()
+    log.warn('Extracting in %s', tmpdir)
+    old_wd = os.getcwd()
+    try:
+        os.chdir(tmpdir)
+        tar = tarfile.open(tarball)
+        _extractall(tar)
+        tar.close()
+
+        # going in the directory
+        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+        os.chdir(subdir)
+        log.warn('Now working in %s', subdir)
+
+        # installing
+        log.warn('Installing Distribute')
+        if not _python_cmd('setup.py', 'install'):
+            log.warn('Something went wrong during the installation.')
+            log.warn('See the error message above.')
+    finally:
+        os.chdir(old_wd)
+
+
+def _build_egg(egg, tarball, to_dir):
+    # extracting the tarball
+    tmpdir = tempfile.mkdtemp()
+    log.warn('Extracting in %s', tmpdir)
+    old_wd = os.getcwd()
+    try:
+        os.chdir(tmpdir)
+        tar = tarfile.open(tarball)
+        _extractall(tar)
+        tar.close()
+
+        # going in the directory
+        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
+        os.chdir(subdir)
+        log.warn('Now working in %s', subdir)
+
+        # building an egg
+        log.warn('Building a Distribute egg in %s', to_dir)
+        _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
+
+    finally:
+        os.chdir(old_wd)
+    # returning the result
+    log.warn(egg)
+    if not os.path.exists(egg):
+        raise IOError('Could not build the egg.')
+
+
+def _do_download(version, download_base, to_dir, download_delay):
+    egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg'
+                       % (version, sys.version_info[0], sys.version_info[1]))
+    if not os.path.exists(egg):
+        tarball = download_setuptools(version, download_base,
+                                      to_dir, download_delay)
+        _build_egg(egg, tarball, to_dir)
+    sys.path.insert(0, egg)
+    import setuptools
+    setuptools.bootstrap_install_from = egg
+
+
+def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+                   to_dir=os.curdir, download_delay=15, no_fake=True):
+    # making sure we use the absolute path
+    to_dir = os.path.abspath(to_dir)
+    was_imported = 'pkg_resources' in sys.modules or \
+        'setuptools' in sys.modules
+    try:
+        try:
+            import pkg_resources
+            if not hasattr(pkg_resources, '_distribute'):
+                if not no_fake:
+                    _fake_setuptools()
+                raise ImportError
+        except ImportError:
+            return _do_download(version, download_base, to_dir, download_delay)
+        try:
+            pkg_resources.require("distribute>="+version)
+            return
+        except pkg_resources.VersionConflict:
+            e = sys.exc_info()[1]
+            if was_imported:
+                sys.stderr.write(
+                "The required version of distribute (>=%s) is not available,\n"
+                "and can't be installed while this script is running. Please\n"
+                "install a more recent version first, using\n"
+                "'easy_install -U distribute'."
+                "\n\n(Currently using %r)\n" % (version, e.args[0]))
+                sys.exit(2)
+            else:
+                del pkg_resources, sys.modules['pkg_resources']    # reload ok
+                return _do_download(version, download_base, to_dir,
+                                    download_delay)
+        except pkg_resources.DistributionNotFound:
+            return _do_download(version, download_base, to_dir,
+                                download_delay)
+    finally:
+        if not no_fake:
+            _create_fake_setuptools_pkg_info(to_dir)
+
+def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
+                        to_dir=os.curdir, delay=15):
+    """Download distribute from a specified location and return its filename
+
+    `version` should be a valid distribute version number that is available
+    as an egg for download under the `download_base` URL (which should end
+    with a '/'). `to_dir` is the directory where the egg will be downloaded.
+    `delay` is the number of seconds to pause before an actual download
+    attempt.
+    """
+    # making sure we use the absolute path
+    to_dir = os.path.abspath(to_dir)
+    try:
+        from urllib.request import urlopen
+    except ImportError:
+        from urllib2 import urlopen
+    tgz_name = "distribute-%s.tar.gz" % version
+    url = download_base + tgz_name
+    saveto = os.path.join(to_dir, tgz_name)
+    src = dst = None
+    if not os.path.exists(saveto):  # Avoid repeated downloads
+        try:
+            log.warn("Downloading %s", url)
+            src = urlopen(url)
+            # Read/write all in one block, so we don't create a corrupt file
+            # if the download is interrupted.
+            data = src.read()
+            dst = open(saveto, "wb")
+            dst.write(data)
+        finally:
+            if src:
+                src.close()
+            if dst:
+                dst.close()
+    return os.path.realpath(saveto)
+
+
+def _patch_file(path, content):
+    """Will backup the file then patch it"""
+    existing_content = open(path).read()
+    if existing_content == content:
+        # already patched
+        log.warn('Already patched.')
+        return False
+    log.warn('Patching...')
+    _rename_path(path)
+    f = open(path, 'w')
+    try:
+        f.write(content)
+    finally:
+        f.close()
+    return True
+
+
+def _same_content(path, content):
+    return open(path).read() == content
+
+def _no_sandbox(function):
+    def __no_sandbox(*args, **kw):
+        try:
+            from setuptools.sandbox import DirectorySandbox
+            def violation(*args):
+                pass
+            DirectorySandbox._old = DirectorySandbox._violation
+            DirectorySandbox._violation = violation
+            patched = True
+        except ImportError:
+            patched = False
+
+        try:
+            return function(*args, **kw)
+        finally:
+            if patched:
+                DirectorySandbox._violation = DirectorySandbox._old
+                del DirectorySandbox._old
+
+    return __no_sandbox
+
+@_no_sandbox
+def _rename_path(path):
+    new_name = path + '.OLD.%s' % time.time()
+    log.warn('Renaming %s into %s', path, new_name)
+    os.rename(path, new_name)
+    return new_name
+
+def _remove_flat_installation(placeholder):
+    if not os.path.isdir(placeholder):
+        log.warn('Unkown installation at %s', placeholder)
+        return False
+    found = False
+    for file in os.listdir(placeholder):
+        if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
+            found = True
+            break
+    if not found:
+        log.warn('Could not locate setuptools*.egg-info')
+        return
+
+    log.warn('Removing elements out of the way...')
+    pkg_info = os.path.join(placeholder, file)
+    if os.path.isdir(pkg_info):
+        patched = _patch_egg_dir(pkg_info)
+    else:
+        patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
+
+    if not patched:
+        log.warn('%s already patched.', pkg_info)
+        return False
+    # now let's move the files out of the way
+    for element in ('setuptools', 'pkg_resources.py', 'site.py'):
+        element = os.path.join(placeholder, element)
+        if os.path.exists(element):
+            _rename_path(element)
+        else:
+            log.warn('Could not find the %s element of the '
+                     'Setuptools distribution', element)
+    return True
+
+
+def _after_install(dist):
+    log.warn('After install bootstrap.')
+    placeholder = dist.get_command_obj('install').install_purelib
+    _create_fake_setuptools_pkg_info(placeholder)
+
+@_no_sandbox
+def _create_fake_setuptools_pkg_info(placeholder):
+    if not placeholder or not os.path.exists(placeholder):
+        log.warn('Could not find the install location')
+        return
+    pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
+    setuptools_file = 'setuptools-%s-py%s.egg-info' % \
+            (SETUPTOOLS_FAKED_VERSION, pyver)
+    pkg_info = os.path.join(placeholder, setuptools_file)
+    if os.path.exists(pkg_info):
+        log.warn('%s already exists', pkg_info)
+        return
+
+    log.warn('Creating %s', pkg_info)
+    f = open(pkg_info, 'w')
+    try:
+        f.write(SETUPTOOLS_PKG_INFO)
+    finally:
+        f.close()
+
+    pth_file = os.path.join(placeholder, 'setuptools.pth')
+    log.warn('Creating %s', pth_file)
+    f = open(pth_file, 'w')
+    try:
+        f.write(os.path.join(os.curdir, setuptools_file))
+    finally:
+        f.close()
+
+def _patch_egg_dir(path):
+    # let's check if it's already patched
+    pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+    if os.path.exists(pkg_info):
+        if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
+            log.warn('%s already patched.', pkg_info)
+            return False
+    _rename_path(path)
+    os.mkdir(path)
+    os.mkdir(os.path.join(path, 'EGG-INFO'))
+    pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
+    f = open(pkg_info, 'w')
+    try:
+        f.write(SETUPTOOLS_PKG_INFO)
+    finally:
+        f.close()
+    return True
+
+
+def _before_install():
+    log.warn('Before install bootstrap.')
+    _fake_setuptools()
+
+
+def _under_prefix(location):
+    if 'install' not in sys.argv:
+        return True
+    args = sys.argv[sys.argv.index('install')+1:]
+    for index, arg in enumerate(args):
+        for option in ('--root', '--prefix'):
+            if arg.startswith('%s=' % option):
+                top_dir = arg.split('root=')[-1]
+                return location.startswith(top_dir)
+            elif arg == option:
+                if len(args) > index:
+                    top_dir = args[index+1]
+                    return location.startswith(top_dir)
+            elif option == '--user' and USER_SITE is not None:
+                return location.startswith(USER_SITE)
+    return True
+
+
+def _fake_setuptools():
+    log.warn('Scanning installed packages')
+    try:
+        import pkg_resources
+    except ImportError:
+        # we're cool
+        log.warn('Setuptools or Distribute does not seem to be installed.')
+        return
+    ws = pkg_resources.working_set
+    try:
+        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
+                                  replacement=False))
+    except TypeError:
+        # old distribute API
+        setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
+
+    if setuptools_dist is None:
+        log.warn('No setuptools distribution found')
+        return
+    # detecting if it was already faked
+    setuptools_location = setuptools_dist.location
+    log.warn('Setuptools installation detected at %s', setuptools_location)
+
+    # if --root or --preix was provided, and if
+    # setuptools is not located in them, we don't patch it
+    if not _under_prefix(setuptools_location):
+        log.warn('Not patching, --root or --prefix is installing Distribute'
+                 ' in another location')
+        return
+
+    # let's see if its an egg
+    if not setuptools_location.endswith('.egg'):
+        log.warn('Non-egg installation')
+        res = _remove_flat_installation(setuptools_location)
+        if not res:
+            return
+    else:
+        log.warn('Egg installation')
+        pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
+        if (os.path.exists(pkg_info) and
+            _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
+            log.warn('Already patched.')
+            return
+        log.warn('Patching...')
+        # let's create a fake egg replacing setuptools one
+        res = _patch_egg_dir(setuptools_location)
+        if not res:
+            return
+    log.warn('Patched done.')
+    _relaunch()
+
+
+def _relaunch():
+    log.warn('Relaunching...')
+    # we have to relaunch the process
+    args = [sys.executable] + sys.argv
+    sys.exit(subprocess.call(args))
+
+
+def _extractall(self, path=".", members=None):
+    """Extract all members from the archive to the current working
+       directory and set owner, modification time and permissions on
+       directories afterwards. `path' specifies a different directory
+       to extract to. `members' is optional and must be a subset of the
+       list returned by getmembers().
+    """
+    import copy
+    import operator
+    from tarfile import ExtractError
+    directories = []
+
+    if members is None:
+        members = self
+
+    for tarinfo in members:
+        if tarinfo.isdir():
+            # Extract directories with a safe mode.
+            directories.append(tarinfo)
+            tarinfo = copy.copy(tarinfo)
+            tarinfo.mode = 448 # decimal for oct 0700
+        self.extract(tarinfo, path)
+
+    # Reverse sort directories.
+    if sys.version_info < (2, 4):
+        def sorter(dir1, dir2):
+            return cmp(dir1.name, dir2.name)
+        directories.sort(sorter)
+        directories.reverse()
+    else:
+        directories.sort(key=operator.attrgetter('name'), reverse=True)
+
+    # Set correct owner, mtime and filemode on directories.
+    for tarinfo in directories:
+        dirpath = os.path.join(path, tarinfo.name)
+        try:
+            self.chown(tarinfo, dirpath)
+            self.utime(tarinfo, dirpath)
+            self.chmod(tarinfo, dirpath)
+        except ExtractError:
+            e = sys.exc_info()[1]
+            if self.errorlevel > 1:
+                raise
+            else:
+                self._dbg(1, "tarfile: %s" % e)
+
+
+def main(argv, version=DEFAULT_VERSION):
+    """Install or upgrade setuptools and EasyInstall"""
+    tarball = download_setuptools()
+    _install(tarball)
+
+
+if __name__ == '__main__':
+    main(sys.argv[1:])

Added: incubator/oodt/agility/agile-oodt/trunk/docs/HISTORY.txt
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/docs/HISTORY.txt?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/docs/HISTORY.txt (added)
+++ incubator/oodt/agility/agile-oodt/trunk/docs/HISTORY.txt Sun Mar 21 22:08:01 2010
@@ -0,0 +1,30 @@
+Changelog
+=========
+
+What follows is a history of changes to this software.
+
+
+1.0.0 - Initial Apache Release
+------------------------------
+
+This release marks the induction of Agile OODT into the Apache Software
+Foundation's Incubator_, and in celebration we call it version 1 (finally).
+
+
+0.0.1 - Package Cleanup
+-----------------------
+
+This release merely cleaned up the harsh edges in the packaging of Agile OODT.
+It made the package more amenable to setuptools.
+
+
+0.0.0 - FCS
+-----------
+
+The first customer ship of OODT Agility.  This software was used to implement
+the Planetary Data Access Protocol (PDAP) for the IPDA_.
+
+
+.. References:
+.. _IPDA: http://planetarydata.org/
+.. _Incubator: http://incubator.apache.org/
\ No newline at end of file

Added: incubator/oodt/agility/agile-oodt/trunk/docs/INSTALL.txt
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/docs/INSTALL.txt?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/docs/INSTALL.txt (added)
+++ incubator/oodt/agility/agile-oodt/trunk/docs/INSTALL.txt Sun Mar 21 22:08:01 2010
@@ -0,0 +1,91 @@
+Installation
+============
+
+What follows are the installation instructions for Agile OODT.
+
+
+Quick Instructions
+------------------
+
+As a user with administrative privileges, run either::
+
+    pip oodt
+
+or::
+
+    easy_install oodt
+
+depending on what's available on your system.  You're done!
+
+
+Full Instructions
+-----------------
+
+Agile OODT requires the Python_ programming language.  We recommend version 2.4
+or later.  As of this writing, 2.6 is the latest stable version.  If Python is
+not yet installed on your system, you can find binary and and source
+distributions from the Python website.
+
+To test if a correct version of Python is available on your system, run::
+
+    python -V
+    
+You should see output similar to::
+
+    Python 2.6
+    
+indicating the version of Python installed.  You may then proceed to install
+Agile OODT.
+
+By far the easiest, recommended, and encouraged way to install Agile OODT is
+either with Pip_ or EasyInstall_.  If your Python installation has either of
+these installers available to it, then one command is all you need to run in
+order to download, build, install, and generate command-line tools all in one
+go for all users on your system.  For Pip users, it's::
+
+    pip oodt
+
+And for EasyInstall users, it's::
+
+    easy_install oodt
+    
+Be sure to run that command as an administrative user.  For example, on Mac OS
+X and other Unix systems, you might need to run either of::
+
+    sudo pip oodt
+    sudo easy_install oodt
+
+
+Installing From Source
+~~~~~~~~~~~~~~~~~~~~~~
+
+If neither Pip nor EasyInstall are available, you can still make a proper
+installation of Agile OODT by building it from its source code.  Just follow
+these instructions:
+
+1.  Download the Agile OODT source distribution and extract the source
+    archive.  The source distribution is packaged as a gzip'd tar archive.
+2.  Change the current working directory to the newly extracted directory.
+3.  As an administrative user, run: ``python setup.py install``
+
+
+For More Information
+--------------------
+
+Visit any of the following links for additional information, to ask questions,
+report bugs, and so forth:
+
+OODT Home Page
+    http://incubator.apache.org/projects/oodt.html
+Mailing List for OODT Development
+    mailto:oodt-dev@incubator.apache.org
+Package Page (Cheese Shop)
+    http://pypi.python.org/pypi/oodt/
+Issue Tracker (submit bug reports here)
+    https://issues.apache.org/jira/browse/OODT
+
+
+.. References:
+.. _EasyInstall: http://packages.python.org/distribute/easy_install.html
+.. _Pip: http://pip.openplans.org/
+.. _Python: http://python.org/

Added: incubator/oodt/agility/agile-oodt/trunk/docs/LICENSE.txt
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/docs/LICENSE.txt?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/docs/LICENSE.txt (added)
+++ incubator/oodt/agility/agile-oodt/trunk/docs/LICENSE.txt Sun Mar 21 22:08:01 2010
@@ -0,0 +1,179 @@
+License, Terms, and Conditions
+==============================
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS

Propchange: incubator/oodt/agility/agile-oodt/trunk/oodt/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Sun Mar 21 22:08:01 2010
@@ -0,0 +1,8 @@
+._*
+.DS_Store
+*.log
+*.pyc
+*.pyo
+*.egg-info
+dist
+build

Added: incubator/oodt/agility/agile-oodt/trunk/oodt/__init__.py
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/oodt/__init__.py?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/oodt/__init__.py (added)
+++ incubator/oodt/agility/agile-oodt/trunk/oodt/__init__.py Sun Mar 21 22:08:01 2010
@@ -0,0 +1,27 @@
+# encoding: utf-8
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE.txt file distributed with
+# this work for additional information regarding copyright ownership.  The ASF
+# licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.    
+
+'''Agile Object Oriented Data Technology (OODT).
+
+OODT is a set of software components that permit the discovery, correlation,
+search, and retrieval of data and metadata.  For more information, see the
+OODT web site_.
+
+.. _site: http://incubator.apache.org/projects/oodt.html
+'''
+
+__docformat__ = 'restructuredtext'

Added: incubator/oodt/agility/agile-oodt/trunk/oodt/oodterrors.py
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/oodt/oodterrors.py?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/oodt/oodterrors.py (added)
+++ incubator/oodt/agility/agile-oodt/trunk/oodt/oodterrors.py Sun Mar 21 22:08:01 2010
@@ -0,0 +1,26 @@
+# encoding: utf-8
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE.txt file distributed with
+# this work for additional information regarding copyright ownership.  The ASF
+# licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.    
+
+'''Object Oriented Data Technology exceptions.
+'''
+
+__docformat__ = 'restructuredtext'
+
+class OODTException(Exception):
+    '''Common OODT exception base.
+    '''
+    pass

Added: incubator/oodt/agility/agile-oodt/trunk/oodt/profile.py
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/oodt/profile.py?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/oodt/profile.py (added)
+++ incubator/oodt/agility/agile-oodt/trunk/oodt/profile.py Sun Mar 21 22:08:01 2010
@@ -0,0 +1,502 @@
+# encoding: utf-8
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE.txt file distributed with
+# this work for additional information regarding copyright ownership.  The ASF
+# licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.    
+
+'''Profiles.  A profile is a metadata description of a resource.
+
+Profiles capture:
+
+* Inception metadata.  This includes items such as a resouce's title,
+  description, creators, contributors, publishers, language, and so on.  This
+  set of metdata is based on the Dublin Core.  Class `ResourceAttributes`
+  captures this metadata.
+
+* Composition metadata.  This includes data elements that describe what
+  the resource contains, such as ranges of data values (high and low
+  temperature, latitude/longitude) or enumerated data values (zoning code,
+  genus/species, etc.).  Subclasses of `ProfileElement` captures this
+  metadata.
+
+* Profile metadata.  This is metadata describing the profile itself, such
+  as revision notes, ID number, etc.  Class `ProfileAttributes` captures this
+  metadata.
+
+Objects of these classes are unified into a single `Profile` object.
+'''
+
+__docformat__ = 'restructuredtext'
+
+import xmlutils
+import xml.dom
+from xmlutils import DocumentableField
+
+class ProfileAttributes(xmlutils.Documentable):
+    '''Attributes of a profile.  These are attributes not related to the
+    resource that a profile profiles, but rather the profile itself.  In
+    most cases, simply constructing this object with no initializer
+    arguments will suffice.
+    '''
+    def __init__(self, id='UNKNOWN', version='1.0.0', type='profile', statusID='active', securityType='unclassified',
+        parentID='UNKNOWN', childIDs=None, regAuthority='UNKNOWN', revNotes=None, node=None):
+        '''Initialize profile attributes.
+        
+        - `id` unique ID for the profile
+        - `version` number
+        - `type` should always be the string "profile"
+        - `statusID` should always be the string "active"
+        - `securityType` tells whether the profile is secret
+        - `parentID` gives the unique ID for any "parent" profile
+        - `childIDs` should be a sequnece of any "children" profiles
+        - `regAuthority` is a unique ID identifying the registration
+           authority responsible for the profile
+        - `revNotes` is a sequence of comments detailing historical
+           changes to the profile
+        - `node` is an XML DOM node.  If the `node` argument is given,
+           it's used to initialize the object.
+        '''
+        self.id = id
+        self.version = version
+        self.type = type
+        self.statusID = statusID
+        self.securityType = securityType
+        self.parentID = parentID
+        self.childIDs = childIDs
+        self.regAuthority = regAuthority
+        self.revNotes = revNotes
+        if self.childIDs is None:
+            self.childIDs = []
+        if self.revNotes is None:
+            self.revNotes = []
+        if node is not None:
+            self.parse(node)
+    
+    def getDocumentElementName(self):
+        '''Get the XML tag for objects of this class: `profAttributes`.
+        '''
+        return 'profAttributes'
+    
+    def getDocumentableFields(self):
+        '''Get the attributes that are put into XML.
+        '''
+        return (DocumentableField('id', u'profId', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('version', u'profVersion', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('type', u'profType', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('statusID', u'profStatusId', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('securityType', u'profSecurityType', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('parentID', u'profParentId', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('childIDs', u'profChildId', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('regAuthority', u'profRegAuthority', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('revNotes', u'profRevisionNote', DocumentableField.MULTI_VALUED_KIND))
+
+    def __repr__(self):
+        return ('ProfileAttributes(id=%s,version=%s,type=%s,statusID=%s,securityType=%s,parentID=%s,childIDs=%s,' \
+            'regAuthority=%s,revNotes=%s)') % (self.id, self.version, self.type, self.statusID, self.securityType,
+            self.parentID, self.childIDs, self.regAuthority, self.revNotes)
+            
+    def __cmp__(self, other):
+        return cmp(self.id, other.id)
+    
+    def __hash__(self):
+        return hash(self.id)
+    
+
+class ResourceAttributes(xmlutils.Documentable):
+    '''Attributes of the resource.  Objects of this class collect data about the resource's
+    inception and are based on Dublin Core.
+    '''
+    def __init__(self, identifier='UNKNOWN', title='UNKNOWN', formats=None, description='UNKNOWN', creators=None, subjects=None,
+        publishers=None, contributors=None, dates=None, types=None, sources=None, languages=None, relations=None,
+        coverages=None, rights=None, contexts=None, aggregation='UNKNOWN', resClass='UNKNOWN', locations=None, node=None):
+        '''Initialize ResourceAttributes.
+        
+        The following arguments are required:
+        
+        - `identifier` is a URI uniquely identifying the resource
+        - `title` names the resource
+        - `description` gives a summary or abstract of it
+        - `aggregation` tells the gross structure of the resource and
+           should be one of the following values:
+            - data.granule
+            - data.dataSet
+            - data.dataSetCollection
+        - `resClass` gives the kind of the resource (what to expect
+           when connecting to one of its locations)
+        
+        All of the others should be initialized with sequences as the
+        resource attributes follows the Dublin Core recommendation on
+        multiplicity.
+        
+        If `node` is given, it's treated as an XML DOM node and is used to
+        initialize the resource attributes.
+        '''
+        self.identifier = identifier
+        self.title = title
+        self.formats = formats
+        self.description = description
+        self.creators = creators
+        self.subjects = subjects
+        self.publishers = publishers
+        self.contributors = contributors
+        self.dates = dates
+        self.types = types
+        self.sources = sources
+        self.languages = languages
+        self.relations = relations
+        self.coverages = coverages
+        self.rights = rights
+        self.contexts = contexts
+        self.aggregation = aggregation
+        self.resClass = resClass
+        self.locations = locations
+        for attr in ('formats', 'creators', 'subjects', 'publishers', 'contributors', 'dates', 'sources', 'languages',
+            'relations', 'coverages', 'rights', 'contexts', 'locations', 'types'):
+            if getattr(self, attr) is None:
+                setattr(self, attr, [])
+        if node is not None:
+            self.parse(node)
+    
+    def getDocumentElementName(self):
+        '''Give the XML tag name: `resAttributes`.
+        '''
+        return 'resAttributes'
+        
+    def __hash__(self):
+        return hash(self.identifier)
+    
+    def __cmp__(self, other):
+        return cmp(self.identifier, other.identifier)
+    
+    def getDocumentableFields(self):
+        '''Get the attributes that go into XML.
+        '''
+        return (DocumentableField('identifier', u'Identifier', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('title', u'Title', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('formats', u'Format', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('description', u'Description', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('creators', u'Creator', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('subjects', u'Subject', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('publishers', u'Publisher', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('contributors', u'Contributor', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('dates', u'Date', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('types', u'Type', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('sources', u'Source', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('languages', u'Language', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('relations', u'Relation', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('coverages', u'Coverage', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('rights', u'Rights', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('contexts', u'resContext', DocumentableField.MULTI_VALUED_KIND),
+            DocumentableField('aggregation', u'resAggregation', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('resClass', u'resClass', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('locations', u'resLocation', DocumentableField.MULTI_VALUED_KIND))
+    
+
+class Profile(object):
+    '''A profile "profiles" a resource by describing it with metadata.
+    '''
+    def __init__(self, profAttr=None, resAttr=None, profElements=None, node=None):
+        '''Initialize a profile.  The profElements should be a dicitonary that maps
+        from element name to instance of a `ProfileElement`.
+        '''
+        self.profAttr, self.resAttr, self.profElements = profAttr, resAttr, profElements
+        if self.profAttr is None:
+            self.profAttr = ProfileAttributes()
+        if self.resAttr is None:
+            self.resAttr = ResourceAttributes()
+        if self.profElements is None:
+            self.profElements = {}
+        if node is not None:
+            self.parse(node)
+    
+    def parse(self, node):
+        '''Initialize this object from the given XML DOM `node`.
+        '''
+        if node.nodeName != 'profile':
+            raise ValueError('Expected profile element but got "%s"' % node.nodeName)
+        for child in filter(lambda node: node.nodeType == xml.dom.Node.ELEMENT_NODE, node.childNodes):
+            name = child.nodeName
+            if name == u'profAttributes':
+                self.profAttr = ProfileAttributes()
+                self.profAttr.parse(child)
+            elif name == u'resAttributes':
+                self.resAttr = ResourceAttributes()
+                self.resAttr.parse(child)
+            elif name == u'profElement':
+                elem = _parseProfileElement(child)
+                self.profElements[elem.name] = elem
+                
+    def toXML(self, owner):
+        '''Convert this object into XML owned by the given `owner` document.
+        '''
+        root = owner.createElement(u'profile')
+        root.appendChild(self.profAttr.toXML(owner))
+        root.appendChild(self.resAttr.toXML(owner))
+        for elem in self.profElements.itervalues():
+            root.appendChild(elem.toXML(owner))
+        return root
+    
+    def __cmp__(self, other):
+        profAttr = cmp(self.profAttr, other.profAttr)
+        if profAttr < 0:
+            return -1
+        elif profAttr == 0:
+            resAttr = cmp(self.resAttr, other.resAttr)
+            if resAttr < 0:
+                return -1
+            elif resAttr == 0:
+                return cmp(self.profElements, other.profElements)
+        return 1
+    
+    def __hash__(self):
+        return hash(self.profAttr) ^ hash(self.resAttr)
+    
+    def __repr__(self):
+        return 'Profile(profAttr=%s,resAttr=%s,profElements=%s)' % (self.profAttr, self.resAttr, self.profElements)
+    
+
+class ProfileElement(object):
+    '''Abstract profile element.
+    '''
+    def __init__(self, name, description, type, units, synonyms, comment):
+        '''Initialize profile element.
+        '''
+        self.name = name
+        self.description = description
+        self.type = type
+        self.units = units
+        self.synonyms = synonyms
+        self.comment = comment
+    
+    def __repr__(self):
+        return 'ProfileElement(name=%r,description=%r,type=%r,units=%r,synonyms=%r,comment=%r)' % (self.name,
+            self.description, self.type, self.units, self.synonyms, self.comment)
+    
+    def __cmp__(self, other):
+        rc = cmp(self.name, other.name)
+        if rc < 0:
+            return -1
+        elif rc == 0:
+            rc = cmp(self.description, other.description)
+            if rc < 0:
+                return -1
+            elif rc == 0:
+                rc = cmp(self.type, other.type)
+                if rc < 0:
+                    return -1
+                elif rc == 0:
+                    rc = cmp(self.units, other.units)
+                    if rc < 0:
+                        return -1
+                    elif rc == 0:
+                        rc = cmp(self.synonyms, other.synonyms)
+                        if rc < 0:
+                            return -1
+                        elif rc == 0:
+                            return cmp(self.comment, other.comment)
+        return 1
+    
+    def __hash__(self):
+        return reduce(lambda x, y: hash(x) ^ hash(y), [getattr(self, attr) for attr in ('name', 'description', 'type',
+            'units', 'synonyms', 'comment')])
+    
+    def isEnumerated(self):
+        '''Is this an enumerated element?  Enumerated elements include those with
+        discrete values as well unspecified elements.
+        '''
+        return False;
+    
+    def getValues(self):
+        '''Get the discrete values of this element, which may be empty.
+        '''
+        return []
+    
+    def getMinValue(self):
+        '''Get the minimum value of this element, which may be zero.
+        '''
+        return 0.0
+    
+    def getMaxValue(self):
+        '''Get the maximum value of this element, which may be zero.
+        '''
+        return 0.0
+    
+    def toXML(self, owner):
+        '''Convert this object into XML owned by the given `owner` document.
+        '''
+        root = owner.createElement('profElement')
+        xmlutils.add(root, u'elemId', self.name)
+        xmlutils.add(root, u'elemName', self.name)
+        xmlutils.add(root, u'elemDesc', self.description)
+        xmlutils.add(root, u'elemType', self.type)
+        xmlutils.add(root, u'elemUnit', self.units)
+        if self.isEnumerated():
+            flag = 'T'
+        else:
+            flag = 'F'
+        xmlutils.add(root, u'elemEnumFlag', flag)
+        for value in self.getValues():
+            elem = owner.createElement('elemValue')
+            root.appendChild(elem)
+            elem.appendChild(owner.createCDATASection(value))
+        if not self.isEnumerated():
+            xmlutils.add(root, u'elemMinValue', str(self.getMinValue()))
+            xmlutils.add(root, u'elemMaxValue', str(self.getMaxValue()))
+        xmlutils.add(root, u'elemSynonym', self.synonyms)
+        xmlutils.add(root, u'elemComment', self.comment)
+        return root
+    
+
+class UnspecifiedProfileElement(ProfileElement):
+    '''An unspecified profile element merely documents the existence of a element within a
+    dataset but says nothing about its actual values.
+    '''
+    def __init__(self, name, description, type, units, synonyms, comment):
+        '''Initialize an "unspecified" profile element.
+        '''
+        super(UnspecifiedProfileElement, self).__init__(name, description, type, units, synonyms, comment)
+    
+    def isEnumerated(self):
+        '''An unspecified profile element is indeed enumerated.  It just has no enumerated values.
+        '''
+        return True
+    
+
+class EnumeratedProfileElement(ProfileElement):
+    '''An enumerated profile element describes set of discrete values.
+    '''
+    def __init__(self, name, description, type, units, synonyms, comment, values):
+        '''Initialize an enumerated profile element.
+        '''
+        super(EnumeratedProfileElement, self).__init__(name, description, type, units, synonyms, comment)
+        self.values = values
+        
+    def isEnumerated(self):
+        '''An enumerated profile element is indeed enumerated.
+        '''
+        return True
+    
+    def getValues(self):
+        '''Return the sequence of values.
+        '''
+        return self.values
+        
+    def __cmp__(self, other):
+        rc = super(EnumeratedProfileElement, self).__cmp__(other)
+        if rc < 0:
+            return -1
+        elif rc == 0:
+            return cmp(self.values, other.values)
+        return 1
+    
+
+class RangedProfileElement(ProfileElement):
+    '''A ranged profile element describes a value between two numeric ranges.
+    '''
+    def __init__(self, name, description, type, units, synonyms, comment, minValue, maxValue):
+        '''Initialize a ranged profile element.
+        '''
+        super(RangedProfileElement, self).__init__(name, description, type, units, synonyms, comment)
+        self.minValue = minValue
+        self.maxValue = maxValue
+    
+    def getMinValue(self):
+        '''Get the minimum value.
+        '''
+        return self.minValue
+    
+    def getMaxValue(self):
+        '''Get the maximum value.
+        '''
+        return self.maxValue
+    
+    def __repr__(self):
+        return 'RangedProfileElement(%r,minValue=%r,maxValue=%r)' % (super(RangedProfileElement, self).__repr__(),
+            self.minValue, self.maxValue)
+    
+    def __cmp__(self, other):
+        rc = super(RangedProfileElement, self).__cmp__(other)
+        if rc < 0:
+            return -1
+        elif rc == 0:
+            rc = self.minValue - other.minValue
+            if rc < 0:
+                return -1
+            elif rc == 0:
+                return self.maxValue - other.maxValue
+        return 1
+    
+
+def _parseProfileElement(node):
+    '''Construct an appropriate profile element from the given DOM node.
+    '''
+    if node.nodeName != u'profElement':
+        raise ValueError('Expected profElement element but got "%s"' % node.nodeName)
+    settings = dict(elemId=u'UNKNOWN', elemDesc=u'UNKNOWN', elemType=u'UNKNOWN', elemUnit=u'UNKNOWN', elemEnumFlag=u'F',
+        elemComment=u'UNKNOWN')
+    values, syns = [], []
+    for child in filter(lambda node: node.nodeType == xml.dom.Node.ELEMENT_NODE, node.childNodes):
+        text = xmlutils.text(child)
+        if child.nodeName == u'elemValue':
+            values.append(text)
+        elif child.nodeName == u'elemSynonym':
+            syns.append(text)
+        else:
+            settings[str(child.nodeName)] = text
+    if 'elemName' not in settings:
+        raise ValueError('profElement requires elemName but none specified')
+    if 'elemEnumFlag' not in settings:
+        raise ValueError('profElement requires elemEnumFlag but none specified')
+        
+    # Normally I'd treat only those XML elements where elemEnumFlag as T as possibly producing
+    # unspecified or enumerated, and F producing *only* ranged elements.  But PDS profile
+    # servers are producing profile elements with F for elemEnumFlag and yet NO elemMinValue
+    # and elemMaxValue.  If they're using the Java profile code, I'd call that a bug in that
+    # code.  If they're not, then I'd say they're producing profiles incorrectly.
+    if settings['elemEnumFlag'] == 'T':
+        if len(values) == 0:
+            return UnspecifiedProfileElement(settings['elemName'], settings['elemDesc'], settings['elemType'],
+                settings['elemUnit'], syns, settings['elemComment'])
+        else:
+            return EnumeratedProfileElement(settings['elemName'], settings['elemDesc'], settings['elemType'],
+                settings['elemUnit'], syns, settings['elemComment'], values)
+    else:
+        if 'elemMinValue' not in settings or 'elemMaxValue' not in settings:
+            return UnspecifiedProfileElement(settings['elemName'], settings['elemDesc'], settings['elemType'],
+                settings['elemUnit'], syns, settings['elemComment'])
+        else:
+            return RangedProfileElement(settings['elemName'], settings['elemDesc'], settings['elemType'],
+            settings['elemUnit'], syns, settings['elemComment'], float(settings['elemMinValue']),
+            float(settings['elemMaxValue']))
+    
+
+# Some sample code:
+# if __name__ == '__main__':
+#   import urllib2, xml.dom.minidom
+#   x = urllib2.urlopen('http://starbrite.jpl.nasa.gov/q?object=urn%3Aeda%3Armi%3AJPL.PDS.MasterProd&type=profile&keywordQuery=TARGET_NAME+%3D+MARS')
+#   d = xml.dom.minidom.parse(x)
+#   x.close()
+#   profiles = []
+#   for i in d.documentElement.getElementsByTagName(u'profile'):
+#       profiles.append(Profile(node=i))
+#       
+#   doc = xml.dom.minidom.getDOMImplementation().createDocument(None, None, None)
+#   print '<?xml version="1.0" encoding="UTF-8"?>'
+#   print '<!DOCTYPE profiles PUBLIC "-//JPL//DTD Profile 1.1//EN"'
+#   print '  "http://oodt.jpl.nasa.gov/grid-profile/dtd/prof.dtd">'
+#   print '<profiles>'
+#   for profile in profiles:
+#       print profile.toXML(doc).toxml()
+#   print '</profiles>'
+#   

Added: incubator/oodt/agility/agile-oodt/trunk/oodt/query.py
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/oodt/query.py?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/oodt/query.py (added)
+++ incubator/oodt/agility/agile-oodt/trunk/oodt/query.py Sun Mar 21 22:08:01 2010
@@ -0,0 +1,456 @@
+# encoding: utf-8
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE.txt file distributed with
+# this work for additional information regarding copyright ownership.  The ASF
+# licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.    
+
+'''
+Agile OODT Query Expressions.
+
+Query expressions in OODT are based on the "DIS" style expressions originally
+developed for the Planetary Data System.  They consist of
+keyword/operator/literal-value triples, such as `targetName = Mars`, each
+linked with logical operators (and, or, not) and grouped with parentheses.
+For more information, see OODT_.
+
+This module defines classes that model the aspects of a query.  In general,
+use the `Query` class, passing a string containing your keyword expression as
+the first constructor argument.  From there, you have a `Query` object you can
+pass around to profile servers, product servers, and so forth.
+
+.. _OODT: http://incubator.apache.org/projects/oodt.html
+'''
+
+__docformat__ = 'restructuredtext'
+
+import oodterrors, shlex, xmlutils, xml.dom
+from xmlutils import DocumentableField
+
+class _QueryExpressionScanner(shlex.shlex):
+    '''Extend the shlex scanner but for the DIS-style query expressions we expect.
+    This means adding a dot to the characters that comprise a word so we can easily parse
+    floating-point numbers.  Also, there's no comment character.
+    '''
+    def __init__(self, str):
+        '''Create scanner.  `str` is the string to scan.
+        '''
+        shlex.shlex.__init__(self, str)
+        self.commenters = ''
+        self.wordchars = self.wordchars + '.-/:'
+        
+
+    def get_token(self):
+        '''Get the next token.  We strip quotes from strings, attach negative signs
+        to the numbers they're negating, and attach the = to <, >, and ! where needed.
+        '''
+        token = shlex.shlex.get_token(self)
+        if token == self.eof or token == None:
+            return None
+        if token[0] in self.quotes:
+            token = token[1:-1]
+        elif token in ('<', '>', '!'):
+            next = shlex.shlex.get_token(self)
+            if next == self.eof or next == None:
+                return None
+            elif next == '=':
+                token = token + next
+            else:
+                self.push_token(next)
+        return token
+    
+
+class QueryException(oodterrors.OODTException):
+    '''Exceptions related to query expression or query services
+    '''
+    pass
+    
+
+class ExpressionParseError(QueryException):
+    '''Error in parsing a query expression.
+    '''
+    pass
+    
+
+class QueryElement(xmlutils.Documentable):
+    '''An element of a query.
+    '''
+    def __init__(self, role='UNKNOWN', value='UNKNOWN', node=None):
+        '''Create a QueryElement.  You can provide role and value settings, or provide an XML
+        DOM node which will be parsed for role/value.
+        '''
+        self.role, self.value = role, value
+        if node != None:
+            self.parse(node)
+
+    def getDocumentElementName(self):
+        '''Give the XML tag name: `queryElement`.
+        '''
+        return 'queryElement'
+    
+    def getDocumentableFields(self):
+        '''Get the attributes that go into XML.
+        '''
+        return (DocumentableField('role', u'tokenRole', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('value', u'tokenValue', DocumentableField.SINGLE_VALUE_KIND))
+    
+    def __repr__(self):
+        return 'QueryElement(role="%s",value="%s")' % (self.role, self.value)
+    
+
+class QueryHeader(xmlutils.Documentable):
+    '''Header of a query.  Captures metadata like data dictionary in use, etc.
+    '''
+    def __init__(self, id='UNKNOWN', title='UNKNOWN', desc='UNKNOWN', type='QUERY', status='ACTIVE', security='UNKNOWN',
+        rev='2005-10-01 SCK v0.0.0 Under Development', dataDict='UNKNOWN', node=None):
+        '''Initialize a QueryHeader.  Provide id, title, desc, type, status, security, rev,
+        and dataDict settings.  Or, provide just an XML DOM node to be parsed.
+        '''
+        self.id, self.title, self.desc, self.type, self.status, self.security, self.rev, self.dataDict = \
+            id, title, desc, type, status, security, rev, dataDict
+        if node != None:
+            self.parse(node)
+    
+    def getDocumentElementName(self):
+        '''Give the XML tag name: `queryAttributes`.
+        '''
+        return 'queryAttributes'
+    
+    def getDocumentableFields(self):
+        '''Get the attributes that go into XML.
+        '''
+        return (DocumentableField('id', u'queryId', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('title', u'queryTitle', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('desc', u'queryDesc', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('type', u'queryType', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('status', u'queryStatusId', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('security', u'querySecurityType', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('rev', u'queryRevisionNote', DocumentableField.SINGLE_VALUE_KIND),
+            DocumentableField('dataDict', u'queryDataDictId', DocumentableField.SINGLE_VALUE_KIND))
+    
+    def __repr__(self):
+        return 'QueryHeader(id="%s",title="%s",desc="%s",type="%s",status="%s",security="%s",rev="%s",dataDict="%s")' % (
+            self.id, self.title, self.desc, self.type, self.status, self.security, self.rev, self.dataDict
+        )
+    
+    def __cmp__(self, other):
+        return cmp((self.id, self.title, self.desc, self.type, self.status, self.security, self.rev, self.dataDict),
+               (other.id, other.title, other.desc, other.type, other.status, other.security, other.rev, other.dataDict))
+    
+
+class QueryResult(object):
+    '''Result of a query.
+    '''
+    def __init__(self, results=[], node=None):
+        '''Results of a query are captured as a sequence of generic objects.
+        '''
+        self.results = results
+        if node != None: self.parse(node)
+    
+    def parse(self, node):
+        '''Initialize this object from the given XML DOM `node`.
+        '''
+        if 'queryResultSet' != node.nodeName:
+            raise ValueError('Expected queryResultSet but got "%s"' % node.nodeName)
+        for child in node.childNodes:
+            if child.nodeType == xml.dom.Node.ELEMENT_NODE and 'resultElement' == child.nodeName:
+                self.results.append(Result(node=child))
+    
+    def toXML(self, owner):
+        '''Convert this object into XML owned by the given `owner` document.
+        '''
+        root = owner.createElement('queryResultSet')
+        for result in self.results:
+            root.appendChild(result.toXML(owner))
+        return root
+    
+    def clear(self):
+        '''Remove all results.
+        '''
+        self.results = []
+    
+    def __getitem__(self, i):
+        return self.results[i]
+    
+    def __len__(self):
+        return len(self.results)
+    
+    def __cmp__(self, other):
+        return cmp(self.results, other.results)
+    
+
+_RELOPS = {
+    'LT':      'LT', 
+    'lt':      'LT', 
+    '<':       'LT',
+    'LE':      'LE',
+    'le':      'LE',
+    '<=':      'LE',
+    'EQ':      'EQ', 
+    'eq':      'EQ', 
+    '=':       'EQ',
+    'GE':      'GE',
+    'ge':      'GE',
+    '>=':      'GE',
+    'GT':      'GT',
+    'gt':      'GT',
+    '>':       'GT',
+    'NE':      'NE',
+    'ne':      'NE',
+    '!=':      'NE',
+    'LIKE':    'LIKE',
+    'like':    'LIKE',
+    'NOTLIKE': 'NOTLIKE',
+    'notlike': 'NOTLIKE',
+    'notLike': 'NOTLIKE',
+    'IS':      'IS',
+    'is':      'is',
+    'ISNOT':   'ISNOT',
+    'isnot':   'isnot',
+    'isNot':   'isnot'
+}
+
+_LOGOPS = {
+    'AND': 'AND',
+    'and': 'AND',
+    '&':   'AND',
+    'OR':  'OR',
+    'or':  'OR',
+    '|':   'OR',
+    'NOT': 'NOT',
+    'not': 'NOT',
+    '!':   'NOT'
+}
+
+_PRECEDENCE = {
+    'NOT': 2,
+    'AND': 1,
+    'OR': 0,
+}
+
+class Query(object):
+    '''Query.  In old OODT, this was called XMLQuery, even though XML was tagential to it.
+    Captures aspects of a query, including header, results, and so forth.  Most importantly, it
+    captures the query expression, which contains the constraints on user's desiderata and
+    range on what to return.
+    
+    As with other classes in this module, you can provide an XML DOM node to be parsed
+    for the query's settings, or provide each of them individually.
+    '''
+    def __init__(self, keywordQuery=None, header=QueryHeader(), resultModeID='ATTRIBUTE', propType='BROADCAST',
+        propLevels='N/A', maxResults=1, mimeAccept=[], parseQuery=True, node=None):
+        '''Initialize a query.  Usually you provide just the `keywordQuery` argument
+        which should be a keyword/query expression in the DIS-style; or you provide
+        the `node` which is an XML DOM node describing a query.
+        '''
+        self.header, self.resultModeID, self.propType, self.propLevels, self.maxResults, self.mimeAccept = \
+            header, resultModeID, propType, propLevels, maxResults, mimeAccept
+        self.wheres, self.selects, self.froms, self.resultSet = [], [], [], QueryResult()
+        if keywordQuery != None:
+            self.keywordQuery = keywordQuery
+            if parseQuery:
+                self.wheres, self.selects = _parseQuery(keywordQuery)
+        else:
+            self.keywordQuery = ''
+        if node != None:
+            self.parse(node)
+        
+    def toXML(self, owner):
+        '''Yield this query as an XML DOM.
+        '''
+        query = owner.createElement('query')
+        query.appendChild(self.header.toXML(owner))
+        xmlutils.add(query, u'queryResultModeId', self.resultModeID)
+        xmlutils.add(query, u'queryPropogationType', self.propType)
+        xmlutils.add(query, u'queryPropogationLevels', self.propLevels)
+        for mimeType in self.mimeAccept:
+            xmlutils.add(query, u'queryMimeAccept', mimeType)
+        xmlutils.add(query, u'queryMaxResults', str(self.maxResults))
+        xmlutils.add(query, u'queryKWQString', self.keywordQuery)
+        selects = owner.createElement(u'querySelectSet')
+        query.appendChild(selects)
+        for select in self.selects:
+            selects.appendChild(select.toXML(owner))
+        fromElement = owner.createElement(u'queryFromSet')
+        query.appendChild(fromElement)
+        for i in self.froms:
+            fromElement.appendChild(i.toXML(owner))
+        wheres = owner.createElement(u'queryWhereSet')
+        query.appendChild(wheres)
+        for where in self.wheres:
+            wheres.appendChild(where.toXML(owner))
+        query.appendChild(self.resultSet.toXML(owner))
+        return query
+        
+    def parse(self, node):
+        '''Parse the XML DOM node as a query document.
+        '''
+        if 'query' != node.nodeName:
+            raise ValueError('Expected query but got "%s"' % node.nodeName)
+        self.mimeAccept, self.results = [], []
+        for child in node.childNodes:
+            if child.nodeType == xml.dom.Node.ELEMENT_NODE:
+                if child.nodeName == u'queryAttributes':
+                    self.header = QueryHeader(node=child)
+                elif child.nodeName == u'resultModeID':
+                    self.resultModeID = xmlutils.text(child)
+                elif child.nodeName == u'queryPropogationType':
+                    self.propType = xmlutils.text(child)
+                elif child.nodeName == u'queryPropogationLevels':
+                    self.propLevels = xmlutils.text(child)
+                elif child.nodeName == u'queryMimeAccept':
+                    self.mimeAccept.append(xmlutils.text(child))
+                elif child.nodeName == u'queryMaxResults':
+                    self.maxResults = int(xmlutils.text(child))
+                elif child.nodeName == u'queryKWQString':
+                    self.keywordQuery = xmlutils.text(child)
+                elif child.nodeName == u'querySelectSet':
+                    self.selects = _parseQueryElements(child)
+                elif child.nodeName == u'queryFromSet':
+                    self.froms = _parseQueryElements(child)
+                elif child.nodeName == u'queryWhereSet':
+                    self.wheres = _parseQueryElements(child)
+                elif child.nodeName == u'queryResultSet':
+                    self.resultSet = QueryResult(node=child)
+    
+    def __cmp__(self, other):
+        header = cmp(self.header, other.header)
+        if header < 0:
+            return -1
+        elif header == 0:
+            resultModeID = cmp(self.resultModeID, other.resultModeID)
+            if resultModeID < 0:
+                return -1
+            elif resultModeID == 0:
+                propType = cmp(self.propType, other.propType)
+                if propType < 0:
+                    return -1
+                elif propType == 0:
+                    propLevels = cmp(self.propLevels, other.propLevels)
+                    if propLevels < 0:
+                        return -1
+                    elif propLevels == 0:
+                        maxResults = self.maxResults - other.maxResults
+                        if maxResults < 0:
+                            return -1
+                        elif maxResults == 0:
+                            mimeAccept = cmp(self.mimeAccept, other.mimeAccept)
+                            if mimeAccept < 0:
+                                return -1
+                            elif mimeAccept == 0:
+                                selects = cmp(self.selects, other.selects)
+                                if selects < 0:
+                                    return -1
+                                elif selects == 0:
+                                    froms = cmp(self.froms, other.froms)
+                                    if froms < 0:
+                                        return -1
+                                    elif froms == 0:
+                                        wheres = cmp(self.wheres, other.wheres)
+                                        if wheres < 0:
+                                            return -1
+                                        elif wheres == 0:
+                                            return cmp(self.resultSet, other.resultSet)
+        return 1
+    
+
+def _parseQueryElements(node):
+    '''The children of the given XML DOM node are a sequence of queryElements.  Parse them
+    and return a list of QueryElement objects.
+    '''
+    a = []
+    for child in node.childNodes:
+        if child.nodeType == xml.dom.Node.ELEMENT_NODE:
+            a.append(QueryElement(node=child))
+    return a
+    
+
+def _parseQuery(s):
+    '''Parse the query expression in `s`.
+    '''
+    if s is None:
+        return [], []
+    if len(s.strip()) == 0:
+        return [], []
+    if s.count('(') != s.count(')'):
+        raise ExpressionParseError('Unbalanced parentheses')
+    scanner = _QueryExpressionScanner(s)
+    return _buildQuery(scanner)
+    
+
+def _buildQuery(scanner):
+    '''Build the query stacks using the given `scanner`.
+    '''
+    operators, expression, selectors = [], [], []
+    while True:
+        token = scanner.get_token()
+        if token is None: break
+        if token in _LOGOPS:
+            op = QueryElement('LOGOP', _LOGOPS[token])
+            if len(operators) == 0:
+                operators.append(op)
+            else:
+                while len(operators) > 0 and _PRECEDENCE[operators[-1].value] > _PRECEDENCE[op.value]:
+                    expression.append(operators.pop())
+                operators.append(op)
+        elif token == '(':
+            subExpr, subSelectors = _buildQuery(scanner)
+            expression.extend(subExpr)
+            selectors.extend(subSelectors)
+        elif token == ')':
+            break
+        else:
+            _addTerm(token, scanner, expression, operators, selectors)
+    if len(operators) > 0 and len(expression) == 0:
+        raise ExpressionParseError('Query contains only logical operators')
+    operators.reverse()
+    expression.extend(operators)
+    return expression, selectors
+    
+
+def _addTerm(elemName, scanner, expression, operators, selectors):
+    '''Add a term to the correct stack.
+    '''
+    relop = scanner.get_token()
+    if relop is None:
+        raise ExpressionParseError('Expected relational operator after element name "%s"' % elemName)
+    if relop not in _RELOPS:
+        raise ExpressionParseError('Unknown relational operator "%s"' % relop)
+    literal = scanner.get_token()
+    if literal is None:
+        raise ExpressionParseError('Expected literal value for "%s %s" comparison' % (elemName, relop))
+    if elemName == 'RETURN':
+        selectors.append(QueryElement('elemName', literal))
+        if len(operators) > 0:
+            operators.pop()
+        else:
+            scanner.get_token()
+    else:
+        expression.append(QueryElement('elemName', elemName))
+        expression.append(QueryElement('LITERAL', literal))
+        expression.append(QueryElement('RELOP', _RELOPS[relop]))
+    
+
+# Sample code:
+# if __name__ == '__main__':
+#   import urllib, xml.dom.minidom
+# 
+#   impl = xml.dom.minidom.getDOMImplementation()
+#   doc = impl.createDocument(None, None, None) # nsURI, qName, docType
+# 
+#   q = Query('track = Innocente')
+#   node = q.toXML(doc)
+#   doc.appendChild(node)
+#   q = doc.toxml()
+#   f = urllib.urlopen('http://localhost:8080/pds/prof', urllib.urlencode(dict(xmlq=q)), {}) # url, postdata, proxies (none)
+#   print f.read()

Propchange: incubator/oodt/agility/agile-oodt/trunk/oodt/tests/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Sun Mar 21 22:08:01 2010
@@ -0,0 +1,8 @@
+._*
+.DS_Store
+*.log
+*.pyc
+*.pyo
+*.egg-info
+dist
+build

Added: incubator/oodt/agility/agile-oodt/trunk/oodt/tests/__init__.py
URL: http://svn.apache.org/viewvc/incubator/oodt/agility/agile-oodt/trunk/oodt/tests/__init__.py?rev=925913&view=auto
==============================================================================
--- incubator/oodt/agility/agile-oodt/trunk/oodt/tests/__init__.py (added)
+++ incubator/oodt/agility/agile-oodt/trunk/oodt/tests/__init__.py Sun Mar 21 22:08:01 2010
@@ -0,0 +1,38 @@
+# encoding: utf-8
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE.txt file distributed with
+# this work for additional information regarding copyright ownership.  The ASF
+# licenses this file to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+# License for the specific language governing permissions and limitations
+# under the License.    
+
+'''Agile OODT Tests.
+'''
+
+__docformat__ = 'restructuredtext'
+
+import unittest
+import profileTest, queryTest, xmlutilsTest
+
+def test_suite():
+    '''Create the suite of tests.
+    '''
+    suite = unittest.TestSuite()
+    suite.addTest(profileTest.test_suite())
+    suite.addTest(queryTest.test_suite())
+    suite.addTest(xmlutilsTest.test_suite())
+    return suite
+    
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+