[z3-checkins] r24101 - in z3/jsonserver/trunk: . etc jsolait jsolait/doc jsolait/lib jsolait/libnew jsolait/libws jsolait/src tests

jwashin at codespeak.net jwashin at codespeak.net
Wed Mar 8 13:37:15 CET 2006


Author: jwashin
Date: Wed Mar  8 13:37:02 2006
New Revision: 24101

Added:
   z3/jsonserver/trunk/CHANGES.txt
   z3/jsonserver/trunk/DEPENDENCIES.cfg
   z3/jsonserver/trunk/README.txt
   z3/jsonserver/trunk/SETUP.cfg
   z3/jsonserver/trunk/VERSION.txt
   z3/jsonserver/trunk/ZopePublicLicense.txt
   z3/jsonserver/trunk/__init__.py
   z3/jsonserver/trunk/configure.zcml
   z3/jsonserver/trunk/etc/
   z3/jsonserver/trunk/etc/jsonserver-configure.zcml
   z3/jsonserver/trunk/etc/jsonserver-meta.zcml
   z3/jsonserver/trunk/ftesting.py
   z3/jsonserver/trunk/interfaces.py
   z3/jsonserver/trunk/jsolait/
   z3/jsonserver/trunk/jsolait/README.txt
   z3/jsonserver/trunk/jsolait/doc/
   z3/jsonserver/trunk/jsolait/install_jsolait.py
   z3/jsonserver/trunk/jsolait/lib/
   z3/jsonserver/trunk/jsolait/lib/pythonkw.js
   z3/jsonserver/trunk/jsolait/libnew/
   z3/jsonserver/trunk/jsolait/libws/
   z3/jsonserver/trunk/jsolait/src/
   z3/jsonserver/trunk/jsoncomponent.py
   z3/jsonserver/trunk/jsonrpc.py
   z3/jsonserver/trunk/jsonserver-configure.zcml
   z3/jsonserver/trunk/jsonserver-meta.zcml
   z3/jsonserver/trunk/jsonserver.e3p
   z3/jsonserver/trunk/jsonserver.e3t
   z3/jsonserver/trunk/jsontest.py   (contents, props changed)
   z3/jsonserver/trunk/meta.zcml
   z3/jsonserver/trunk/metaconfigure.py
   z3/jsonserver/trunk/minjson.py
   z3/jsonserver/trunk/requestpublicationfactory.py
   z3/jsonserver/trunk/tests/
   z3/jsonserver/trunk/tests/__init__.py
   z3/jsonserver/trunk/tests/jsonrpc.zcml
   z3/jsonserver/trunk/tests/jsonrpc_error.zcml
   z3/jsonserver/trunk/tests/jsonrpcviews.py
   z3/jsonserver/trunk/tests/test.pt
   z3/jsonserver/trunk/tests/test_directives.py
   z3/jsonserver/trunk/tests/test_httpfactory.py
   z3/jsonserver/trunk/tests/test_json.py
   z3/jsonserver/trunk/tests/test_jsonrpcpublication.py
   z3/jsonserver/trunk/tests/test_jsonrpcrequest.py
Log:
now adding the files


Added: z3/jsonserver/trunk/CHANGES.txt
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/CHANGES.txt	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,17 @@
+#CHANGES.txt
+
+20051202 a bit of backward-compatibility with five:  
+    ++resource++ notation for the javascript.  updated to the available 
+    jsolait (2005-11-15.small)
+
+20051125 some work toward making json requests better zope citizens.  
+Some work toward keyword parameter support.
+Some work toward making more information available in request: now 
+provides IBrowserApplicationRequest. This is experimental.  Let me 
+know what you think.
+
+20050921 Using the next beta of jsolait (20050914).  The init file is now
+ jsolait.js instead of init.js.  jsolait installer adds init.js as a copy of jsolait.js
+ for backwards compatibility.  We are now using the 3.1 ResourceDirectory,
+ so the jsolait file structure does not need to be flattened. Upgrade to this 
+ version using the installer is optional.  

Added: z3/jsonserver/trunk/DEPENDENCIES.cfg
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/DEPENDENCIES.cfg	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,21 @@
+jsonserver
+jsonserver.tests
+zope.app.component
+zope.app.component.tests
+zope.app.location
+zope.app.publication
+zope.app.publication.tests
+zope.app.publisher.xmlrpc
+zope.app.server
+zope.app.testing
+zope.app.zapi
+zope.component
+zope.component.tests
+zope.configuration
+zope.interface
+zope.proxy
+zope.publisher
+zope.publisher.interfaces
+zope.security
+zope.server.http
+zope.testing

Added: z3/jsonserver/trunk/README.txt
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/README.txt	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,254 @@
+==========
+jsonserver
+==========
+
+JSON is javascript object notation. JSON-RPC performs the same service
+as XML-RPC, except the transport is javascript objects instead of
+XML.
+
+jwashin 4 Jan 2006
+
+===================
+jsonserver Project:
+===================
+
+This project provides the additional functionality of listening and responding
+properly to requests of type "application/json-rpc".
+
+Dependencies:
+-------------
+
+This package will work with (unreleased) zope3 version 3.2 or greater, currently
+the development version available at svn://svn.zope.org/repos/main/Zope3/trunk
+
+jsolait from http://jsolait.net is the recommended client-side javascript
+library.  Installation of jsolait is covered in the README.txt file in this
+package's jsolait folder.
+
+Installation:
+-------------
+Install this package in a location accessible to your zope3 instance. The
+lib/python folder of the instance is a good choice.
+
+The files in the etc folder should go into etc/package-includes.
+
+A README.txt file in the jsolait folder has instructions for installing
+a client javascript library.
+
+Usage:
+------
+Similar to xmlrpc usage.
+
+jsonserver looks for content-type "application/json-rpc", and handles those 
+requests as JSON-RPC.  Other http requests are not affected and will 
+presumably work as expected.
+
+jsonrpc provides another namespace,
+'http://namespaces.zope.org/jsonrpc' for zcml configuration.
+Statements like
+
+::
+
+<jsonrpc:view for="" permission="" methods="" class="" />
+
+are used in zcml to make jsonrpc methods viewable.
+
+You may create views that appear only if a jsonrpc listener is installed:
+
+::
+
+  <configure zcml:condition="have jsonrpc">
+    <jsonrpc:view
+        for="someInterface"
+        permission="zope.View"
+        methods="blah blecht"
+        class=".views.JsonViewClass"
+        />
+  </configure>
+
+To make a view class, subclass
+jsonserver.jsonrpc.MethodPublisher like this:
+
+::
+
+    from jsonserver.jsonrpc import MethodPublisher
+    class MyClass(MethodPublisher):
+        def myOutput(self):
+            blah = 'something cool'
+            return blah
+        def myOutput1(self,param1):
+            blecht = self.context.something(param1)
+            return blecht
+
+where the return value can be a python simple object
+(int, long, float, string, etc.) or list or dictionary composed of
+simple objects, lists, and/or dictionaries.  Composite built-ins
+like complex numbers, dates, or classes are not currently
+supported.  Decompose those, and send a list or dictionary instead.
+Multiple returned values will be marshalled into a list.
+
+For web pages, you will need to include a javascript library for the client side
+in your page template:
+
+::
+
+    <script type="text/javascript" src="/++resource++jsolait/jsolait.js"></script>
+
+will bring in the recommended jsolait library, if it is installed here.  The following javascript examples
+are for jsolait, but any similar javascript library may be used, or you can write your own.  The 
+xmlHTTPRequest POST must set a content-type of "application/json-rpc" for this package to invoke
+json-rpc requests on the server.
+
+From your client javascript code, import the jsonrpc module:
+
+::
+
+    var jsonrpc = imprt('jsonrpc');
+
+Then, make a jsolait connection proxy ("." often works fine
+for addr):
+
+::
+
+    addr="address to server object providing jsonrpc view class";
+    //for better error handling, see http://jsolait.net/wiki/documentation
+    try{var aServer = new jsonrpc.ServiceProxy(addr, ["myOutput"]);
+        }catch(e){alert(e);}
+
+then, for async communication, provide a callback function:
+
+::
+
+    function doThis(resp,err){
+    if (!err) {do something with resp} else {do something with err}
+    }
+
+and call the method:
+
+::
+
+    aServer.myOutput(aparam,doThis);
+
+If you want sync communication, call the method without
+the name of a function as the last parameter.
+
+For communication other than in a web browser (javascript), minjson.py
+or other json implementations have functions for reading and writing
+JSON objects.
+
+The text of a JSON-RPC request looks like:
+
+::
+
+    {"id":jsonid,"method":remotemethod,"params":methodparams}
+
+where
+
+- jsonid is a string or number that may identify this specific request
+- remotemethod is the method to call on the server
+- methodparams is a list (javascript Array) of parameters to the method
+
+The text of a JSON-RPC response looks like:
+
+::
+
+    {"id":jsonid,"result":returnedresult,"error":returnederr}
+
+where
+
+- jsonid is the same jsonid as sent in the request
+- returnedresult is a javascript representation of the result or null
+- returnederr is a javascript representation of an error state
+
+Either returnedresult or returnederr will be the javascript null value.
+
+Actual implementation using e.g., urllib is left as an exercise for the
+reader. Hint:  Use the minjson.write(object) and minjson.read(string)
+methods for conversion before and after transport.
+
+Page Templates, Form Variables, and Named Parameters:
+_______________________________________________________
+
+jsonserver will work with page templates and similar
+snippets of HTML.  Most registered views (browser:page or similar)
+are also accessible to json-rpc clients.  The simplest way to use a 
+page template is to call it in javascript just as you would call a 
+jsonrpc:view.  jsonserver sets a request variable, JSONRPC_MODE, 
+which will be True if a template is requested through json-rpc.  
+This may be useful if you need json-rpc-specific behavior.
+
+If you need form data, jsonserver has a special facility for this.  The
+contents of any client object (dict) passed as a parameter to json-rpc 
+that is (cleverly) named "pythonKwMaRkEr" will be available in the request 
+as items in request.form.  If you call methods with named parameters, 
+those items also will replace the named parameters as appropriate.
+
+A pythonkw module is provided here for use with jsolait on the client side. 
+Code like
+
+::
+
+  var pythonkw = imprt("pythonkw");
+  var kwparams = new pythonkw.PythonKw({'parm1': 'aaa', 'parm2': text_value})
+  var result = aServer.my_portlet(kwparams);
+
+will do the marshalling so you do not have to type "pythonKwMaRkEr".
+
+Here is an example of using a page template through a
+jsonrpc:view method ( ViewPageTemplateFile is in zope.app.pagetemplate)
+
+::
+
+    def my_portlet(self,parm1='bbb',parm2=None):
+        date = datetime.now()
+        rand = random.randint(0,2000)
+        portlet = ViewPageTemplateFile("my_portlet.pt")
+        return portlet(self,date=date,random=rand,parm1=parm1)
+
+In the above example, parm1 is available to the template as options/parm1
+and as request/form/parm1.  parm2 may be available to the template as 
+request/form/parm2 if provided in the request.
+        
+Debugging:
+__________
+
+To get verbose output of requests, responses, and errors,
+set level DEBUG for your event log in etc/zope.conf for your
+instance. e.g.,
+
+::
+
+    <eventlog>
+     level DEBUG
+      <logfile>
+        path $LOGDIR/z3.log
+      </logfile>
+      <logfile>
+        path STDOUT
+      </logfile>
+    </eventlog>
+
+You can get pretty much the same results with tcpwatch, except you get the 
+entire request and response with tcpwatch.
+    
+Compatibility:
+______________
+
+Most compatibility issues should  be about client implementations.  
+
+jsonserver will accept any valid JSON-RPC request that is a POST with 
+content-type "application/json-rpc".  Output responses will be of 
+content-type "application/x-javascript" so that browser clients can know
+that the response will be interpreted in javascript.
+
+jsolait should work on any current browser with enabled javascript and
+a functioning xmlHTTPRequest POST implementation.  This includes most
+gecko browsers (Firefox, Mozilla, and Netscape 6.1+), khtml browsers (Safari and
+konqueror), recent IEs, and Opera 8.1+.  If it will do Google maps, it probably
+will do jsolait.
+
+Unicode Support:
+________________
+
+jsonserver supports unicode properly now, I think, (maybe?).  If you have a 
+project that depends on unicode, let me know if this does anything unexpected.

Added: z3/jsonserver/trunk/SETUP.cfg
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/SETUP.cfg	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,5 @@
+# Tell zpkg how to install the ZCML slugs.
+
+<data-files zopeskel/etc/package-includes>
+  jsonserver-*.zcml
+</data-files>

Added: z3/jsonserver/trunk/VERSION.txt
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/VERSION.txt	Wed Mar  8 13:37:02 2006
@@ -0,0 +1 @@
+jsonserver 3.2.2 svn

Added: z3/jsonserver/trunk/ZopePublicLicense.txt
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/ZopePublicLicense.txt	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,54 @@
+Zope Public License (ZPL) Version 2.1
+-------------------------------------
+
+A copyright notice accompanies this license document that
+identifies the copyright holders.
+
+This license has been certified as open source. It has also
+been designated as GPL compatible by the Free Software
+Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions in source code must retain the
+   accompanying copyright notice, this list of conditions,
+   and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the accompanying
+   copyright notice, this list of conditions, and the
+   following disclaimer in the documentation and/or other
+   materials provided with the distribution.
+
+3. Names of the copyright holders must not be used to
+   endorse or promote products derived from this software
+   without prior written permission from the copyright
+   holders.
+
+4. The right to distribute this software or to use it for
+   any purpose does not give you the right to use
+   Servicemarks (sm) or Trademarks (tm) of the copyright
+   holders. Use of them is covered by separate agreement
+   with the copyright holders.
+
+5. If any files are modified, you must cause the modified
+   files to carry prominent notices stating that you changed
+   the files and the date of any change.
+
+Disclaimer
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
+  AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
+  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+  NO EVENT SHALL THE COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+  DAMAGE.

Added: z3/jsonserver/trunk/__init__.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/__init__.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1 @@
+#python package

Added: z3/jsonserver/trunk/configure.zcml
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/configure.zcml	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,86 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:jsonrpc="http://namespaces.zope.org/jsonrpc"
+           xmlns:browser="http://namespaces.zope.org/browser"
+           xmlns:help="http://namespaces.zope.org/help"
+           i18n_domain="zope"
+           >
+
+  <publisher
+    name="JSONRPC"
+    factory=".requestpublicationfactory.JSONRPCFactory"
+    methods="POST"
+    mimetypes="application/json-rpc"
+    priority="25"
+    />
+    
+  <utility
+      factory=".jsoncomponent.JSONReader"
+      provides=".interfaces.IJSONReader" 
+    />
+    
+  <utility
+      factory=".jsoncomponent.JSONWriter"
+      provides=".interfaces.IJSONWriter" 
+    />
+  
+  <view
+        for="zope.interface.Interface"
+        type=".interfaces.IJSONRPCRequest"
+        provides=".interfaces.IJSONRPCPublisher"
+        factory="zope.app.publication.traversers.SimpleComponentTraverser"
+        permission="zope.Public"
+        />
+
+  <view
+        for="zope.app.container.interfaces.IItemContainer"
+        type=".interfaces.IJSONRPCRequest"
+        provides=".interfaces.IJSONRPCPublisher"
+        factory="zope.app.container.traversal.ItemTraverser"
+        permission="zope.Public"
+        />
+
+  <view
+        for="zope.app.container.interfaces.IReadContainer"
+        type=".interfaces.IJSONRPCRequest"
+        provides=".interfaces.IJSONRPCPublisher"
+        factory="zope.app.container.traversal.ContainerTraverser"
+        permission="zope.Public"
+        />
+
+  <view
+        for=".interfaces.IMethodPublisher"
+        type=".interfaces.IJSONRPCRequest"
+        provides=".interfaces.IJSONRPCPublisher"
+        factory=".jsonrpc.MethodTraverser"
+        permission="zope.Public"
+        />
+        
+  <!--register a default view for errors, but we do not create the named
+  view, so errors from IException are handled by the publisher.-->
+  <defaultView
+    for="zope.interface.common.interfaces.IException"
+    type=".interfaces.IJSONRPCRequest"
+    name="jsonrpcerror.html"
+        />
+        
+    <browser:resourceDirectory
+           name="jsolait"
+           directory="./jsolait"
+  />
+  <browser:resourceDirectory
+           name="jsolaitlib"
+           directory="./jsolait/lib"
+  />
+  <browser:resourceDirectory
+           name="jsolaitlibws"
+           directory="./jsolait/libws"
+  />
+  
+  <help:register
+      id="json_rpc"
+      title="JSON RPC"
+      doc_path="README.txt"
+      class="zope.app.onlinehelp.onlinehelptopic.RESTOnlineHelpTopic"
+  />
+  
+</configure>

Added: z3/jsonserver/trunk/etc/jsonserver-configure.zcml
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/etc/jsonserver-configure.zcml	Wed Mar  8 13:37:02 2006
@@ -0,0 +1 @@
+<include package="jsonserver"/>

Added: z3/jsonserver/trunk/etc/jsonserver-meta.zcml
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/etc/jsonserver-meta.zcml	Wed Mar  8 13:37:02 2006
@@ -0,0 +1 @@
+<include package="jsonserver" file="meta.zcml" />

Added: z3/jsonserver/trunk/ftesting.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/ftesting.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,48 @@
+import zope.app.testing.functional as functional
+from zope.testing import doctest
+from jsonserver.jsonrpc import JSONRPCRequest, JSONRPCPublication
+ 
+class HTTPCaller(functional.HTTPCaller):
+    def chooseRequestClass(self, method, path, environment):
+        """Choose and return a request class and a publication class"""
+        request_cls, publication_cls = super(HTTPCaller, self).chooseRequestClass(method, path, environment)
+        
+        content_type = environment.get('CONTENT_TYPE', '')
+        is_json = content_type.startswith('application/json-rpc')
+    
+        if method in ('GET', 'POST', 'HEAD'):
+            if (method == 'POST' and is_json):
+                request_cls = JSONRPCRequest
+                publication_cls = JSONRPCPublication
+    
+        return request_cls, publication_cls
+    
+def FunctionalDocFileSuite(*paths, **kw):
+    globs = kw.setdefault('globs', {})
+    globs['http'] = HTTPCaller()
+    globs['getRootFolder'] = functional.getRootFolder
+    globs['sync'] = functional.sync
+
+    kw['package'] = doctest._normalize_module(kw.get('package'))
+
+    kwsetUp = kw.get('setUp')
+    def setUp(test):
+        functional.FunctionalTestSetup().setUp()
+
+        if kwsetUp is not None:
+            kwsetUp(test)
+    kw['setUp'] = setUp
+
+    kwtearDown = kw.get('tearDown')
+    def tearDown(test):
+        if kwtearDown is not None:
+            kwtearDown(test)
+        functional.FunctionalTestSetup().tearDown()
+    kw['tearDown'] = tearDown
+
+    if 'optionflags' not in kw:
+        kw['optionflags'] = (doctest.ELLIPSIS
+                             | doctest.REPORT_NDIFF
+                             | doctest.NORMALIZE_WHITESPACE)
+
+    return doctest.DocFileSuite(*paths, **kw)

Added: z3/jsonserver/trunk/interfaces.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/interfaces.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors. 
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+# adapted from various xmlrpc interface files jwashin 2005-06-26
+
+#2005-08-16 A few changes needed after a zope3 trunk change
+#2005-11-07 Allowed IDefaultBrowserLayer in JSONRPCRequest.  This permits skin
+#           lookups
+
+from zope.publisher.interfaces import IPublishTraverse
+from zope.publisher.interfaces.http import IHTTPApplicationRequest,\
+        IHTTPCredentials
+from zope.interface import Interface
+from zope.component.interfaces import IView, IPresentation
+from zope.interface import Attribute
+from zope.app.publisher.xmlrpc import IMethodPublisher
+from zope.publisher.interfaces.xmlrpc import IXMLRPCPublication
+from zope.app.publication.interfaces import IRequestFactory
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+
+class IJSONRPCRequestFactory(IRequestFactory):
+    """Browser request factory"""
+
+class IJSONRPCPublisher(IPublishTraverse):
+    """JSON-RPC Publisher
+    like zope.publisher.interfaces.xmlrpc.IXMLRPCPublisher
+    """
+class IJSONRPCPublication(IXMLRPCPublication):
+    """Object publication framework.
+    like zope.publisher.interfaces.xmlrpc.IXMLRPCPublication
+    """
+class IJSONRPCRequest(IHTTPApplicationRequest, IHTTPCredentials, IDefaultBrowserLayer):
+    """JSON-RPC Request
+    like zope.publisher.interfaces.xmlrpc.IXMLRPCRequest
+    """
+    jsonID=Attribute("""JSON-RPC ID for the request""")
+
+class IJSONReader(Interface):
+    def read(aString):
+        """read and interpret a string in JSON as python"""
+        
+class IJSONWriter(Interface):
+    def write(anObject, encoding=None):
+        """return a JSON unicode string representation of a python object
+           Encode if encoding is provided.
+        """
+
+class IJSON(IJSONReader,IJSONWriter):
+    """read and write JSON"""
+
+#class IMethodPublisher(Interface):
+#
+#    """Marker interface for an object that wants to publish methods
+#    see zope.app.publisher.xmlrpc.IMethodPublisher
+#
+#    it's commented here for completeness; actually, this uses
+#    the one in zope.app.publisher.xmlrpc
+#    """
+
+class IJSONRPCPresentation(IPresentation):
+    """JSONRPC Presentation
+    like zope.app.publisher.interfaces.xmlrpc.IXMLRPCPresentation
+    """
+
+class IJSONRPCView(IJSONRPCPresentation,IView):
+    """JSONRPC View
+    like zope.app.publisher.interfaces.xmlrpc.IXMLRPCView
+    """

Added: z3/jsonserver/trunk/jsolait/README.txt
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/jsolait/README.txt	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,28 @@
+This is a place for the jsolait javascript library from 
+
+ http://jsolait.net/
+ 
+It will be accessible as a resource , '/@@/jsolait/...'
+ 
+Manual installation is something like:
+
+get the library
+extract it here
+fix the paths in jsolait.js so that all .js files are accessible from the 
+/@@/jsolait resourceDirectory, i.e., this folder
+fix the submodule GET so that things still work after page refresh on konqueror and safari 
+    (just add a ?s="[timestamp]" so the file gets refetched instead of mouldering in cache)
+fix content-types in jsonrpc.js from text/plain or text/xml to text/x-json
+
+There is a script here that does the above for you. It's small, and
+you probably want to read it so that it cannot suprise you.  After 
+viewing it, use it from this directory:
+
+python install_jsolait.py
+
+The installer uses urllib and writes files in this directory, so expect
+it to fail if you do not have write privileges or an accessible internet 
+connection.
+ 
+Jim Washington
+21 Sep 2005

Added: z3/jsonserver/trunk/jsolait/install_jsolait.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/jsolait/install_jsolait.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,154 @@
+##############################################################################
+#
+# Copyright (c) 2005 Jim Washington and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+# jsolait installer
+#
+#
+# jsolait is available on the net, licensed LGPL. When you run this
+# script, you will download, unzip, and make appropriate changes to
+# the jsolait library to make it usable with jsonserver.
+# jwashin 2005-05-16
+# 2005-09-21 Updated for 20050914 beta of jsolait
+
+import os, md5, urllib
+
+#get the current, working version of jsolait
+#this one (a beta of 1.1) works as of now (3 Jun 05)
+loc = 'http://jsolait.net/download/'
+
+filename = 'jsolait.2005-11-15.small.zip'
+#md5sum = '69014dddf37fb7cb7b88b5eac84d1569'
+#md5sum = '475b7a505d6ab911b25818831244bd43'
+md5sum = 'c21c32a7a8756a35a0e48a30b710b3e1'
+fileurl = loc + filename
+
+#Assure the folders are there.
+for directory in ['src','doc','lib','libws']:
+    if not os.path.exists(directory):
+        os.mkdir(directory)
+
+zippedfile = os.path.join('src',filename)
+
+#get file unless it is already there
+if not os.path.exists(zippedfile):
+    print "retrieving %s" % fileurl
+    urllib.urlretrieve(fileurl,zippedfile)
+else:
+    print "%s exists; reprocessing. " % zippedfile
+    print "to retrieve again, delete or rename %s." % zippedfile
+
+#check file signature
+print "checking md5"
+filedata = file(zippedfile,'r').read()
+check = md5.new(filedata).hexdigest()
+if not check == md5sum:
+    raise ValueError('md5 sums do not match')
+
+#got the file; now, unzip it.
+
+import zipfile
+
+print "unzipping %s" % zippedfile
+
+zip = zipfile.ZipFile(zippedfile,'r')
+
+filesList = zip.namelist()
+
+        
+#put the files in the folders
+for k in filesList:
+    if not k.endswith('/'):
+        f = os.path.split(k)
+        #print f
+        if 'doc' in f[0]:
+            file(os.path.join('doc',f[1]),'wb').write(zip.read(k))
+        elif 'libws' in f[0]:
+            file(os.path.join('libws',f[1]),'wb').write(zip.read(k))
+        elif 'lib' in f[0]:
+            file(os.path.join('lib',f[1]),'wb').write(zip.read(k))
+        else:
+            file(f[-1],'wb').write(zip.read(k))
+
+# patch to modify paths in init.js
+# init.js is no more, replaced by jsolait.js, so fix those paths
+
+linesep = os.linesep
+mfile = 'jsolait.js'
+print "patching %s" % mfile
+t = file(mfile,'U')
+lines = t.readlines()
+t.close()
+t = file(mfile,'w')
+moda = False
+modb = False
+lineadded = False
+for k in lines:
+    d = k.rstrip()
+    #set the installPath to the resource directory name
+    if d.find('mod.baseURI="./jsolait"') >= 0:
+        s = 'mod.baseURI = "/++resource++jsolait";'
+    #this one works around some javascript clients not using js if it's already in cache
+    #OK, we load the js each time it's used, but it still works if you refresh the page
+    #in konqueror or safari.
+    #elif d.find('xmlhttp.open("GET", url, false);') >= 0:
+    #    s = 'var d = new Date();\nxmlhttp.open("GET", url +"?m="+d.getTime(), false);'
+    #elif d.find('xmlhttp.open("GET", uri, false);') >= 0:
+    #    s = 'var d = new Date();\nxmlhttp.open("GET", uri +"?m="+d.getTime(), false);'
+    #elif d.find('xmlhttp.open("GET",uri,false);') >= 0:
+    #    s = 'var d=new Date();%sxmlhttp.open("GET",uri +"?m="+d.getTime(),false);' % linesep
+    elif d.find('baseURI)s') > 0:
+        s = d.replace('/','',1)
+        if lineadded == False:
+            #add a line for pythonkw
+            s = s + linesep + 'pythonkw:"%(baseURI)slib/pythonkw.js",'
+            lineadded = True
+    elif d.find('if(xmlhttp.status==200') == 0:
+        s = 'if(xmlhttp.status==200||xmlhttp.status==0||xmlhttp.status==null){'
+    else:
+        s = d
+    t.write('%s%s' % (s,linesep))
+t.write('importModule=imprt'+linesep)
+t.close()
+
+#for compat with prev version, copy jsolait.js to init.js
+t = file(mfile,'r')
+z = t.readlines()
+t.close()
+f = file('init.js','w')
+for k in z:
+    f.write(k)
+f.close()
+
+#patch text/plain and text/xml to text/x-json
+mfile = 'jsonrpc.js'
+os.chdir('lib')
+print "patching %s" % mfile
+t = file(mfile,'U')
+lines = t.readlines()
+t.close()
+t = file(mfile,'w')
+for k in lines:
+    d = k.rstrip()
+    if d.find('text/plain') >= 0:
+        s = d.replace('text/plain','application/json-rpc')
+    elif d.find('text/xml') >= 0:
+        s = d.replace('text/xml','application/json-rpc')
+    elif d.find('ImportFailed(') > 0 and not d.endswith(';'):
+        s = d + ';'
+    else:
+        s = d
+    t.write('%s%s' % (s,linesep))
+t.close()
+os.chdir('..')
+print "done."
+print "original zip file is %s" % zippedfile

Added: z3/jsonserver/trunk/jsolait/lib/pythonkw.js
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/jsolait/lib/pythonkw.js	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,20 @@
+
+Module("pythonkw","1.0.0", function(mod){
+
+    var jsonrpc = imprt("jsonrpc");
+
+    mod.PythonKw = Class(function(publ){
+        publ.__init__ = function(kw){
+            this.kw = kw;
+            }
+
+        publ.toJSON = function(){
+            var pack = {};
+            pack["pythonKwMaRkEr"] = this.kw;
+            return jsonrpc.marshall(pack);
+            }
+
+        publ.kw;
+    })
+
+})

Added: z3/jsonserver/trunk/jsoncomponent.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/jsoncomponent.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,39 @@
+##############################################################################
+#
+# Copyright (c) 2005 Jim Washington and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+# jsoncomponent.py
+# json implementation component
+# jwashin 2005-07-27
+# 2005-10-09 updated to properly? handle unicode on the boundary
+# 2005-09-08 updated for modularity and IResult
+
+import minjson as json
+from interfaces import IJSONReader, IJSONWriter
+from zope.interface import implements
+
+
+class JSONReader(object):
+    """component implementing JSON reading"""
+    implements(IJSONReader)
+    
+    def read(self,aString, encoding='utf-8'):
+        return json.read(aString,encoding)
+
+
+class JSONWriter(object):
+    """component implementing JSON writing"""
+    implements(IJSONWriter)
+    
+    def write(self,anObject):
+        return json.write(anObject)

Added: z3/jsonserver/trunk/jsonrpc.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/jsonrpc.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,331 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+#adapted from various trunk xmlrpc files jwashin 2005-06-27
+
+#2005-06-26 removed JSONRPCNotifyResponse and got JSONRPCResponse to handle
+#           'notify' events
+#2005-06-26 let the publisher handle encoding
+#2005-06-27 set outgoing content-type the same as incoming content-type.
+#2005-09-08 updated to work with the new IResult idea (wsgi)
+#2005-10-09 unicode handling update
+
+__docformat__ = 'restructuredtext'
+
+from zope.app.publication.http import BaseHTTPPublication
+from interfaces import IMethodPublisher, IJSONRPCView, IJSONRPCPublisher,\
+    IJSONRPCRequest, IJSONReader, IJSONWriter
+from zope.interface import implements
+#from zope.publisher.http import IResult
+from zope.app.location.location import Location
+from zope.publisher.http import HTTPRequest, HTTPResponse, \
+    getCharsetUsingRequest, DirectResult
+from zope.publisher.browser import BrowserRequest
+from zope.security.proxy import isinstance
+from StringIO import StringIO
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.publisher.interfaces.browser import IBrowserApplicationRequest
+from zope.component import getUtility
+
+import traceback
+import logging
+
+DEBUG = logging.DEBUG
+logger = logging.getLogger()
+
+keyword_key = "pythonKwMaRkEr"
+
+#writeProfileData transcribes reads and writes files in the zope3
+# instance directory for profiling use.
+# profiledata.py has response dicts that can be read as python
+# profiledata.js has request javascript objects in javascript
+writeProfileData = False
+
+JSONRPCPublication = BaseHTTPPublication
+
+class JSONRPCPublicationFactory(object):
+    """a JSON-RPC Publication handler
+    modeled after zope.app.publication.xmlrpc.XMLRPCPublicationFactory
+    """
+    def __init__(self,db):
+        self.__pub = JSONRPCPublication(db)
+    def __call__(self):
+        return self.__pub
+
+class JSONRPCRequest(BrowserRequest):
+    """a JSON-RPC Request
+    modeled after zope.publisher.xmlrpc.XMLRPCRequest
+    REQUEST from JSON-RPC client
+    should have
+    method
+    params
+    id
+    """
+    jsonID = 'dummy'
+    form = {}
+    #IBrowserRequest is necessary because sometimes complete views may be
+    #transported that may need to look-up e.g., icons.
+    implements(IJSONRPCRequest,IBrowserRequest, IBrowserApplicationRequest)
+    _args = ()
+    def _createResponse(self):
+        """return a response"""
+        return JSONRPCResponse()
+        
+    def processInputs(self):
+        """take the converted request and make useful args of it"""
+        
+        json = getUtility(IJSONReader)
+        stream = self._body_instream
+        input = []
+        incoming = stream.read(1000)
+        while incoming:
+            input.append(incoming)
+            incoming = stream.read(1000)
+        input = ''.join(input)
+        #make it unicode; we are at the boundary
+        input = self._decode(input)
+        #encoding = getCharsetUsingRequest(self)
+        #if not isinstance(input,unicode):
+        #    input = input.decode(encoding)
+        data = json.read(input)
+        if writeProfileData:
+            infile = 'profiledata.js'
+            t = open(infile,'a')
+            t.write('%s\n' % input)
+            t.close()
+        logger.log(DEBUG, "processing inputs (%s)" % data)
+        #print "processing inputs (%s)" % data
+        functionstr = data['method']
+        # failure unless we split on '.' Why?
+        function = functionstr.split('.')
+        self.jsonID = data['id']
+        args = list(data['params'])
+        # now, look for keyword parameters
+        kwargs = None
+        notPositional = []
+        for k in args:
+            if isinstance(k,dict):
+                if k.has_key(keyword_key):
+                    if isinstance(k[keyword_key],dict):
+                        j = k[keyword_key]
+                        kwargs= j
+                        notPositional.append(k)
+        if notPositional:
+            for k in notPositional:
+                args.remove(k)
+        if kwargs:
+            for m in kwargs.keys():
+                self.form[str(m)] = kwargs[m]
+        self._args = tuple(args)
+        #make environment,cookies, etc., available to request.get()
+        super(JSONRPCRequest,self).processInputs()
+        self._environ['JSONRPC_MODE'] = True
+        if function:
+            self.setPathSuffix(function)
+
+    def traverse(self,object):
+        return super(BrowserRequest,self).traverse(object)
+        
+    def __getitem__(self,key):
+        return self.get(key)
+        
+# IResult has gone PRIVATE; for we now will send a bytestring and set 
+# charset.
+
+##class JSONResult(object):
+##    implements(IResult)
+##    def __init__(self,aString, encoding='utf-8'):
+##        #statements to help figure out Opera
+##        #encoding='utf-8'  #yields oriental charset, unparseable
+##        #encoding='utf-16' #yields something that looks like the right code,
+##        #    no trouble in gecko, but opera refuses to parse it
+##        #encoding = None is troublesome for gecko; only get '{'
+##        #encoding = 'iso-8859-1'
+##        try:
+##            s= aString.encode(encoding)
+##        except UnicodeEncodeError:
+##            s = aString
+##        self.body = [s]
+##        if encoding == 'utf-8':
+##            # don't need charset for utf-8; charset handling seems buggy on 
+##            # some browsers, so best not to use unnecessarily
+##            self.headers = [('content-type',
+##            'application/x-javascript'),
+##            ('content-length',str(len(s)))]
+##        else:
+##            self.headers = [('content-type',
+##            'application/x-javascript;charset=%s' % encoding),
+##            ('content-length',str(len(s)))]
+####        else:
+####            self.body = [aString]
+####            self.headers = [('content-type','application/x-javascript'),
+####                ('content-length',str(len(aString)))]
+##
+
+class JSONRPCResponse(HTTPResponse):
+    """JSON-RPC Response
+    modeled after zope.publisher.xmlrpc.XMLRPCResponse
+    """
+    #def getBase(self):
+    #    return True
+        
+    def setResult(self,result):
+        """return
+        {
+        'id' : matches id in request
+        'result' : the result or null if error
+        'error' : the error or null if result
+        }
+        """
+        id = self._request.jsonID
+        if id is not None:
+            result = premarshal(result)
+            wrapper = {'id':id}
+            wrapper['result'] = result
+            wrapper['error'] = None
+            if writeProfileData:
+                outfile = 'profiledata.py'
+                t = open(outfile,'a')
+                t.write('%s\n' % wrapper)
+                t.close()
+            json = getUtility(IJSONWriter)
+            encoding = getCharsetUsingRequest(self._request)
+            result = json.write(wrapper)
+            #body = JSONResult(result, encoding)
+            body = self._prepareResult(result)
+            super(JSONRPCResponse,self).setResult(body)
+            logger.log(DEBUG,"%s" % result)
+        else:
+            self.setStatus(204)
+            super(JSONRPCResponse,self).setResult('')
+            
+    def _prepareResult(self,result):
+        #we've asked json to return unicode.  result should be unicode
+        encoding = getCharsetUsingRequest(self._request) or 'utf-8'
+        if isinstance(result,unicode):
+            body = result.encode(encoding)
+            charset = encoding
+        #elif isinstance(result,string):
+        #    body = result
+        #    charset = sys.getdefaultencoding()
+        else:
+            #something's wrong.  json did not return unicode.
+            raise AttributeError
+        self.setHeader('content-type',"application/x-javascript;charset=%s" % charset)
+        return body
+
+    def handleException(self,exc_info):
+        t, value = exc_info[:2]
+        exc_data = []
+        for file, lineno, function, text in traceback.extract_tb(exc_info[2]):
+            exc_data.append("%s %s %s %s %s" % (file, "line",
+                lineno, "in", function))
+            exc_data.append("%s %s" % ( "=>", repr(text)))
+            exc_data.append( "** %s: %s" % exc_info[:2])
+        logger.log(logging.ERROR,"\n".join(exc_data))
+        s = '%s: %s' % (getattr(t, '__name__', t), value)
+        wrapper = {'id':self._request.jsonID}
+        wrapper['result'] = None
+        wrapper['error'] = s
+        json = getUtility(IJSONWriter)
+        result = json.write(wrapper)
+        body = self._prepareResult(result)
+        super(JSONRPCResponse,self).setResult(body)
+        logger.log(DEBUG,"Exception: %s" % result)
+        self.setStatus(200)
+
+def premarshal_dict(data):
+    """return a non-proxied dict"""
+    return dict([(premarshal(k), premarshal(v))
+                 for (k, v) in data.items()])
+
+def premarshal_list(data):
+    """return a non-proxied list"""
+    return map(premarshal, data)
+
+#note: no dates or datetimes in json, though supported by xmlrpc
+premarshal_dispatch_table = {
+    dict: premarshal_dict,
+    list: premarshal_list,
+    tuple: premarshal_list,
+    }
+
+premarshal_dispatch = premarshal_dispatch_table.get
+
+def premarshal(data):
+    premarshaller = premarshal_dispatch(data.__class__)
+    if premarshaller is not None:
+        return premarshaller(data)
+    return data
+
+
+class JSONRPCView(object):
+    """A base JSON-RPC view that can be used as mix-in for JSON-RPC views.
+       like zope.app.publisher.xmlrpc.XMLRPCView
+    """
+    implements(IJSONRPCView)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+
+class MethodPublisher(JSONRPCView, Location):
+    """Base class for JSON-RPC views that publish methods
+       like zope.app.publisher.xmlrpc.MethodPublisher
+    """
+    implements(IMethodPublisher)
+
+    def __getParent(self):
+        return hasattr(self, '_parent') and self._parent or self.context
+
+    def __setParent(self, parent):
+        self._parent = parent
+
+    __parent__ = property(__getParent, __setParent)
+
+
+class MethodTraverser(object):
+    implements(IJSONRPCPublisher)
+
+    __used_for__ = IMethodPublisher
+
+    def __init__(self, context, request):
+        self.context = context
+
+    def publishTraverse(self, request, name):
+        return getattr(self.context, name)
+
+
+class TestRequest(JSONRPCRequest):
+    """modeled after zope.publisher.xmlrpc.TestRequest"""
+    def __init__(self, body_instream=None, environ=None,
+                 response=None, **kw):
+
+        _testEnv =  {
+            'SERVER_URL':         'http://127.0.0.1',
+            'HTTP_HOST':          '127.0.0.1',
+            'CONTENT_LENGTH':     '0',
+            'GATEWAY_INTERFACE':  'TestFooInterface/1.0',
+            }
+
+        if environ:
+            _testEnv.update(environ)
+        if kw:
+            _testEnv.update(kw)
+        if body_instream is None:
+            body_instream = StringIO('')
+
+        super(TestRequest, self).__init__(
+            body_instream, _testEnv, response)
+

Added: z3/jsonserver/trunk/jsonserver-configure.zcml
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/jsonserver-configure.zcml	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,5 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <include package="jsonserver" />
+
+</configure>

Added: z3/jsonserver/trunk/jsonserver-meta.zcml
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/jsonserver-meta.zcml	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,5 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+  <include package="jsonserver" file="meta.zcml" />
+
+</configure>

Added: z3/jsonserver/trunk/jsonserver.e3p
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/jsonserver.e3p	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,160 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Project SYSTEM "Project-3.8.dtd">
+<!-- Project file for project jsonserver -->
+<!-- Saved: 2006-01-01, 12:26:01 -->
+<!-- Copyright (C) 2006 ,  -->
+<Project version="3.8">
+  <ProgLanguage mixed="0">Python</ProgLanguage>
+  <UIType>Console</UIType>
+  <Description></Description>
+  <Version></Version>
+  <Author></Author>
+  <Email></Email>
+  <Sources>
+    <Source>
+      <Dir>tests</Dir>
+      <Name>__init__.py</Name>
+    </Source>
+    <Source>
+      <Dir>tests</Dir>
+      <Name>test_directives.py</Name>
+    </Source>
+    <Source>
+      <Dir>tests</Dir>
+      <Name>test_httpfactory.py</Name>
+    </Source>
+    <Source>
+      <Dir>tests</Dir>
+      <Name>test_jsonrpcrequest.py</Name>
+    </Source>
+    <Source>
+      <Dir>tests</Dir>
+      <Name>test_jsonrpcpublication.py</Name>
+    </Source>
+    <Source>
+      <Dir>jsolait</Dir>
+      <Name>install_jsolait.py</Name>
+    </Source>
+    <Source>
+      <Name>metaconfigure.py</Name>
+    </Source>
+    <Source>
+      <Name>jsonrpc.py</Name>
+    </Source>
+    <Source>
+      <Name>__init__.py</Name>
+    </Source>
+    <Source>
+      <Name>interfaces.py</Name>
+    </Source>
+    <Source>
+      <Dir>tests</Dir>
+      <Name>jsonrpcviews.py</Name>
+    </Source>
+    <Source>
+      <Name>minjson.py</Name>
+    </Source>
+    <Source>
+      <Dir>tests</Dir>
+      <Name>test_json.py</Name>
+    </Source>
+    <Source>
+      <Name>jsoncomponent.py</Name>
+    </Source>
+    <Source>
+      <Name>requestpublicationfactory.py</Name>
+    </Source>
+    <Source>
+      <Name>ftesting.py</Name>
+    </Source>
+  </Sources>
+  <Forms>
+  </Forms>
+  <Translations>
+  </Translations>
+  <Interfaces>
+  </Interfaces>
+  <Others>
+    <Other>
+      <Name>configure.zcml</Name>
+    </Other>
+    <Other>
+      <Name>meta.zcml</Name>
+    </Other>
+    <Other>
+      <Name>README.txt</Name>
+    </Other>
+    <Other>
+      <Name>ZopePublicLicense.txt</Name>
+    </Other>
+    <Other>
+      <Dir>jsolait</Dir>
+      <Name>README.txt</Name>
+    </Other>
+    <Other>
+      <Dir>etc</Dir>
+      <Name>jsonserver-configure.zcml</Name>
+    </Other>
+    <Other>
+      <Dir>etc</Dir>
+      <Name>jsonserver-meta.zcml</Name>
+    </Other>
+    <Other>
+      <Dir>tests</Dir>
+      <Name>jsonrpc.zcml</Name>
+    </Other>
+    <Other>
+      <Dir>tests</Dir>
+      <Name>jsonrpc_error.zcml</Name>
+    </Other>
+    <Other>
+      <Dir>tests</Dir>
+      <Name>test.pt</Name>
+    </Other>
+    <Other>
+      <Name>VERSION.txt</Name>
+    </Other>
+    <Other>
+      <Name>jsonserver-configure.zcml</Name>
+    </Other>
+    <Other>
+      <Name>jsonserver-meta.zcml</Name>
+    </Other>
+    <Other>
+      <Name>DEPENDENCIES.cfg</Name>
+    </Other>
+    <Other>
+      <Name>SETUP.cfg</Name>
+    </Other>
+    <Other>
+      <Dir>jsolait</Dir>
+      <Dir>lib</Dir>
+      <Name>nothing.txt</Name>
+    </Other>
+    <Other>
+      <Dir>jsolait</Dir>
+      <Dir>libws</Dir>
+      <Name>nothing.txt</Name>
+    </Other>
+    <Other>
+      <Name>CHANGES.txt</Name>
+    </Other>
+    <Other>
+      <Dir>jsolait</Dir>
+      <Dir>lib</Dir>
+      <Name>pythonkw.js</Name>
+    </Other>
+  </Others>
+  <Vcs>
+    <VcsType>Subversion</VcsType>
+    <VcsOptions>{'status': [''], 'log': [''], 'global': [''], 'update': [''], 'remove': [''], 'add': [''], 'tag': [''], 'export': [''], 'diff': [''], 'commit': [''], 'checkout': [''], 'history': ['']}</VcsOptions>
+    <VcsOtherData>{'standardLayout': True}</VcsOtherData>
+  </Vcs>
+  <FiletypeAssociations>
+    <FiletypeAssociation pattern="*.ui.h" type="FORMS" />
+    <FiletypeAssociation pattern="*.ui" type="FORMS" />
+    <FiletypeAssociation pattern="*.idl" type="INTERFACES" />
+    <FiletypeAssociation pattern="*.py" type="SOURCES" />
+    <FiletypeAssociation pattern="*.ptl" type="SOURCES" />
+  </FiletypeAssociations>
+</Project>

Added: z3/jsonserver/trunk/jsonserver.e3t
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/jsonserver.e3t	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE Tasks SYSTEM "Tasks-3.8.dtd">
+<!-- Tasks file for project jsonserver -->
+<!-- Saved: 2006-02-03, 09:04:27 -->
+<Tasks version="3.8">
+</Tasks>

Added: z3/jsonserver/trunk/jsontest.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/jsontest.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,329 @@
+from unittest import TestCase, TestSuite, main, makeSuite
+import minjson as json
+#import jsonnew as json
+
+##    jsontest.py implements tests for the json.py JSON
+##    (http://json.org) reader and writer.
+##    Copyright (C) 2005  Patrick D. Logan
+##    Contact mailto:patrickdlogan at stardecisions.com
+##
+##    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
+
+# The object tests should be order-independent. They're not.
+# i.e. they should test for existence of keys and values
+# with read/write invariance.
+
+
+
+class JsonTest(TestCase):
+
+    def testReadEmptyObject(self):
+        obj = json.read("{}")
+        self.assertEqual({}, obj)
+
+    def testWriteEmptyObject(self):
+        s = json.write({})
+        self.assertEqual("{}", s)
+
+    def testReadStringValue(self):
+        obj = json.read('{ "name" : "Patrick" }')
+        self.assertEqual({ "name" : "Patrick" }, obj)
+
+    def testReadEscapedQuotationMark(self):
+        obj = json.read(r'"\""')
+        #self.assertEqual(r'"', obj)
+
+    def testReadEscapedSolidus(self):
+        obj = json.read(r'"\/"')
+        #self.assertEqual(r'/', obj)
+
+    def testReadEscapedReverseSolidus(self):
+        obj = json.read(r'"\\"')
+        self.assertEqual("\\", obj)
+
+    def testReadEscapedBackspace(self):
+        obj = json.read(r'"\b"')
+        self.assertEqual("\b", obj)
+
+    def testReadEscapedFormfeed(self):
+        obj = json.read(r'"\f"')
+        self.assertEqual("\f", obj)
+
+    def testReadEscapedNewline(self):
+        obj = json.read(r'"\n"')
+        self.assertEqual("\n", obj)
+
+    def testReadEscapedCarriageReturn(self):
+        obj = json.read(r'"\r"')
+        self.assertEqual("\r", obj)
+
+    def testReadEscapedHorizontalTab(self):
+        obj = json.read(r'"\t"')
+        self.assertEqual("\t", obj)
+
+    def testReadEscapedHexCharacter(self):
+        obj = json.read(r'"\u000A"')
+        #self.assertEqual("\n", obj)
+        obj = json.read(r'"\u1001"')
+        #self.assertEqual(u'\u1001', obj)
+
+    #def testReadBadEscapedHexCharacter(self):
+    #    self.assertRaises(UnicodeDecodeError, self.doReadBadEscapedHexCharacter)
+        #self.doReadBadEscapedHexCharacter()
+        #do we really care about this?
+
+    #def doReadBadEscapedHexCharacter(self):
+    #    json.read('"\u10K5"')
+
+
+    def testReadBadObjectKey(self):
+        self.assertRaises(SyntaxError, self.doReadBadObjectKey)
+
+    def doReadBadObjectKey(self):
+        json.read('{ "44 : "age" }')
+
+    def testReadDoubleSolidusComment(self):
+        obj = json.read("[1, 2, // This is a comment.\n 3]")
+        self.assertEqual([1, 2, 3], obj)
+        obj = json.read('[1, 2, // This is a comment.\n{"last":3}]')
+        self.assertEqual([1, 2, {"last":3}], obj)
+
+    def testReadBadDoubleSolidusComment(self):
+        self.assertRaises(SyntaxError, self.doReadBadDoubleSolidusComment)
+
+    def doReadBadDoubleSolidusComment(self):
+        json.read("[1, 2, / This is not a comment.\n 3]")
+
+    def testReadCStyleComment(self):
+        obj = json.read("[1, 2, /* This is a comment. \n */ 3]")
+        self.assertEqual([1, 2, 3], obj)
+        obj = json.read('[1, 2, /* This is a comment. */{"last":3}]')
+        self.assertEqual([1, 2, {"last":3}], obj)
+
+    def testReadCStyleCommentWithoutEnd(self):
+        self.assertRaises(SyntaxError, self.doReadCStyleCommentWithoutEnd)
+
+    def testReadCStyleCommentWithSlashStar(self):
+        obj = self.doReadCStyleCommentWithSlashStar()
+        self.assertEqual([1, 2, 3], obj)
+
+    def doReadCStyleCommentWithoutEnd(self):
+        json.read("[1, 2, /* This is not a comment./ 3]")
+
+    def doReadCStyleCommentWithSlashStar(self):
+        return json.read("[1, 2, /* This is not a comment./* */ 3]")
+
+    def testReadBadObjectSyntax(self):
+        self.assertRaises(SyntaxError, self.doReadBadObjectSyntax)
+
+    def doReadBadObjectSyntax(self):
+        json.read('{"age", 44}')
+
+    def testWriteStringValue(self):
+        s = json.write({ 'name' : 'Patrick' })
+        self.assertEqual("{'name': 'Patrick'}", s)
+
+    def testReadIntegerValue(self):
+        obj = json.read('{ "age" :  44 }')
+        self.assertEqual({ "age" : 44 }, obj)
+
+    def testReadNegativeIntegerValue(self):
+        obj = json.read('{ "key" : -44 }')
+        self.assertEqual({ "key" : -44 }, obj)
+
+    def testReadFloatValue(self):
+        obj = json.read('{ "age" : 44.5 }')
+        self.assertEqual({ "age" : 44.5 }, obj)
+
+    def testReadNegativeFloatValue(self):
+        obj = json.read(' { "key" : -44.5 } ')
+        self.assertEqual({ "key" : -44.5 }, obj)
+
+    def testReadBadNumber(self):
+        self.assertRaises(SyntaxError, self.doReadBadNumber)
+
+    def doReadBadNumber(self):
+        json.read('-44.4.4')
+
+    def testReadSmallObject(self):
+        obj = json.read('{ "name" : \'Patrick\', "age":44} ')
+        self.assertEqual({ "age" : 44, "name" : "Patrick" }, obj)
+
+    def testReadEmptyArray(self):
+        obj = json.read('[]')
+        self.assertEqual([], obj)
+
+    def testWriteEmptyArray(self):
+        self.assertEqual("[]", json.write([]))
+
+    def testReadSmallArray(self):
+        obj = json.read(' [ "a" , "b", "c" ] ')
+        self.assertEqual(["a", "b", "c"], obj)
+
+    def testWriteSmallArray(self):
+        self.assertEqual('[1, 2, 3, 4]', json.write([1, 2, 3, 4]))
+
+    def testWriteSmallObject(self):
+        s = json.write({ "name" : "Patrick", "age": 44 })
+        self.assertEqual("{'age': 44, 'name': 'Patrick'}", s)
+
+    def testWriteFloat(self):
+        self.assertEqual("3.445567", json.write(3.445567))
+
+    def testWriteLong(self):
+         self.assertEqual("12345678901234567890",json.write(12345678901234567890))
+
+    def testReadNegativeLongValue(self):
+        obj = json.read('{ "key" : -44556677889900112233 }')
+        self.assertEqual({ "key" : -44556677889900112233 }, obj)
+
+    def testReadTrue(self):
+        self.assertEqual(json.read("true"),True)
+
+    def testReadFalse(self):
+        self.assertEqual(json.read("false"),False)
+
+    def testReadNull(self):
+        self.assertEqual(None, json.read("null"))
+
+    def testWriteTrue(self):
+        self.assertEqual("true", json.write(True))
+
+    def testWriteFalse(self):
+        self.assertEqual("false", json.write(False))
+
+    def testReadEmptyArrayAtEndOfObject(self):
+        self.assertEqual({"a":"a","b":"b","c":[]}, json.read('{"a":"a","b":"b","c":[]}'))
+
+    def testReadObjectAtEndOfArray(self):
+        self.assertEqual(["a","b","c",{"a":"a","b":"b"}], json.read('["a","b","c",{"a":"a","b":"b"}  ]'))
+
+    def testReadEmptyObjectAtEndOfArray(self):
+        self.assertEqual(["a","b","c",{}], json.read('["a","b","c",{}]'))
+
+    def testReadEmptyArrayAtEndOfArray(self):
+        self.assertEqual(["a","b",[]], json.read('["a","b",[]]'))
+
+    def testReadEmptyObjectAtEndOfObject(self):
+        self.assertEqual({"a":"a","b":"b","c":{}}, json.read('{"a":"a","b":"b","c":{}}'))
+
+
+    #The following 3 yield a syntax error in minjson
+    #def testWriteAndReadComplexString(self):
+    #    obj = 'she said, "isn\'t this great?"'
+    #    print
+    #    print obj
+    #    obj = str(obj)
+    #    w = json.write(obj)
+    #    print w
+    #    r = json.read(w)
+    #   self.assertEqual(obj,str(r))
+
+    #def testWriteAndReadTripleQuotedString(self):
+    #    obj = """ Mary said, "Hi, show me a 'good time!'"
+    #              To which Bob replied, "Sure!"
+    #    """
+    #    w = json.write(obj)
+    #    print str(w)
+    #    r = json.read(obj)
+    #    self.assertEqual(r , obj)
+
+    #def testWriteAndReadUnicode(self):
+    #    obj = u'La Pe\xf1a'
+    #    w = json.write(obj)
+    #    print
+    #    print w
+    #    print '_________________________'
+    #    r = json.read(w)
+    #    print r
+    #    self.assertEqual(r,obj)
+
+    def testWriteNull(self):
+        self.assertEqual("null", json.write(None))
+
+    def testWriteUnicode(self):
+        self.assertEqual(u'test', json.write(u'test'))
+
+    def testReadArrayOfSymbols(self):
+        self.assertEqual([True, False, None], json.read(" [ true, false,null] "))
+
+    def testWriteArrayOfSymbols(self):
+        self.assertEqual("[true, false, null]", json.write([True, False, None]))
+
+    def testReadArray2(self):
+        self.assertEqual(json.read('{"a":[1,2,3]}'),{'a':[1,2,3]})
+
+    def testReadComplexObject(self):
+        src = '''
+    { "name": "Patrick", "age" : 44, "Employed?" : true, "Female?" : false, "grandchildren":null }
+'''
+        obj = json.read(src)
+        self.assertEqual({"name":"Patrick","age":44,"Employed?":True,"Female?":False,"grandchildren":None}, obj)
+
+    def testReadSingleQuotedDictKeys(self):
+        src = "{'id':'HttpReq','params':[1],'method':'action'}"
+        obj = json.read(src)
+
+    def testReadSingleQuotedStrings(self):
+        src = "'hello'"
+        obj = json.read(src)
+
+    def testReadLongArray(self):
+        src = '''[    "used",
+    "abused",
+    "confused",
+    true, false, null,
+    1,
+    2,
+    [3, 4, 5]  ]
+'''
+        obj = json.read(src)
+        self.assertEqual(["used","abused","confused", True, False, None,
+                          1,2,[3,4,5]], obj)
+
+
+    def testReadComplexArray(self):
+        src = '''
+[
+    { "name": "Patrick", "age" : 44,
+      "Employed?" : true, "Female?" : false,
+      "grandchildren":null },
+    "used",
+    "abused",
+    "confused",
+    1,
+    2,
+    [3, 4, 5]
+]
+'''
+        obj = json.read(src)
+        self.assertEqual([{"name":"Patrick","age":44,"Employed?":True,"Female?":False,"grandchildren":None},
+                          "used","abused","confused",
+                          1,2,[3,4,5]], obj)
+
+    def testWriteComplexArray(self):
+        obj = [{"name":"Patrick","age":44,"Employed?":True,"Female?":False,"grandchildren":None},
+               "used","abused","confused",
+               1,2,[3,4,5]]
+        self.assertEqual("[{'Female?': false, 'age': 44, 'name': 'Patrick', 'grandchildren': null, 'Employed?': true}, 'used', 'abused', 'confused', 1, 2, [3, 4, 5]]",
+                         json.write(obj))
+
+def test_suite():
+    return TestSuite((
+    makeSuite(JsonTest),
+    ))
+
+if __name__ == '__main__':
+    main(defaultTest = 'test_suite')

Added: z3/jsonserver/trunk/meta.zcml
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/meta.zcml	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,20 @@
+<configure
+    xmlns:meta="http://namespaces.zope.org/meta"
+    xmlns="http://namespaces.zope.org/zope">
+    
+  <!--
+  
+    using xmlrpc's code for schema;
+    If we needed something slightly different, we would have  
+    copied and altered that metadirectives.py file
+    
+    -->
+    
+  <meta:directive
+       namespace="http://namespaces.zope.org/jsonrpc"
+       name="view"
+       schema="zope.app.publisher.xmlrpc.metadirectives.IViewDirective"
+       handler=".metaconfigure.view" />
+       
+    <meta:provides feature="jsonrpc" />
+</configure>

Added: z3/jsonserver/trunk/metaconfigure.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/metaconfigure.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,115 @@
+############################################################################
+##
+#
+# Copyright (c) 2001 - 2005 Zope Corporation and Contributors. 
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+############################################################################
+##
+"""JSON-RPC configuration code
+
+like zope.app.publisher.xmlrpc.metaconfigure
+
+updated 2005-12-03 Roger Ineichen
+jwashin 2005-06-06
+"""
+import zope.interface
+from zope.interface import Interface
+from zope.security.checker import CheckerPublic, Checker
+from zope.configuration.exceptions import ConfigurationError
+from interfaces import IJSONRPCRequest
+
+from zope.app.component.interface import provideInterface
+from zope.app.component.metaconfigure import handler
+from jsonrpc import MethodPublisher
+
+def view(_context, for_=None, interface=None, methods=None,
+         class_=None,  permission=None, name=None):
+
+    interface = interface or []
+    methods = methods or []
+
+    # If there were special permission settings provided, then use them
+    if permission == 'zope.Public':
+        permission = CheckerPublic
+
+    require = {}
+    for attr_name in methods:
+        require[attr_name] = permission
+
+    if interface:
+        for iface in interface:
+            for field_name in iface:
+                require[field_name] = permission
+            _context.action(
+                discriminator = None,
+                callable = provideInterface,
+                args = ('', for_)
+                )
+
+    # Make sure that the class inherits MethodPublisher, so that the views
+    # have a location
+    if class_ is None:
+        class_ = MethodPublisher
+        original_class = class_
+    else:
+        original_class = class_
+        class_ = type(class_.__name__, (class_, MethodPublisher), {})
+
+    if name:
+        # Register a single view
+
+        if permission:
+            checker = Checker(require)
+
+            def proxyView(context, request, class_=class_, checker=checker):
+                view = class_(context, request)
+                # We need this in case the resource gets unwrapped and
+                # needs to be rewrapped
+                view.__Security_checker__ = checker
+                return view
+
+            class_ =  proxyView
+            class_.factory = original_class
+
+        # Register the new view.
+        _context.action(
+            discriminator = ('view', for_, name, IJSONRPCRequest),
+            callable = handler,
+            args = ('provideAdapter',
+                    (for_, IJSONRPCRequest), Interface, name, class_,
+                    _context.info)
+            )
+    else:
+        if permission:
+            checker = Checker({'__call__': permission})
+        else:
+            checker = None
+
+        for name in require:
+            # create a new callable class with a security checker;
+            cdict = {'__Security_checker__': checker,
+                     '__call__': getattr(class_, name)}
+            new_class = type(class_.__name__, (class_,), cdict)
+            _context.action(
+                discriminator = ('view', for_, name, IJSONRPCRequest),
+                callable = handler,
+                args = ('provideAdapter',
+                        (for_, IJSONRPCRequest), Interface, name, new_class,
+                        _context.info)
+                )
+
+    # Register the used interfaces with the site manager
+    if for_ is not None:
+        _context.action(
+            discriminator = None,
+            callable = provideInterface,
+            args = ('', for_)
+            )

Added: z3/jsonserver/trunk/minjson.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/minjson.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,436 @@
+##############################################################################
+#
+# Copyright (c) 2005 Jim Washington and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+# minjson.py
+# reads minimal javascript objects.
+# str's objects and fixes the text to write javascript.
+
+#UNICODE USAGE:  Minjson tries hard to accommodate naive usage in a 
+#"Do what I mean" manner.  Real applications should handle unicode separately.
+# The "right" way to use minjson in an application is to provide minjson a 
+# python unicode string for reading and accept a unicode output from minjson's
+# writing.  That way, the assumptions for unicode are yours and not minjson's.
+
+# That said, the minjson code has some (optional) unicode handling that you 
+# may look at as a model for the unicode handling your application may need.
+
+# Thanks to Patrick Logan for starting the json-py project and making so many
+# good test cases.
+
+# Additional thanks to Balazs Ree for replacing the writing module.
+
+# Jim Washington 30 Dec 2005.
+
+# 2005-12-30 writing now traverses the object tree instead of relying on 
+#            str() or unicode()
+# 2005-10-10 on reading, looks for \\uxxxx and replaces with u'\uxxxx'
+# 2005-10-09 now tries hard to make all strings unicode when reading.
+# 2005-10-07 got rid of eval() completely, makes object as found by the
+#            tokenizer.
+# 2005-09-06 imported parsing constants from tokenize; they changed a bit from
+#            python2.3 to 2.4
+# 2005-08-22 replaced the read sanity code
+# 2005-08-21 Search for exploits on eval() yielded more default bad operators.
+# 2005-08-18 Added optional code from Koen van de Sande to escape
+#            outgoing unicode chars above 128
+
+
+from re import compile, sub, search, DOTALL
+from token import ENDMARKER, NAME, NUMBER, STRING, OP, ERRORTOKEN
+from tokenize import tokenize, TokenError, NL
+
+#Usually, utf-8 will work, set this to utf-16 if you dare.
+emergencyEncoding = 'utf-8'
+
+class ReadException(Exception):
+    pass
+
+class WriteException(Exception):
+    pass
+
+#################################
+#      read JSON object         #
+#################################
+
+slashstarcomment = compile(r'/\*.*?\*/',DOTALL)
+doubleslashcomment = compile(r'//.*\n')
+
+unichrRE = compile(r"\\u[0-9a-fA-F]{4,4}")
+
+def unichrReplace(match):
+    return unichr(int(match.group()[2:],16))
+
+escapeStrs = (('\\','\\\\'),('\n',r'\n'),('\b',r'\b'),
+    ('\f',r'\f'),('\t',r'\t'),('\r',r'\r'), ('"',r'\"')
+    )
+
+class DictToken:
+    __slots__=[]
+    pass
+class ListToken:
+    __slots__=[]
+    pass
+class ColonToken:
+    __slots__=[]
+    pass
+class CommaToken:
+    __slots__=[]
+    pass
+
+class JSONReader(object):
+    """raise SyntaxError if it is not JSON, and make the object available"""
+    def __init__(self,data):
+        self.stop = False
+        #make an iterator of data so that next() works in tokenize.
+        self._data = iter([data])
+        self.lastOp = None
+        self.objects = []
+        self.tokenize()
+
+    def tokenize(self):
+        try:
+            tokenize(self._data.next,self.readTokens)
+        except TokenError:
+            raise SyntaxError
+
+    def resolveList(self):
+        #check for empty list
+        if isinstance(self.objects[-1],ListToken):
+            self.objects[-1] = []
+            return
+        theList = []
+        commaCount = 0
+        try:
+            item = self.objects.pop()
+        except IndexError:
+            raise SyntaxError
+        while not isinstance(item,ListToken):
+            if isinstance(item,CommaToken):
+                commaCount += 1
+            else:
+                theList.append(item)
+            try:
+                item = self.objects.pop()
+            except IndexError:
+                raise SyntaxError
+        if not commaCount == (len(theList) -1):
+            raise SyntaxError
+        theList.reverse()
+        item = theList
+        self.objects.append(item)
+
+    def resolveDict(self):
+        theList = []
+        #check for empty dict
+        if isinstance(self.objects[-1], DictToken):
+            self.objects[-1] = {}
+            return
+        #not empty; must have at least three values
+        try:
+            #value (we're going backwards!)
+            value = self.objects.pop()
+        except IndexError:
+            raise SyntaxError
+        try:
+            #colon
+            colon = self.objects.pop()
+            if not isinstance(colon, ColonToken):
+                raise SyntaxError
+        except IndexError:
+            raise SyntaxError
+        try:
+            #key
+            key = self.objects.pop()
+            if not isinstance(key,basestring):
+                raise SyntaxError
+        except IndexError:
+
+            raise SyntaxError
+        #salt the while
+        comma = value
+        while not isinstance(comma,DictToken):
+            # store the value
+            theList.append((key,value))
+            #do it again...
+            try:
+                #might be a comma
+                comma = self.objects.pop()
+            except IndexError:
+                raise SyntaxError
+            if isinstance(comma,CommaToken):
+                #if it's a comma, get the values
+                try:
+                    value = self.objects.pop()
+                except IndexError:
+                    #print self.objects
+                    raise SyntaxError
+                try:
+                    colon = self.objects.pop()
+                    if not isinstance(colon, ColonToken):
+                        raise SyntaxError
+                except IndexError:
+                    raise SyntaxError
+                try:
+                    key = self.objects.pop()
+                    if not isinstance(key,basestring):
+                        raise SyntaxError
+                except IndexError:
+                    raise SyntaxError
+        theDict = {}
+        for k in theList:
+            theDict[k[0]] = k[1]
+        self.objects.append(theDict)
+
+    def readTokens(self,type, token, (srow, scol), (erow, ecol), line):
+        # UPPERCASE consts from tokens.py or tokenize.py
+        if type == OP:
+            if token not in "[{}],:-":
+                raise SyntaxError
+            else:
+                self.lastOp = token
+            if token == '[':
+                self.objects.append(ListToken())
+            elif token == '{':
+                self.objects.append(DictToken())
+            elif token == ']':
+                self.resolveList()
+            elif token == '}':
+                self.resolveDict()
+            elif token == ':':
+                self.objects.append(ColonToken())
+            elif token == ',':
+                self.objects.append(CommaToken())
+        elif type == STRING:
+            tok = token[1:-1]
+            for k in escapeStrs:
+                if k[1] in tok:
+                    tok = tok.replace(k[1],k[0])
+            self.objects.append(tok)
+        elif type == NUMBER:
+            if self.lastOp == '-':
+                factor = -1
+            else:
+                factor = 1
+            try:
+                self.objects.append(factor * int(token))
+            except ValueError:
+                self.objects.append(factor * float(token))
+        elif type == NAME:
+            try:
+                self.objects.append({'true':True,
+                    'false':False,'null':None}[token])
+            except KeyError:
+                raise SyntaxError
+        elif type == ENDMARKER:
+            pass
+        elif type == NL:
+            pass
+        elif type == ERRORTOKEN:
+            if ecol == len(line):
+                #it's a char at the end of the line.  (mostly) harmless.
+                pass
+            else:
+                raise SyntaxError
+        else:
+            raise SyntaxError
+    def output(self):
+        try:
+            assert len(self.objects) == 1
+        except AssertionError:
+            raise SyntaxError
+        return self.objects[0]
+
+def safeRead(aString, encoding=None):
+    """read the js, first sanitizing a bit and removing any c-style comments
+    If the input is a unicode string, great.  That's preferred.  If the input 
+    is a byte string, strings in the object will be produced as unicode anyway.
+    """
+    # get rid of trailing null. Konqueror appends this.
+    CHR0 = chr(0)
+    while aString.endswith(CHR0):
+        aString = aString[:-1]
+    # strip leading and trailing whitespace
+    aString = aString.strip()
+    # zap /* ... */ comments
+    aString = slashstarcomment.sub('',aString)
+    # zap // comments
+    aString = doubleslashcomment.sub('',aString)
+    # detect and handle \\u unicode characters. Note: This has the side effect
+    # of converting the entire string to unicode. This is probably OK.
+    unicodechars = unichrRE.search(aString)
+    if unicodechars:
+        aString = unichrRE.sub(unichrReplace, aString)
+    #if it's already unicode, we won't try to decode it
+    if isinstance(aString, unicode):
+        s = aString
+    else:
+        if encoding:
+            # note: no "try" here.  the encoding provided must work for the
+            # incoming byte string.  UnicodeDecode error will be raised
+            # in that case.  Often, it will be best not to provide the encoding
+            # and allow the default
+            s = unicode(aString, encoding)
+            #print "decoded %s from %s" % (s,encoding)
+        else:
+            # let's try to decode to unicode in system default encoding
+            try:
+                s = unicode(aString)
+                #import sys
+                #print "decoded %s from %s" % (s,sys.getdefaultencoding())
+            except UnicodeDecodeError:
+                # last choice: handle as emergencyEncoding
+                enc = emergencyEncoding
+                s = unicode(aString, enc)
+                #print "%s decoded from %s" % (s, enc)
+    # parse and get the object.
+    try:
+        data = JSONReader(s).output()
+    except SyntaxError:
+        raise ReadException, 'Unacceptable JSON expression: %s' % aString
+    return data
+
+read = safeRead
+
+#################################
+#   write object as JSON        #
+#################################
+
+import re, codecs
+from cStringIO import StringIO
+
+### Codec error handler
+
+def jsonreplace_handler(exc):
+    '''Error handler for json
+
+    If encoding fails, \\uxxxx must be emitted. This
+    is similar to the "backshashreplace" handler, only
+    that we never emit \\xnn since this is not legal
+    according to the JSON syntax specs.
+    '''
+    if isinstance(exc, UnicodeEncodeError):
+        part = exc.object[exc.start]
+        # repr(part) will convert u'\unnnn' to u'u\\nnnn'
+        return u'\\u%04x' % ord(part), exc.start+1
+    else:
+        raise exc
+
+# register the error handler
+codecs.register_error('jsonreplace', jsonreplace_handler)
+
+### Writer
+
+def write(input, encoding='utf-8', outputEncoding=None):
+    writer = JsonWriter(input_encoding=encoding, output_encoding=outputEncoding)
+    writer.write(input)
+    return writer.getvalue()
+
+re_strmangle = re.compile('"|\b|\f|\n|\r|\t|\\\\')
+
+def func_strmangle(match):
+    return {
+        '"': '\\"',
+        '\b': '\\b',
+        '\f': '\\f',
+        '\n': '\\n',
+        '\r': '\\r',
+        '\t': '\\t',
+        '\\': '\\\\',
+        }[match.group(0)]
+
+def strmangle(text):
+    return re_strmangle.sub(func_strmangle, text)
+
+class JsonStream(object):
+
+    def __init__(self):
+        self.buf = []
+
+    def write(self, text):
+        self.buf.append(text)
+
+    def getvalue(self):
+        return ''.join(self.buf)
+
+class JsonWriter(object):
+
+    def __init__(self, stream=None, input_encoding='utf-8', output_encoding=None):
+        '''
+        - stream is optional, if specified must also give output_encoding
+        - The input strings can be unicode or in input_encoding
+        - output_encoding is optional, if omitted, result will be unicode
+        '''
+        if stream is not None:
+            if output_encoding is None:
+                raise WriteException, 'If a stream is given, output encoding must also be provided'
+        else:
+            stream = JsonStream()
+        self.stream = stream
+        self.input_encoding = input_encoding
+        self.output_encoding = output_encoding
+
+    def write(self, obj):
+        if isinstance(obj, (list, tuple)):
+            self.stream.write('[')
+            first = True
+            for elem in obj:
+                if first:
+                    first = False
+                else:
+                    self.stream.write(',')
+                self.write(elem)
+            self.stream.write(']'),
+        elif isinstance(obj, dict):
+            self.stream.write('{')
+            first = True
+            for key, value in obj.iteritems():
+                if first:
+                    first = False
+                else:
+                    self.stream.write(',')
+                self.write(key)
+                self.stream.write(':')
+                self.write(value)
+            self.stream.write('}')
+        elif obj is True:
+            self.stream.write('true')
+        elif obj is False:
+            self.stream.write('false')
+        elif obj is None:
+            self.stream.write('null')
+        elif not isinstance(obj, basestring):
+            # if we are not baseobj, convert to it
+            try:
+                obj = str(obj)
+            except Exception, exc:
+                raise WriteException, 'Cannot write object (%s: %s)' % (exc.__class__, exc)
+            self.stream.write(obj)
+        else:
+            # convert to unicode first
+            if not isinstance(obj, unicode):
+                try:
+                    obj = unicode(obj, self.input_encoding)
+                except (UnicodeDecodeError, UnicodeTranslateError):
+                    obj = unicode(obj, 'utf-8', 'replace')
+            # do the mangling
+            obj = strmangle(obj)
+            # make the encoding
+            if self.output_encoding is not None:
+                obj = obj.encode(self.output_encoding, 'jsonreplace')
+            self.stream.write('"')
+            self.stream.write(obj)
+            self.stream.write('"')
+
+    def getvalue(self):
+        return self.stream.getvalue()

Added: z3/jsonserver/trunk/requestpublicationfactory.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/requestpublicationfactory.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,21 @@
+# class for json-rpc publication factory.
+# like zope.app.publication.requestpublicationfactories, which
+# registers SOAPFactory, XMLRPCFactory, HTTPFactory, and BrowserFactory.
+# JSONRPCFactory is similar to those.
+
+from zope.app.publication.interfaces import IRequestPublicationFactory
+from interfaces import IJSONRPCRequestFactory
+from zope import component
+from jsonrpc import JSONRPCRequest, JSONRPCPublication
+from zope.interface import implements
+
+class JSONRPCFactory(object):
+    implements(IRequestPublicationFactory)
+    
+    def canHandle(self,environment):
+        return True
+        
+    def __call__(self):
+        request_class = component.queryUtility(
+            IJSONRPCRequestFactory, default=JSONRPCRequest)
+        return request_class, JSONRPCPublication

Added: z3/jsonserver/trunk/tests/__init__.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/tests/__init__.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1 @@
+#tests for json-rpc; now with WorryFree&reg; :)
\ No newline at end of file

Added: z3/jsonserver/trunk/tests/jsonrpc.zcml
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/tests/jsonrpc.zcml	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,55 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:jsonrpc="http://namespaces.zope.org/jsonrpc"
+           i18n_domain="zope">
+
+  <include package="jsonserver" file="meta.zcml"/>
+  <include package="zope.app.security" file="meta.zcml"/>
+  
+  <jsonrpc:view
+      name="test"
+      class="zope.app.component.tests.views.V1"
+      permission="zope.Public"
+      for="zope.app.component.tests.views.IC"
+      />
+
+  <jsonrpc:view
+      name="test2"
+      class="zope.app.component.tests.views.V1"
+      for="zope.app.component.tests.views.IC"
+      permission="zope.Public"
+      interface="zope.app.component.tests.views.IV"
+      />
+
+  <jsonrpc:view
+      name="test3"
+      class=".jsonrpcviews.V1"
+      for="zope.app.component.tests.views.IC"
+      permission="zope.Public"
+      methods="action" />
+
+  <jsonrpc:view
+      name="test4"
+      class=".jsonrpcviews.V1"
+      for="zope.app.component.tests.views.IC"
+      permission="zope.Public"
+      methods="action"
+      interface="zope.app.component.tests.views.IV" />
+
+  <jsonrpc:view
+      name="test5"
+      class=".jsonrpcviews.V1"
+      for="zope.app.component.tests.views.IC"
+      permission="zope.Public"
+      methods="action index"
+      interface="zope.app.component.tests.views.IV" />
+
+
+  <jsonrpc:view
+      class=".jsonrpcviews.V1"
+      for="zope.app.component.tests.views.IC"
+      interface="zope.app.component.tests.views.IV"
+      permission="zope.Public"
+      methods="action"
+      />
+
+</configure>

Added: z3/jsonserver/trunk/tests/jsonrpc_error.zcml
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/tests/jsonrpc_error.zcml	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,13 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:jsonrpc="http://namespaces.zope.org/jsonrpc"
+           i18n_domain="zope">
+
+  <include package="zope.app.publisher.jsonrpc" file="meta.zcml"/>
+
+  <jsonrpc:view
+        name="test"
+        factory="zope.component.tests.views.V1"
+        for="zope.component.tests.views.IC"
+        methods="action index" />
+
+</configure>

Added: z3/jsonserver/trunk/tests/jsonrpcviews.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/tests/jsonrpcviews.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,47 @@
+##############################################################################
+#
+# Copyright (c) 2001 - 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""JSON-RPC Views test objects
+
+adapted from zope.publisher.tests.xmlrpcviews
+jwashin 2005-06-06
+
+"""
+from zope.interface import Interface, implements
+from jsonserver.interfaces import IJSONRPCPublisher
+
+class IC(Interface): pass
+
+class V1(object):
+    implements(IJSONRPCPublisher)
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+    def action(self):
+        return 'done'
+    def index(self):
+        return 'V1 here'
+
+class VZMI(V1):
+    pass
+
+class R1(object):
+    def __init__(self, request):
+        self.request = request
+
+    implements(IJSONRPCPublisher)
+
+class RZMI(R1):
+    pass
+

Added: z3/jsonserver/trunk/tests/test.pt
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/tests/test.pt	Wed Mar  8 13:37:02 2006
@@ -0,0 +1 @@
+<html><body><p>test</p></body></html>

Added: z3/jsonserver/trunk/tests/test_directives.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/tests/test_directives.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,99 @@
+##############################################################################
+#
+# Copyright (c) 2001 - 2005 Zope Corporation and Contributors. 
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test 'jsonrpc' ZCML Namespace directives.
+
+mod from zope.app.publisher.xmlrpc.tests.test_directives.py jwashin 2005-06-06
+"""
+import unittest
+
+from zope.configuration import xmlconfig
+from zope.configuration.exceptions import ConfigurationError
+from zope.app.component.tests.views import IC, V1
+from zope.app.testing.placelesssetup import PlacelessSetup
+from zope.security.proxy import ProxyFactory
+
+from zope.component.tests.request import Request
+
+from jsonserver.interfaces import IJSONRPCRequest
+
+import jsonserver
+from jsonserver import jsonrpc
+from zope.interface import implements
+from zope.component import queryMultiAdapter, getMultiAdapter
+
+#Michael Dudzik wanted this for his test setup
+import jsonserver.tests
+
+request = Request(IJSONRPCRequest)
+
+class Ob(object):
+    implements(IC)
+
+ob = Ob()
+
+class DirectivesTest(PlacelessSetup, unittest.TestCase):
+
+    def testView(self):
+        self.assertEqual(
+            queryMultiAdapter((ob, request), name='test'), None)
+        xmlconfig.file("jsonrpc.zcml", jsonserver.tests)
+        view = queryMultiAdapter((ob, request), name='test')
+        self.assert_(V1 in view.__class__.__bases__)
+        self.assert_(jsonrpc.MethodPublisher in view.__class__.__bases__)
+
+    def testInterfaceProtectedView(self):
+        xmlconfig.file("jsonrpc.zcml", jsonserver.tests)
+        v = getMultiAdapter((ob, request), name='test2')
+        v = ProxyFactory(v)
+        self.assertEqual(v.index(), 'V1 here')
+        self.assertRaises(Exception, getattr, v, 'action')
+
+    def testAttributeProtectedView(self):
+        xmlconfig.file("jsonrpc.zcml", jsonserver.tests)
+        v = getMultiAdapter((ob, request), name='test3')
+        v = ProxyFactory(v)
+        self.assertEqual(v.action(), 'done')
+        self.assertRaises(Exception, getattr, v, 'index')
+
+    def testInterfaceAndAttributeProtectedView(self):
+        xmlconfig.file("jsonrpc.zcml", jsonserver.tests)
+        v = getMultiAdapter((ob, request), name='test4')
+        self.assertEqual(v.index(), 'V1 here')
+        self.assertEqual(v.action(), 'done')
+
+    def testDuplicatedInterfaceAndAttributeProtectedView(self):
+        xmlconfig.file("jsonrpc.zcml", jsonserver.tests)
+        v = getMultiAdapter((ob, request), name='test5')
+        self.assertEqual(v.index(), 'V1 here')
+        self.assertEqual(v.action(), 'done')
+
+    def testIncompleteProtectedViewNoPermission(self):
+        self.assertRaises(ConfigurationError, xmlconfig.file,
+                          "jsonrpc_error.zcml", jsonserver.tests)
+
+    def test_no_name(self):
+        xmlconfig.file("jsonrpc.zcml", jsonserver.tests)
+        v = getMultiAdapter((ob, request), name='index')
+        self.assertEqual(v(), 'V1 here')
+        v = getMultiAdapter((ob, request), name='action')
+        self.assertEqual(v(), 'done')
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(DirectivesTest),
+        ))
+
+if __name__ == '__main__':
+    unittest.main()

Added: z3/jsonserver/trunk/tests/test_httpfactory.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/tests/test_httpfactory.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,76 @@
+##############################################################################
+#
+# Copyright (c) 2003 - 2005 Zope Corporation and Contributors. 
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Tests for the JSONRPC Publication Request Factory.
+
+modified from zope.app.publication.tests.test_requestpublicationfactories.py jwashin 2005-11-06
+
+"""
+from unittest import TestCase, TestSuite, main, makeSuite
+
+from StringIO import StringIO
+from zope import component,interface
+from zope.publisher.browser import BrowserRequest
+from zope.publisher.http import HTTPRequest
+from jsonserver.jsonrpc import JSONRPCRequest
+from zope.component.tests.placelesssetup import PlacelessSetup
+from jsonserver.interfaces import IJSONRPCRequestFactory
+from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
+from zope.app.publication.browser import BrowserPublication
+from zope.app.publication.http import HTTPPublication
+from jsonserver.requestpublicationfactory import JSONRPCFactory
+from jsonserver.jsonrpc import JSONRPCPublication
+
+from zope.app.testing import ztapi
+
+class DummyRequestFactory(object):
+    def __call__(self, input_stream, env):
+        self.input_stream = input_stream
+        self.env = env
+        return self
+
+    def setPublication(self, pub):
+        self.pub = pub
+
+class Test(PlacelessSetup, TestCase):
+
+    def setUp(self):
+        super(Test, self).setUp()
+        self.__factory = HTTPPublicationRequestFactory(None)
+        self.__env =  {
+            'SERVER_URL':         'http://127.0.0.1',
+            'HTTP_HOST':          '127.0.0.1',
+            'CONTENT_LENGTH':     '0',
+            'GATEWAY_INTERFACE':  'TestFooInterface/1.0'
+            }
+
+    def test_jsonrpcfactory(self):
+        jsonrpcrequestfactory = DummyRequestFactory()
+        interface.directlyProvides(
+            jsonrpcrequestfactory, IJSONRPCRequestFactory)
+        component.provideUtility(jsonrpcrequestfactory)
+        env = self.__env
+        factory = JSONRPCFactory()
+        self.assertEqual(factory.canHandle(env), True)
+        request, publication = factory()
+        self.assertEqual(isinstance(request, DummyRequestFactory), True)
+        self.assertEqual(publication, JSONRPCPublication)
+
+
+def test_suite():
+    return TestSuite((
+        makeSuite(Test),
+        ))
+
+if __name__=='__main__':
+    main(defaultTest='test_suite')

Added: z3/jsonserver/trunk/tests/test_json.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/tests/test_json.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,463 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2005 Jim Washington and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""JSON Tests
+jwashin 2005-08-18
+"""
+import unittest
+
+from jsonserver import minjson as json
+from jsonserver.minjson import ReadException, WriteException
+
+def spaceless(aString):
+    return aString.replace(' ','')
+
+class JSONTests(unittest.TestCase):
+
+    def testReadString(self):
+        s = u"'hello'"
+        self.assertEqual(json.read(s) ,'hello')
+
+    def testWriteString(self):
+        s = 'hello'
+        self.assertEqual(json.write(s), '"hello"')
+
+    def testReadInt(self):
+        s = u"1"
+        self.assertEqual(json.read(s), 1)
+
+    def testWriteInt(self):
+        s = 1
+        self.assertEqual(json.write(s), "1")
+
+    def testReadLong(self):
+        s = u"999999999999999999999"
+        self.assertEqual(json.read(s), 999999999999999999999)
+
+    def testWriteShortLong(self):
+        s = 1L
+        self.assertEqual(json.write(s), "1")
+
+    def testWriteLongLong(self):
+        s = 999999999999999999999L
+        self.assertEqual(json.write(s), "999999999999999999999")
+
+    def testReadNegInt(self):
+        s = u"-1"
+        assert json.read(s) == -1
+
+    def testWriteNegInt(self):
+        s = -1
+        assert json.write(s) == '-1'
+
+    def testReadFloat(self):
+        s = u"1.334"
+        assert json.read(s) == 1.334
+
+    def testReadEFloat1(self):
+        s = u"1.334E2"
+        assert json.read(s) == 133.4
+
+    def testReadEFloat2(self):
+        s = u"1.334E-02"
+        assert json.read(s) == 0.01334
+
+    def testReadeFloat1(self):
+        s = u"1.334e2"
+        assert json.read(s) == 133.4
+
+    def testReadeFloat2(self):
+        s = u"1.334e-02"
+        assert json.read(s) == 0.01334
+
+    def testWriteFloat(self):
+        s = 1.334
+        assert json.write(s) == "1.334"
+
+    def testWriteDecimal(self):
+        try:
+            from decimal import Decimal
+            s = Decimal('1.33')
+            assert json.write(s) == "1.33"
+        except ImportError:
+            pass
+
+    def testReadNegFloat(self):
+        s = u"-1.334"
+        assert json.read(s) == -1.334
+
+    def testWriteNegFloat(self):
+        s = -1.334
+        assert json.write(s) == "-1.334"
+
+    def testReadEmptyDict(self):
+        s = u"{}"
+        assert json.read(s) == {}
+
+    def testWriteEmptyList(self):
+        s = []
+        assert json.write(s) == "[]"
+
+    def testWriteEmptyTuple(self):
+        s = ()
+        assert json.write(s) == "[]"
+
+    def testReadEmptyList(self):
+        s = u"[]"
+        assert json.read(s) == []
+
+    def testWriteEmptyDict(self):
+        s = {}
+        assert json.write(s) == '{}'
+
+    def testReadTrue(self):
+        s = u"true"
+        assert json.read(s) == True
+
+    def testWriteTrue(self):
+        s = True
+        assert json.write(s) == "true"
+
+    def testReadStringTrue(self):
+        s = u'"true"'
+        assert json.read(s) == 'true'
+
+    def testWriteStringTrue(self):
+        s = "True"
+        assert json.write(s) == '"True"'
+
+    def testReadStringNull(self):
+        s = u'"null"'
+        assert json.read(s) == 'null'
+
+    def testWriteStringNone(self):
+        s = "None"
+        assert json.write(s) == '"None"'
+
+    def testReadFalse(self):
+        s = u"false"
+        assert json.read(s) == False
+
+    def testWriteFalse(self):
+        s = False
+        assert json.write(s) == 'false'
+
+    def testReadNull(self):
+        s = u"null"
+        assert json.read(s) == None
+
+    def testWriteNone(self):
+        s = None
+        assert json.write(s) == "null"
+
+    def testReadDictOfLists(self):
+        s = u"{'a':[],'b':[]}"
+        assert json.read(s) == {'a':[],'b':[]}
+
+    def testReadDictOfListsWithSpaces(self):
+        s = u"{  'a' :    [],  'b'  : []  }    "
+        assert json.read(s) == {'a':[],'b':[]}
+
+    def testWriteDictOfLists(self):
+        s = {'a':[],'b':[]}
+        assert spaceless(json.write(s)) == '{"a":[],"b":[]}'
+
+    def testWriteDictOfTuples(self):
+        s = {'a':(),'b':()}
+        assert spaceless(json.write(s)) == '{"a":[],"b":[]}'
+
+    def testWriteDictWithNonemptyTuples(self):
+        s = {'a':('fred',7),'b':('mary',1.234)}
+        w = json.write(s)
+        assert spaceless(w) == '{"a":["fred",7],"b":["mary",1.234]}'
+
+    def testWriteVirtualTuple(self):
+        s = 4,4,5,6
+        w = json.write(s)
+        assert spaceless(w) == '[4,4,5,6]'
+
+    def testReadListOfDicts(self):
+        s = u"[{},{}]"
+        assert json.read(s) == [{},{}]
+
+    def testReadListOfDictsWithSpaces(self):
+        s = u" [ {    } ,{   \n} ]   "
+        assert json.read(s) == [{},{}]
+
+    def testWriteListOfDicts(self):
+        s = [{},{}]
+        assert spaceless(json.write(s)) == "[{},{}]"
+
+    def testWriteTupleOfDicts(self):
+        s = ({},{})
+        assert spaceless(json.write(s)) == "[{},{}]"
+
+    def testReadListOfStrings(self):
+        s = u"['a','b','c']"
+        assert json.read(s) == ['a','b','c']
+
+    def testReadListOfStringsWithSpaces(self):
+        s = u" ['a'    ,'b'  ,\n  'c']  "
+        assert json.read(s) == ['a','b','c']
+
+    def testReadStringWithWhiteSpace(self):
+        s = ur"'hello \tworld'"
+        assert json.read(s) == 'hello \tworld'
+
+    def testWriteMixedList(self):
+        o = ['OIL',34,199L,38.5]
+        assert spaceless(json.write(o)) == '["OIL",34,199,38.5]'
+
+    def testWriteMixedTuple(self):
+        o = ('OIL',34,199L,38.5)
+        assert spaceless(json.write(o)) == '["OIL",34,199,38.5]'
+
+    def testWriteStringWithWhiteSpace(self):
+        s = 'hello \tworld'
+        assert json.write(s) == r'"hello \tworld"'
+
+    def testWriteListofStringsWithApostrophes(self):
+        s = ["hasn't","don't","isn't",True,"won't"]
+        w = json.write(s)
+        assert spaceless(w) == '["hasn\'t","don\'t","isn\'t",true,"won\'t"]'
+
+    def testWriteTupleofStringsWithApostrophes(self):
+        s = ("hasn't","don't","isn't",True,"won't")
+        w = json.write(s)
+        assert spaceless(w) == '["hasn\'t","don\'t","isn\'t",true,"won\'t"]'
+
+    def testWriteListofStringsWithRandomQuoting(self):
+        s = ["hasn't","do\"n't","isn't",True,"wo\"n't"]
+        w = json.write(s)
+        assert "true" in w
+
+    def testWriteStringWithDoubleQuote(self):
+        s = "do\"nt"
+        w = json.write(s)
+        assert w == '"do\\\"nt"'
+
+    def testReadDictWithSlashStarComments(self):
+        s = """
+        {'a':false,  /*don't want b
+            b:true, */
+        'c':true
+        }
+        """
+        assert json.read(s) == {'a':False,'c':True}
+
+    def testReadDictWithTwoSlashStarComments(self):
+        s = """
+        {'a':false,  /*don't want b
+            b:true, */
+        'c':true,
+        'd':false,  /*don;t want e
+            e:true, */
+        'f':true
+        }
+        """
+        assert json.read(s) == {'a':False,'c':True, 'd':False,'f':True}
+
+    def testReadDictWithDoubleSlashComments(self):
+        s = """
+        {'a':false,
+          //  b:true, don't want b
+        'c':true
+        }
+        """
+        assert json.read(s) == {'a':False,'c':True}
+
+    def testReadStringWithEscapedSingleQuote(self):
+        s = '"don\'t tread on me."'
+        assert json.read(s) == "don't tread on me."
+
+    def testWriteStringWithEscapedDoubleQuote(self):
+        s = 'he said, \"hi.'
+        t = json.write(s)
+        assert json.write(s) == '"he said, \\\"hi."'
+
+    def testReadStringWithEscapedDoubleQuote(self):
+        s = r'"She said, \"Hi.\""'
+        assert json.read(s) == 'She said, "Hi."'
+
+    def testReadStringWithNewLine(self):
+        s = r'"She said, \"Hi,\"\n to which he did not reply."'
+        assert json.read(s) == 'She said, "Hi,"\n to which he did not reply.'
+
+    def testReadNewLine(self):
+        s = r'"\n"'
+        assert json.read(s) == '\n'
+
+    def testWriteNewLine(self):
+        s = u'\n'
+        assert json.write(s) == r'"\n"'
+
+    def testWriteSimpleUnicode(self):
+        s = u'hello'
+        assert json.write(s) == '"hello"'
+
+    def testReadBackSlashuUnicode(self):
+        s = u'"\u0066"'
+        assert json.read(s) == 'f'
+
+    def testReadBackSlashuUnicodeInDictKey(self):
+        s = u'{"\u0066ather":34}'
+        assert json.read(s) == {'father':34}
+
+    def testReadDictKeyWithBackSlash(self):
+        s = u'{"mo\\use":22}'
+        self.assertEqual(json.read(s) , {r'mo\use':22})
+
+    def testWriteDictKeyWithBackSlash(self):
+        s = {"mo\\use":22}
+        self.assertEqual(json.write(s) , r'{"mo\\use":22}')
+
+    def testWriteListOfBackSlashuUnicodeStrings(self):
+        s = [u'\u20ac',u'\u20ac',u'\u20ac']
+        self.assertEqual(spaceless(json.write(s)) ,u'["\u20ac","\u20ac","\u20ac"]')
+
+    def testWriteUnicodeCharacter(self):
+        s = json.write(u'\u1001', 'ascii')
+        self.assertEqual(u'"\u1001"', s)
+
+    def testWriteUnicodeCharacter1(self):
+        s = json.write(u'\u1001', 'ascii',outputEncoding='ascii')
+        self.assertEqual(r'"\u1001"', s)
+
+    def testWriteHexUnicode(self):
+        s = unicode('\xff\xfe\xbf\x00Q\x00u\x00\xe9\x00 \x00p\x00a\x00s\x00a\x00?\x00','utf-16')
+        p = json.write(s, 'latin-1', outputEncoding="latin-1")
+        self.assertEqual(unicode(p,'latin-1'), u'"¿Qué pasa?"')
+
+    def testWriteHexUnicode1(self):
+        s = unicode('\xff\xfe\xbf\x00Q\x00u\x00\xe9\x00 \x00p\x00a\x00s\x00a\x00?\x00','utf-16')
+        p = json.write(s, 'latin-1')
+        self.assertEqual(p, u'"¿Qué pasa?"')
+
+    def testWriteDosPath(self):
+        s = 'c:\\windows\\system'
+        assert json.write(s) == r'"c:\\windows\\system"'
+
+    def testWriteDosPathInList(self):
+        s = ['c:\windows\system','c:\\windows\\system',r'c:\windows\system']
+        self.assertEqual(json.write(s) , r'["c:\\windows\\system","c:\\windows\\system","c:\\windows\\system"]')
+
+
+    def readImportExploit(self):
+        s = ur"\u000aimport('os').listdir('.')"
+        json.read(s)
+
+    def testImportExploit(self):
+        self.assertRaises(ReadException, self.readImportExploit)
+
+    def readClassExploit(self):
+        s = ur'''"__main__".__class__.__subclasses__()'''
+        json.read(s)
+
+    def testReadClassExploit(self):
+        self.assertRaises(ReadException, self.readClassExploit)
+
+    def readBadJson(self):
+        s = "'DOS'*30"
+        json.read(s)
+
+    def testReadBadJson(self):
+        self.assertRaises(ReadException, self.readBadJson)
+
+    def readUBadJson(self):
+        s = ur"\u0027DOS\u0027*30"
+        json.read(s)
+
+    def testReadUBadJson(self):
+        self.assertRaises(ReadException, self.readUBadJson)
+
+    def testReadEncodedUnicode(self):
+        obj = "'La Peña'"
+        r = json.read(obj, 'utf-8')
+        self.assertEqual(r, unicode('La Peña','utf-8'))
+
+    def testUnicodeFromNonUnicode(self):
+        obj = "'\u20ac'"
+        r = json.read(obj)
+        self.assertEqual(r, u'\u20ac')
+
+    def testUnicodeInObjectFromNonUnicode(self):
+        obj = "['\u20ac', '\u20acCESS', 'my\u20ACCESS']"
+        r = json.read(obj)
+        self.assertEqual(r, [u'\u20AC', u'\u20acCESS', u'my\u20acCESS'])
+
+    def testWriteWithEncoding(self):
+        obj = u'La Peña'
+        r = json.write(obj,'latin-1',outputEncoding='latin-1')
+        self.assertEqual(unicode(r,'latin-1'), u'"La Peña"')
+
+    def testWriteWithEncodingBaseCases(self):
+        #input_uni =  u"'�rvíztŹr� tßkÜrfúrógÊp'"
+        input_uni = u'\xc1rv\xedzt\u0171r\u0151 t\xfck\xf6rf\xfar\xf3g\xe9p'
+        #print "input_uni is %s" % input_uni.encode('latin2')
+        # the result supposes doUxxxx = False
+        good_result = u'"\xc1rv\xedzt\u0171r\u0151 t\xfck\xf6rf\xfar\xf3g\xe9p"'
+        #
+        # from utf8
+        obj = input_uni.encode('utf-8')
+        r = json.write(obj, 'utf-8',outputEncoding='utf-8')
+        self.assertEqual(unicode(r,'utf-8'), good_result)
+        #
+        # from unicode
+        obj = input_uni
+        r = json.write(obj, outputEncoding='utf-8')
+        self.assertEqual(unicode(r,'utf-8'), good_result)
+        #
+        # from latin2
+        obj = input_uni.encode('latin2')
+        r = json.write(obj, 'latin2', outputEncoding='latin2')
+        self.assertEqual(unicode(r,'latin2'), good_result)
+        #
+        # from unicode, encoding is ignored
+        obj = input_uni
+        r = json.write(obj, 'latin2', outputEncoding='latin2')
+        self.assertEqual(unicode(r,'latin2'), good_result)
+        #
+        # same with composite types, uni
+        good_composite_result = \
+        u'["\xc1rv\xedzt\u0171r\u0151 t\xfck\xf6rf\xfar\xf3g\xe9p","\xc1rv\xedzt\u0171r\u0151 t\xfck\xf6rf\xfar\xf3g\xe9p"]'
+        #print "Good composite result = %s" % good_composite_result.encode('latin2')
+        obj = [input_uni, input_uni]
+        r = json.write(obj, outputEncoding='utf-8')
+        #print "r is %s, length is %s." % (r, len(r))
+        self.assertEqual(unicode(r,'utf-8'), good_composite_result)
+        #
+        # same with composite types, utf-8
+        obj = [input_uni.encode('utf-8'), input_uni.encode('utf-8')]
+        r = json.write(obj, 'utf-8')
+        # print unicode(r,'utf-8'), good_composite_result
+        #self.assertEqual(unicode(r,'utf-8'), good_composite_result)
+##        #
+        #
+##        # same with composite types, latin2
+        obj = [input_uni.encode('latin2'), input_uni.encode('latin2')]
+        r = json.write(obj, 'latin2')
+        #cannot assertEqual here, but the printed representation should be readable
+        #self.assertEqual(unicode(r,'latin2'), good_composite_result)
+        #
+##        # same with composite types, mixed utf-8 with unicode
+        obj = [input_uni, input_uni.encode('utf-8')]
+        r = json.write(obj, 'utf-8')
+        #cannot assertEqual here, but the printed representation should be readable
+        #self.assertEqual(unicode(r,'utf-8'), good_composite_result)
+def test_suite():
+    loader = unittest.TestLoader()
+    return loader.loadTestsFromTestCase(JSONTests)
+
+if __name__=='__main__':
+    unittest.TextTestRunner().run(test_suite())

Added: z3/jsonserver/trunk/tests/test_jsonrpcpublication.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/tests/test_jsonrpcpublication.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,135 @@
+##############################################################################
+#
+# Copyright (c) 2001 - 2005 Zope Corporation and Contributors. 
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""JSON-RPC Publication Tests
+
+modified from zope.app.publication.tests.test_xmlrpcpublication.py jwashin 2005-06-06
+
+"""
+import unittest
+
+from zope.app.publication.tests.test_zopepublication import \
+     BasePublicationTests
+from zope.app.publication.traversers import TestTraverser
+from jsonserver.jsonrpc import JSONRPCPublication
+from zope.interface import Interface, implements
+from zope.proxy import removeAllProxies
+from zope.publisher.interfaces import NotFound
+from jsonserver.interfaces import IJSONRPCPresentation
+from jsonserver.interfaces import IJSONRPCRequest
+from jsonserver.interfaces import IJSONRPCPublisher
+from jsonserver.jsonrpc import TestRequest
+from zope.app.testing import ztapi
+
+class SimpleObject(object):
+    def __init__(self, v):
+        self.v = v
+
+
+
+class JSONRPCPublicationTests(BasePublicationTests):
+
+    klass = JSONRPCPublication
+
+    def _createRequest(self, path, publication, **kw):
+        request = TestRequest(PATH_INFO=path, **kw)
+        request.setPublication(publication)
+        return request
+
+    def testTraverseName(self):
+        pub = self.klass(self.db)
+        class C(object):
+            x = SimpleObject(1)
+        ob = C()
+        r = self._createRequest('/x', pub)
+        ztapi.provideView(None, IJSONRPCRequest, IJSONRPCPublisher,
+                          '', TestTraverser)
+        ob2 = pub.traverseName(r, ob, 'x')
+        self.assertEqual(removeAllProxies(ob2).v, 1)
+
+    def testDenyDirectMethodAccess(self):
+        pub = self.klass(self.db)
+        class I(Interface):
+            pass
+
+        class C(object):
+            implements(I)
+
+            def foo(self):
+                return 'bar'
+
+        class V(object):
+            def __init__(self, context, request):
+                pass
+            implements(IJSONRPCPresentation)
+
+        ob = C()
+        r = self._createRequest('/foo', pub)
+
+        ztapi.provideView(I, IJSONRPCPresentation, Interface, 'view', V)
+        ztapi.setDefaultViewName(I, 'view', type=IJSONRPCPresentation)
+        self.assertRaises(NotFound, pub.traverseName, r, ob, 'foo')
+
+
+    def testTraverseNameView(self):
+        pub = self.klass(self.db)
+
+        class I(Interface):
+            pass
+
+        class C(object):
+            implements(I)
+
+        ob = C()
+
+        class V(object):
+            def __init__(self, context, request):
+                pass
+            implements(IJSONRPCPresentation)
+
+
+        # Register the simple traverser so we can traverse without @@
+        from jsonserver.jsonrpc import IJSONRPCPublisher
+        from jsonserver.interfaces import IJSONRPCRequest
+        from zope.app.publication.traversers import SimpleComponentTraverser
+        ztapi.provideView(Interface, IJSONRPCRequest, IJSONRPCPublisher, '',
+                          SimpleComponentTraverser)
+
+        r = self._createRequest('/@@spam', pub)
+        ztapi.provideView(I, IJSONRPCRequest, Interface, 'spam', V)
+        ob2 = pub.traverseName(r, ob, '@@spam')
+        self.assertEqual(removeAllProxies(ob2).__class__, V)
+
+        ob2 = pub.traverseName(r, ob, 'spam')
+        self.assertEqual(removeAllProxies(ob2).__class__, V)
+
+
+    def testTraverseNameSiteManager(self):
+        pub = self.klass(self.db)
+        class C(object):
+            def getSiteManager(self):
+                return SimpleObject(1)
+        ob = C()
+        r = self._createRequest('/++etc++site',pub)
+        ob2 = pub.traverseName(r, ob, '++etc++site')
+        self.assertEqual(removeAllProxies(ob2).v, 1)
+        
+        
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(JSONRPCPublicationTests),
+        ))
+
+if __name__ == '__main__':
+    unittest.main()

Added: z3/jsonserver/trunk/tests/test_jsonrpcrequest.py
==============================================================================
--- (empty file)
+++ z3/jsonserver/trunk/tests/test_jsonrpcrequest.py	Wed Mar  8 13:37:02 2006
@@ -0,0 +1,156 @@
+##############################################################################
+#
+# Copyright (c) 2001 - 2005 Zope Corporation and Contributors. 
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""JSON-RPC Request Tests
+modified from zope/publisher/tests/test_xmlrpcrequest.py jwashin 2005-06-06
+"""
+import unittest
+from StringIO import StringIO
+
+from zope.publisher.base import DefaultPublication
+from zope.publisher.http import HTTPCharsets
+from jsonserver.jsonrpc import JSONRPCRequest
+from zope.app.testing import ztapi
+from jsonserver.jsoncomponent import JSONReader, JSONWriter
+from jsonserver.interfaces import IJSONReader, IJSONWriter
+
+class Publication(DefaultPublication):
+
+    require_docstrings = 0
+
+    def getDefaultTraversal(self, request, ob):
+        if hasattr(ob, 'browserDefault'):
+            return ob.browserDefault(request)
+        return ob, ()
+
+
+class TestJSONRPCRequest(JSONRPCRequest, HTTPCharsets):
+    """Make sure that our request also implements IHTTPCharsets, so that we do
+    not need to register any adapters."""
+
+    def __init__(self, *args, **kw):
+        self.request = self
+        JSONRPCRequest.__init__(self, *args, **kw)
+
+class TestCall:
+    def __init__(self):
+        self.body = '{"id":"httpReq","method":"action","params":[1]}'
+        self.headers = []
+
+#jsonrpc_call = JSONWriter().output({'id':'httpReq','method':'action','params':[1]})
+jsonrpc_call = TestCall()
+
+class ParamTestCall:
+    def __init__(self):
+        self.body = '{"id":"httpReq","method":"keyworded","params":[1,{"pythonKwMaRkEr":{"kw1":"aaa"}}]}'
+        self.headers = []
+
+class JSONRPCTests(unittest.TestCase):
+    """The only thing different to HTTP is the input processing; so there
+       is no need to redo all the HTTP tests again.
+    """
+
+    _testEnv =  {
+        'PATH_INFO':          '/folder/item2/view/',
+        'QUERY_STRING':       '',
+        'SERVER_URL':         'http://foobar.com',
+        'HTTP_HOST':          'foobar.com',
+        'CONTENT_LENGTH':     '0',
+        'REQUEST_METHOD':     'POST',
+        'HTTP_AUTHORIZATION': 'Should be in accessible',
+        'GATEWAY_INTERFACE':  'TestFooInterface/1.0',
+        'HTTP_OFF_THE_WALL':  "Spam 'n eggs",
+        'HTTP_ACCEPT_CHARSET': 'ISO-8859-1, UTF-8;q=0.66, UTF-16;q=0.33',
+    }
+
+    def setUp(self):
+        super(JSONRPCTests, self).setUp()
+
+        class AppRoot(object):
+            pass
+
+        class Folder(object):
+            pass
+
+        class Item(object):
+
+            def __call__(self, a, b):
+                return "%s, %s" % (`a`, `b`)
+
+            def doit(self, a, b):
+                return 'do something %s %s' % (a, b)
+
+        class View(object):
+
+            def action(self, a):
+                return "Parameter[type: %s; value: %s" %(
+                    type(a).__name__, `a`)
+                    
+            def keyworded(self, a, kw1="spam"):
+                return "kw1: [type: %s; value: %s]" %(
+                    type(kw1).__name__, `kw1`)
+            
+        class Item2(object):
+            view = View()
+
+        self.app = AppRoot()
+        self.app.folder = Folder()
+        self.app.folder.item = Item()
+        self.app.folder.item2 = Item2()
+        ztapi.provideUtility(IJSONReader,JSONReader())
+
+    def _createRequest(self, extra_env={}, body=""):
+        env = self._testEnv.copy()
+        env.update(extra_env)
+        if len(body.body):
+            env['CONTENT_LENGTH'] = str(len(body.body))
+
+        publication = Publication(self.app)
+        instream = StringIO(body.body)
+        request = TestJSONRPCRequest(instream, env)
+        request.setPublication(publication)
+        return request
+
+
+    def testProcessInput(self):
+        req = self._createRequest({}, jsonrpc_call)
+        req.processInputs()
+        self.failUnlessEqual(req._args, (1,))
+        self.failUnlessEqual(tuple(req._path_suffix), ('action',))
+
+
+    def testTraversal(self):
+        req = self._createRequest({}, jsonrpc_call)
+        req.processInputs()
+        action = req.traverse(self.app)
+        self.failUnlessEqual(action(*req._args),
+                             "Parameter[type: int; value: 1")
+                             
+