You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@abdera.apache.org by jm...@apache.org on 2008/02/09 21:13:45 UTC

svn commit: r620186 - in /incubator/abdera/java/trunk/adapters/couchdb: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/abdera/ src/main/java/org/apache/abdera/protocol/ src/main/java/org/apache/ab...

Author: jmsnell
Date: Sat Feb  9 12:13:43 2008
New Revision: 620186

URL: http://svn.apache.org/viewvc?rev=620186&view=rev
Log:
simple couchdb adapter.  see the readme.  this has not been incorporated into the build
or fully tested yet.  treat this as experimental for now.

Added:
    incubator/abdera/java/trunk/adapters/couchdb/
    incubator/abdera/java/trunk/adapters/couchdb/readme
    incubator/abdera/java/trunk/adapters/couchdb/src/
    incubator/abdera/java/trunk/adapters/couchdb/src/main/
    incubator/abdera/java/trunk/adapters/couchdb/src/main/java/
    incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/
    incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/
    incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/
    incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/
    incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/
    incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/
    incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/
    incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/CouchDbAdapter.java
    incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/JsonObjectResponseContext.java
    incubator/abdera/java/trunk/adapters/couchdb/src/test/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/java/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/abdera/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/abdera/protocol/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/abdera/protocol/server/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/abdera/protocol/server/test/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/abdera/protocol/server/test/couchdb/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/abdera/protocol/server/test/couchdb/CouchDbTest.java
    incubator/abdera/java/trunk/adapters/couchdb/src/test/resources/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/resources/abdera/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/resources/abdera/adapter/
    incubator/abdera/java/trunk/adapters/couchdb/src/test/resources/abdera/adapter/couchdb.properties

Added: incubator/abdera/java/trunk/adapters/couchdb/readme
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/adapters/couchdb/readme?rev=620186&view=auto
==============================================================================
--- incubator/abdera/java/trunk/adapters/couchdb/readme (added)
+++ incubator/abdera/java/trunk/adapters/couchdb/readme Sat Feb  9 12:13:43 2008
@@ -0,0 +1,50 @@
+CouchDB Collection Adapter
+
+Dependencies: 
+
+  * Abdera Core, Parser, Protocol, Server modules
+  * CouchDb4J (http://www.couchdbwiki.com/index.php?title=Getting_Started_with_Java)
+  
+Using the Adapter:
+
+  The CouchDB adapter is based on Abdera Managed Adapter model.  To deploy an 
+  instance of the adapter, put a file called /abdera/adapter/[id].properties
+  in the classpath where [id] is the id of the service instance.  For instance,
+  /abdera/adapter/couchdb.properties
+  
+  
+Sample *.properties file
+
+  subUri=couchdb
+  adapterClassName=org.apache.abdera.protocol.server.adapters.couchdb.CouchDbAdapter
+  title=CouchDB Feed
+  author=james
+  configFile=dummyFileLocation
+
+  # uncomment to override the defaults
+  #couchdb.host=localhost
+  #couchdb.port=5984
+  
+Then, deploy the AbderaServlet using the BasicProvider in your Web application environment:
+
+Jetty deployment example:
+
+    Server server = new Server(9002);
+    Context context = new Context(server, "/", Context.SESSIONS);
+    ServletHolder servletHolder = new ServletHolder(new AbderaServlet());
+    servletHolder.setInitParameter(ServiceManager.PROVIDER, BasicAdapter.class.getName());
+    context.addServlet(servletHolder, "/*");
+    server.start();
+    
+Using this example, the following resources will be served:
+
+    http://localhost:9002/                ==>  Atompub Service Document
+    http://localhost:9002/couchdb         ==>  Atompub Collection Feed
+    http://localhost:9002/couchdb/{entry} ==>  Entry document
+    
+Limitations:
+
+  The adapter does not support media collections or feed paging. Persistence
+  of extension elements has not yet been fully tested.
+  
+  
\ No newline at end of file

Added: incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/CouchDbAdapter.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/CouchDbAdapter.java?rev=620186&view=auto
==============================================================================
--- incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/CouchDbAdapter.java (added)
+++ incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/CouchDbAdapter.java Sat Feb  9 12:13:43 2008
@@ -0,0 +1,291 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.protocol.server.adapters.couchdb;
+
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.sf.json.JSONObject;
+
+import org.apache.abdera.Abdera;
+import org.apache.abdera.i18n.text.Normalizer;
+import org.apache.abdera.i18n.text.Sanitizer;
+import org.apache.abdera.model.AtomDate;
+import org.apache.abdera.model.Entry;
+import org.apache.abdera.model.Link;
+import org.apache.abdera.protocol.server.ProviderHelper;
+import org.apache.abdera.protocol.server.RequestContext;
+import org.apache.abdera.protocol.server.ResponseContext;
+import org.apache.abdera.protocol.server.Target;
+import org.apache.abdera.protocol.server.provider.managed.FeedConfiguration;
+import org.apache.abdera.protocol.server.provider.managed.ManagedCollectionAdapter;
+
+import com.fourspaces.couchdb.Database;
+import com.fourspaces.couchdb.Document;
+import com.fourspaces.couchdb.Session;
+import com.fourspaces.couchdb.ViewResults;
+
+public class CouchDbAdapter 
+  extends ManagedCollectionAdapter {
+
+  private final DocumentComparator sorter = new DocumentComparator();
+  private final String host;
+  private final int port;
+  
+  public CouchDbAdapter(
+    Abdera abdera, 
+    FeedConfiguration config) {
+      this(
+        abdera,
+        config,
+        null,
+        -1);
+  }
+  
+  public CouchDbAdapter(
+    Abdera abdera, 
+    FeedConfiguration config,
+    String host, 
+    int port) {
+      super(abdera, config);
+      if (host == null) { 
+        host = (String)config.getProperty("couchdb.host");
+        if (host == null) host = "localhost";
+      }
+      if (port < 0) {
+        try {
+          port = Integer.parseInt((String)config.getProperty("couchdb.port"));
+        } catch (Exception e) {}
+        if (port < 0) port = 5984;
+      }
+      this.host = host;
+      this.port = port;
+  }
+  
+  private synchronized Database getDatabase(
+    RequestContext request) {
+      String name = request.getTarget().getParameter("feed");
+      Session session = new Session(host,port);
+      Database db = null;
+      try {
+        db = session.getDatabase(name);
+      } catch (Exception e) {}
+      if (db == null)
+        db = session.createDatabase(name);
+      return db;
+  }
+  
+  public ResponseContext getFeed(
+    RequestContext request) {
+      Database db = getDatabase(request);
+      ViewResults res = db.getAllDocuments();
+      List<Document> entries = new ArrayList<Document>();
+      List<Document> docs = res.getResults();
+      for (Document doc : docs) {
+        entries.add(
+          db.getDocument(doc.getString("id")));
+      }
+      Collections.sort(entries, sorter);
+      return new JsonObjectResponseContext(
+        request.getAbdera(),
+        config,
+        res,
+        entries.toArray(new Document[entries.size()]))
+          .setStatus(200)
+          .setEntityTag(res.getRev());
+  }
+      
+  public ResponseContext deleteEntry(
+    RequestContext request) {
+      Target target = request.getTarget();
+      String feed = target.getParameter("feed");
+      String entry = target.getParameter("entry");
+      Session session = new Session(host,port);
+      Database db = session.getDatabase(feed);
+      Document doc = db.getDocument(entry);
+      if (doc != null) {
+        db.deleteDocument(doc);
+        return ProviderHelper.nocontent();
+      } else 
+        return ProviderHelper.notfound(request);
+  }
+
+  private void setEditDetail(RequestContext request) throws IOException {
+    Target target = request.getTarget();
+    String feed = target.getParameter("feed");
+    String id = target.getParameter("entry");
+    Entry entry = (Entry) request.getDocument().getRoot();
+    entry.setEdited(new Date());
+    Link link = entry.getEditLink();
+    Map<String,Object> params = new HashMap<String,Object>();
+    params.put("feed", feed);
+    params.put("entry", id);
+    String href = request.absoluteUrlFor("entry", params);
+    if (link == null) {
+      entry.addLink(href, "edit");
+    } else {
+      link.setHref(href);
+    }
+  }
+  
+  public ResponseContext postEntry(
+    RequestContext request) {
+      Target target = request.getTarget();
+      String feed = target.getParameter("feed");
+      Session session = new Session(host,port);
+      Database db = session.getDatabase(feed);
+      try {
+        CharArrayWriter cwriter = new CharArrayWriter();
+        setEditDetail(request);
+        request.getDocument().getRoot().writeTo("json", cwriter);
+        String json = new String(cwriter.toCharArray());
+        JSONObject obj = JSONObject.fromObject(json);
+        String key = createKey(request);
+        Document doc = null;
+        try {
+          doc = db.getDocument(key);
+        } catch (Exception e) {}
+        if (doc != null) { 
+          return ProviderHelper.conflict(
+            request, 
+            "Entry with that key already exists");
+        } else {
+          doc = new Document(obj);
+          doc.setId(key);
+          db.saveDocument(doc);
+          doc = db.getDocument(key);
+          if (doc != null) {
+            Map<String,Object> params = new HashMap<String,Object>();
+            params.put("feed", feed);
+            params.put("entry", key);
+            String urlFor = request.absoluteUrlFor("entry", params);
+            return new JsonObjectResponseContext(
+              request.getAbdera(),
+              config,
+              doc)
+                .setStatus(201)
+                .setEntityTag(doc.getRev())
+                .setLocation(urlFor);
+          } else { 
+            return ProviderHelper.servererror(request, null);
+          }
+        }
+      } catch (IOException e) {
+        return ProviderHelper.servererror(request, e);
+      }
+  }
+  
+  private String createKey(RequestContext request) throws IOException {
+    String slug = request.getSlug();
+    if (slug == null) {
+      slug = ((Entry)request.getDocument().getRoot()).getTitle();
+    }
+    return Sanitizer.sanitize(slug, "", true, Normalizer.Form.D);
+  }
+  
+  public ResponseContext getEntry(
+    RequestContext request) {
+      Target target = request.getTarget();
+      String feed = target.getParameter("feed");
+      String entry = target.getParameter("entry");
+      Session session = new Session(host,port);
+      Database db = session.getDatabase(feed);
+      Document doc = db.getDocument(entry);
+      if (doc != null)
+        return new JsonObjectResponseContext(
+          request.getAbdera(),
+          config,
+          doc)
+            .setStatus(200)
+            .setEntityTag(doc.getRev());
+      else 
+        return ProviderHelper.notfound(request);
+  }
+  
+  public ResponseContext putEntry(
+    RequestContext request) {
+      Target target = request.getTarget();
+      String feed = target.getParameter("feed");
+      Session session = new Session(host,port);
+      Database db = session.getDatabase(feed);
+      try {
+        CharArrayWriter cwriter = new CharArrayWriter();
+        setEditDetail(request);
+        request.getDocument().getRoot().writeTo("json", cwriter);
+        String json = new String(cwriter.toCharArray());
+        JSONObject obj = JSONObject.fromObject(json);
+        String key = target.getParameter("entry");
+        Document doc = null;
+        try {
+          doc = db.getDocument(key);
+        } catch (Exception e) {}
+        if (doc == null) { 
+          return ProviderHelper.notfound(request);
+        } else {
+          db.deleteDocument(doc);
+          doc = new Document(obj);
+          doc.setId(key);
+          db.saveDocument(doc);
+          doc = db.getDocument(key);
+          if (doc != null)
+            return new JsonObjectResponseContext(
+              request.getAbdera(),
+              config,
+              doc)
+                .setStatus(200)
+                .setEntityTag(doc.getRev());
+          else 
+            return ProviderHelper.servererror(request, null);
+        }
+      } catch (IOException e) {
+        return ProviderHelper.servererror(request, e);
+      }
+  }
+
+  public ResponseContext getCategories(
+    RequestContext request) {
+      return ProviderHelper.notfound(request);
+  }
+    
+  public ResponseContext extensionRequest(
+    RequestContext request) {
+    return ProviderHelper.notfound(request);
+  }
+
+  private static class DocumentComparator 
+    implements Comparator<Document> {
+      public int compare(
+        Document doc1, 
+        Document doc2) {
+          String ed1 = doc1.getString("edited");
+          String ed2 = doc2.getString("edited");
+          Date d1 = AtomDate.parse(ed1);
+          Date d2 = AtomDate.parse(ed2);
+          if (d1.before(d2)) return 1;
+          else if (d1.after(d2)) return -1;
+          else return 0;
+      }
+  }
+}

Added: incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/JsonObjectResponseContext.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/JsonObjectResponseContext.java?rev=620186&view=auto
==============================================================================
--- incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/JsonObjectResponseContext.java (added)
+++ incubator/abdera/java/trunk/adapters/couchdb/src/main/java/org/apache/abdera/protocol/server/adapters/couchdb/JsonObjectResponseContext.java Sat Feb  9 12:13:43 2008
@@ -0,0 +1,395 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  The ASF licenses this file to You
+* under the Apache License, Version 2.0 (the "License"); you may not
+* use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.  For additional information regarding
+* copyright in this work, please see the NOTICE file in the top level
+* directory of this distribution.
+*/
+package org.apache.abdera.protocol.server.adapters.couchdb;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.Map;
+
+import javax.xml.namespace.QName;
+
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+
+import org.apache.abdera.Abdera;
+import org.apache.abdera.model.Content;
+import org.apache.abdera.model.Text;
+import org.apache.abdera.protocol.server.context.StreamWriterResponseContext;
+import org.apache.abdera.protocol.server.provider.managed.FeedConfiguration;
+import org.apache.abdera.util.Constants;
+import org.apache.abdera.writer.StreamWriter;
+
+import com.fourspaces.couchdb.Document;
+
+public class JsonObjectResponseContext 
+  extends StreamWriterResponseContext {
+  
+  private final FeedConfiguration config;
+  private final Document doc;
+  private final Document[] entries;
+
+  protected JsonObjectResponseContext(
+    Abdera abdera,
+    FeedConfiguration config,
+    Document doc) {
+      this(abdera,config,doc,(Document[])null);
+  }
+  
+  protected JsonObjectResponseContext(
+    Abdera abdera,
+    FeedConfiguration config,
+    Document doc,
+    Document... entries) {
+      super(abdera);
+      this.doc = doc;
+      this.entries = entries;
+      this.config = config;
+      setContentType(
+        entries != null ? 
+          "application/atom+xml" :
+          "application/atom+xml;type=entry");
+  }
+
+  protected JsonObjectResponseContext(
+    Abdera abdera, 
+    FeedConfiguration config,
+    String encoding,
+    Document doc,
+    Document... entries) {
+      super(abdera, encoding);
+      this.doc = doc;
+      this.entries = entries;
+      this.config = config;
+  }
+  
+  protected void writeTo(
+    StreamWriter sw) 
+      throws IOException {
+    sw.startDocument();
+    if (entries != null) 
+      writeJsonFeed(config,doc, entries, sw);
+    else  writeJsonEntry(doc, sw);
+    sw.endDocument();
+  }
+  
+  public boolean hasEntity() {
+    return true;
+  }
+
+  private static void writeJsonFeed(
+    FeedConfiguration config,
+    Document doc, 
+    Document[] entries,
+    StreamWriter writer) {
+      writer.startFeed();
+      writer.writeId(config.getServerConfiguration().getServerUri() + "/" + config.getFeedId())
+            .writeTitle(config.getFeedTitle())
+            .writeAuthor(config.getFeedAuthor())
+            .writeLink(config.getFeedUri())
+            .writeLink(config.getFeedUri(), "self")
+            .writeUpdated(new Date());
+      for (Document entry : entries)
+        writeJsonEntry(entry, writer);
+      writer.endFeed();
+  }
+  
+  private static void writeJsonEntry(
+    Document doc, 
+    StreamWriter writer) {
+      writer.startEntry();
+      writeJsonAttributes(doc.getJSONObject("attributes"), writer);
+      for (Object key : doc.keySet()) {
+        String name = (String) key;
+        if (!name.startsWith("_")) {
+          if (name.equals("id"))
+            writer.writeId(doc.getString(name));
+          else if (name.equals("title")) {
+            writeJsonTextElement(
+              Constants.TITLE, 
+              doc.get(name), 
+              writer);
+          } else if (name.equals("summary")) {
+            writeJsonTextElement(
+                Constants.SUMMARY, 
+                doc.get(name), 
+                writer);
+          } else if (name.equals("rights")) {
+            writeJsonTextElement(
+                Constants.RIGHTS, 
+                doc.get(name), 
+                writer);
+          } else if (name.equals("links")) {
+            JSONArray array = doc.getJSONArray(name);
+            for (int n = 0; n < array.size(); n++) {
+              JSONObject obj = array.getJSONObject(n);
+              writer.startElement(Constants.LINK);
+              writeJsonAttributes(obj, writer);
+              if (obj.containsKey("extensions"))
+                writeJsonExtensions(obj.getJSONArray("extensions"), writer);
+              writer.endElement();
+            }
+          } else if (name.equals("categories")) {
+            JSONArray array = doc.getJSONArray(name);
+            for (int n = 0; n < array.size(); n++) {
+              JSONObject obj = array.getJSONObject(n);
+              writer.startElement(Constants.CATEGORY);
+              writeJsonAttributes(obj, writer);
+              if (obj.containsKey("extensions"))
+                writeJsonExtensions(obj.getJSONArray("extensions"), writer);
+              writer.endElement();
+            }            
+          } else if (name.equals("authors")) {
+            JSONArray array = doc.getJSONArray(name);
+            for (int n = 0; n < array.size(); n++) {
+              JSONObject obj = array.getJSONObject(n);
+              writeJsonPerson(Constants.AUTHOR, obj, writer);
+            }
+          } else if (name.equals("contributors")) {
+            JSONArray array = doc.getJSONArray(name);
+            for (int n = 0; n < array.size(); n++) {
+              JSONObject obj = array.getJSONObject(n);
+              writeJsonPerson(Constants.CONTRIBUTOR, obj, writer);
+            }            
+          } else if (name.equals("updated")) {
+            writer.writeUpdated(doc.getString(name));
+          } else if (name.equals("published")) {
+            writer.writePublished(doc.getString(name));
+          } else if (name.equals("edited")) {
+            writer.writeEdited(doc.getString(name));
+          } else if (name.equals("content")) {
+            writeJsonContentElement( 
+              doc.get(name), 
+              writer);
+          } else if (name.equals("control")) {
+            JSONObject control = doc.getJSONObject("control");
+            writer.startControl();
+            writeJsonAttributes(control.getJSONObject("attributes"), writer);
+            writer.writeDraft(control.getBoolean("draft"));
+            if (control.containsKey("extensions"))
+              writeJsonExtensions(control.getJSONArray("extensions"), writer);
+            writer.endControl();
+          } else if (name.equals("source")) {
+            
+          }
+        }
+      }
+      writer.endEntry();
+  }
+  
+  private static void writeJsonPerson(
+    QName qname, 
+    JSONObject object, 
+    StreamWriter writer) {
+      writer.startPerson(qname);
+      writer.writePersonName(object.getString("name"));
+      if (object.containsKey("email"))
+        writer.writePersonEmail(object.getString("email"));
+      if (object.containsKey("uri"))
+        writer.writePersonUri(object.getString("uri"));
+      if (object.containsKey("extensions"))
+        writeJsonExtensions(object.getJSONArray("extensions"), writer);
+      writer.endPerson();
+  }
+
+  private static void writeJsonContentElement(
+    Object value, 
+    StreamWriter writer) {
+      if (value instanceof String) {
+        writer.writeContent(Content.Type.TEXT, (String) value);
+      } else if (value instanceof JSONObject) {
+        writeJsonContent( 
+          (JSONObject)value, 
+          writer);
+      }
+  }
+
+  private static void writeJsonContent(
+    JSONObject object, 
+    StreamWriter writer) {
+      String type = object.getJSONObject("attributes").getString("type");
+      Content.Type ctype = Content.Type.valueOf(type.toUpperCase());
+      writer.startContent(ctype);
+      if (ctype == Content.Type.XHTML) 
+        writer.startElement(Constants.DIV);
+      JSONArray array = object.getJSONArray("children");
+      for (int n = 0; n < array.size(); n++) {
+        Object child = array.get(n);
+        if (child instanceof String) {
+          writer.writeElementText((String)child);
+        } else if (child instanceof JSONObject) {
+          writeJsonTextMarkup(
+            (JSONObject) child, 
+            writer, 
+            ctype == Content.Type.XHTML);
+        }
+      }
+      if (ctype == Content.Type.XHTML) 
+        writer.endElement();
+      writer.endElement();
+  }
+  
+  private static void writeJsonTextElement(
+    QName qname, 
+    Object value, 
+    StreamWriter writer) {
+      if (value instanceof String) {
+        writer.writeText(qname, Text.Type.TEXT, (String) value);
+      } else if (value instanceof JSONObject) {
+        writeJsonText(
+          qname, 
+          (JSONObject)value, 
+          writer);
+      }
+  }
+  
+  private static void writeJsonText(
+    QName qname,
+    JSONObject object, 
+    StreamWriter writer) {
+      String type = object.getJSONObject("attributes").getString("type");
+      Text.Type texttype = Text.Type.valueOf(type.toUpperCase());
+      writer.startText(qname,texttype);
+      if (texttype == Text.Type.XHTML) 
+        writer.startElement(Constants.DIV);
+      JSONArray array = object.getJSONArray("children");
+      for (int n = 0; n < array.size(); n++) {
+        Object child = array.get(n);
+        if (child instanceof String) {
+          writer.writeElementText((String)child);
+        } else if (child instanceof JSONObject) {
+          writeJsonTextMarkup(
+            (JSONObject) child, 
+            writer, 
+            texttype == Text.Type.XHTML);
+        }
+      }
+      if (texttype == Text.Type.XHTML) 
+        writer.endElement();
+      writer.endElement();
+  }
+  
+  private static void writeJsonTextMarkup(
+    JSONObject object, 
+    StreamWriter writer, 
+    boolean xhtml) {
+      String name = object.getString("name");
+      if (xhtml) {
+        writer.startElement(name, Constants.XHTML_NS);
+        writeJsonAttributes(object.getJSONObject("attributes"), writer);
+        JSONArray array = object.getJSONArray("children");
+        for (int n = 0; n < array.size(); n++) {
+          Object obj = array.get(n);
+          if (obj instanceof String) {
+            writer.writeElementText((String) obj);
+          } else if (obj instanceof JSONObject) {
+            writeJsonTextMarkup((JSONObject) obj, writer, xhtml);
+          }
+        }
+        writer.endElement();
+      } else {
+        writer.writeElementText("<" + name);
+      
+        JSONObject attrs = object.getJSONObject("attributes");
+        for (Object attr : attrs.entrySet()) {
+          Map.Entry<?,?> entry = (Map.Entry<?,?>)attr;
+          String attrname = (String) entry.getKey();
+          String value = (String) entry.getValue();
+          writer.writeElementText(" " + attrname + "=\"" + value + "\"");
+        }
+        
+        writer.writeElementText(">");
+        JSONArray array = object.getJSONArray("children");
+        for (int n = 0; n < array.size(); n++) {
+          Object obj = array.get(n);
+          if (obj instanceof String) {
+            writer.writeElementText((String) obj);
+          } else if (obj instanceof JSONObject) {
+            writeJsonTextMarkup((JSONObject) obj, writer, xhtml);
+          }
+        }        
+        writer.writeElementText("</" + name  + ">");
+      }
+  }
+  
+  private static void writeJsonAttributes(
+    JSONObject object, 
+    StreamWriter writer) {
+      for (Object key : object.keySet()) {
+        String name = (String) key;
+        if (!name.startsWith("xmlns:")) {
+          Object value = object.get(name);
+          if (value instanceof String) {
+            writer.writeAttribute(name, (String)value);
+          } else if (value instanceof JSONObject){
+            JSONObject valueObj = (JSONObject) value;
+            String namespace = null;
+            JSONObject valueAttrs = valueObj.getJSONObject("attributes");
+            for (Object vkey : valueAttrs.keySet()) {
+              namespace = valueAttrs.getString((String) vkey);
+              break;
+            }
+            String[] namesplit = name.split(":",2);
+            writer.writeAttribute(
+              namesplit[1], 
+              namespace, 
+              namesplit[0], 
+              valueObj.getString("value"));
+          }
+        }
+      }
+  }
+  
+  private static void writeJsonExtensions(
+    JSONArray array, 
+    StreamWriter writer) {
+      for (int n = 0; n < array.size(); n++) {
+        JSONObject object = array.getJSONObject(n);
+        writeJsonObject(object,writer);
+      }
+  }
+  
+  private static void writeJsonObject(
+    JSONObject object, 
+    StreamWriter writer) {
+      String namespace = null;
+      JSONObject valueAttrs = object.getJSONObject("attributes");
+      for (Object vkey : valueAttrs.keySet()) {
+        String svkey = (String) vkey;
+        if (svkey.startsWith("xmlns:"))
+          namespace = valueAttrs.getString(svkey);
+        break;
+      }
+      String name = object.getString("name");
+      String[] namesplit = name.split(":",2);
+      if (namesplit.length > 1) 
+        writer.startElement(namesplit[1], namespace, namesplit[0]);
+      else 
+        writer.startElement(name, namespace);
+      writeJsonAttributes(valueAttrs, writer);
+      JSONArray children = object.getJSONArray("children");
+      for (int i = 0; i < children.size(); i++) {
+        Object child = children.get(i);
+        if (child instanceof String) {
+          writer.writeElementText((String)child);
+        } else if (child instanceof JSONObject) {
+          writeJsonObject((JSONObject) child,writer);
+        }
+      }
+      writer.endElement();
+  }
+}

Added: incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/abdera/protocol/server/test/couchdb/CouchDbTest.java
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/abdera/protocol/server/test/couchdb/CouchDbTest.java?rev=620186&view=auto
==============================================================================
--- incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/abdera/protocol/server/test/couchdb/CouchDbTest.java (added)
+++ incubator/abdera/java/trunk/adapters/couchdb/src/test/java/org/apache/abdera/protocol/server/test/couchdb/CouchDbTest.java Sat Feb  9 12:13:43 2008
@@ -0,0 +1,151 @@
+package org.apache.abdera.protocol.server.test.couchdb;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Date;
+
+import junit.framework.Assert;
+
+import org.apache.abdera.Abdera;
+import org.apache.abdera.model.Base;
+import org.apache.abdera.model.Collection;
+import org.apache.abdera.model.Document;
+import org.apache.abdera.model.Entry;
+import org.apache.abdera.model.Feed;
+import org.apache.abdera.model.Service;
+import org.apache.abdera.model.Workspace;
+import org.apache.abdera.protocol.Response.ResponseType;
+import org.apache.abdera.protocol.client.AbderaClient;
+import org.apache.abdera.protocol.client.ClientResponse;
+import org.apache.abdera.protocol.client.RequestOptions;
+import org.apache.abdera.protocol.server.provider.basic.BasicProvider;
+import org.apache.abdera.protocol.server.test.JettyServer;
+import org.apache.abdera.util.Constants;
+import org.apache.abdera.util.MimeTypeHelper;
+import org.apache.abdera.writer.Writer;
+import org.apache.abdera.writer.WriterFactory;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class CouchDbTest extends Assert {
+
+  private static JettyServer server;
+  private static Abdera abdera = Abdera.getInstance();
+  private static AbderaClient client = new AbderaClient();
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    if (server == null) {
+      server = new JettyServer();
+      server.start(BasicProvider.class);
+    }
+  }
+  
+  @AfterClass
+  public static void tearDown() throws Exception {
+    server.stop();
+  }
+
+  protected void prettyPrint(Base doc) throws IOException {
+    WriterFactory factory = abdera.getWriterFactory();
+    Writer writer = factory.getWriter("prettyxml");
+    writer.writeTo(doc, System.out);
+    System.out.println();
+  }
+
+  @Test
+  public void testGetService() {
+    ClientResponse resp = client.get("http://localhost:9002/");
+    assertNotNull(resp);
+    assertEquals(resp.getType(), ResponseType.SUCCESS);
+    assertTrue(MimeTypeHelper.isMatch(resp.getContentType().toString(), Constants.APP_MEDIA_TYPE));
+    Document<Service> doc = resp.getDocument();
+    Service service = doc.getRoot();
+    assertEquals(service.getWorkspaces().size(), 1);
+    Workspace workspace = service.getWorkspace("Abdera");
+    assertEquals(workspace.getCollections().size(), 2);
+    Collection collection = workspace.getCollection("CouchDB Feed");
+    assertNotNull(collection);
+    assertTrue(collection.acceptsEntry());
+    assertEquals(collection.getResolvedHref().toString(), "http://localhost:9002/couchdb");
+  }
+  @Test
+  public void testGetFeed() {
+    ClientResponse resp = client.get("http://localhost:9002/couchdb");
+    assertNotNull(resp);
+    assertEquals(resp.getType(), ResponseType.SUCCESS);
+    assertTrue(MimeTypeHelper.isMatch(resp.getContentType().toString(), Constants.ATOM_MEDIA_TYPE));
+    Document<Feed> doc = resp.getDocument();
+    Feed feed = doc.getRoot();
+    assertEquals(feed.getId().toString(), "http://localhost:9002/couchdb");
+    assertEquals(feed.getTitle(), "CouchDB Feed");
+    assertEquals(feed.getAuthor().getName(), "james");
+    assertEquals(feed.getEntries().size(), 0);
+    resp.release();
+  }
+  @Test
+  public void testPostEntry() {
+    Entry entry = abdera.newEntry();
+    entry.setId("http://localhost:9002/couchdb/foo");
+    entry.setTitle("test entry");
+    entry.setContent("Test Content");
+    entry.addLink("http://example.org");
+    entry.setUpdated(new Date());
+    entry.addAuthor("James");
+    ClientResponse resp = client.post("http://localhost:9002/couchdb", entry);
+    assertNotNull(resp);
+    assertEquals(resp.getType(), ResponseType.SUCCESS);
+    assertEquals(resp.getStatus(), 201);
+    assertEquals(resp.getLocation().toString(), "http://localhost:9002/couchdb/test_entry");
+    resp = client.get("http://localhost:9002/couchdb");
+    Document<Feed> feed_doc = resp.getDocument();
+    Feed feed = feed_doc.getRoot();
+    assertEquals(feed.getEntries().size(), 1);
+    resp.release();
+  }
+  @Test
+  public void testPostMedia() {
+    ByteArrayInputStream in = new ByteArrayInputStream(new byte[] {0x01, 0x02, 0x03, 0x04});
+    RequestOptions options = client.getDefaultRequestOptions();
+    options.setContentType("application/octet-stream");
+    ClientResponse resp = client.post("http://localhost:9002/couchdb", in, options);
+    assertEquals(resp.getType(), ResponseType.CLIENT_ERROR);
+    assertEquals(resp.getStatus(), 415);
+    resp.release();
+  }
+  @Test
+  public void testPutEntry() {
+    ClientResponse resp = client.get("http://localhost:9002/couchdb/test_entry");
+    Document<Entry> doc = resp.getDocument();
+    Entry entry = (Entry) doc.getRoot().clone();
+    entry.setTitle("This is the modified title");
+    resp.release();
+    resp = client.put("http://localhost:9002/couchdb/test_entry", entry);
+    assertEquals(resp.getType(), ResponseType.SUCCESS);
+    assertEquals(resp.getStatus(), 200);
+    resp.release();
+    resp = client.get("http://localhost:9002/couchdb/test_entry");
+    doc = resp.getDocument();
+    entry = doc.getRoot();
+    assertEquals(entry.getTitle(), "This is the modified title");
+    resp.release();
+    resp = client.get("http://localhost:9002/couchdb");
+    Document<Feed> feed_doc = resp.getDocument();
+    Feed feed = feed_doc.getRoot();
+    assertEquals(feed.getEntries().size(), 1);
+    resp.release();
+  }
+
+  @Test
+  public void testDeleteEntry() {
+    ClientResponse resp = client.delete("http://localhost:9002/couchdb/test_entry");
+    assertEquals(resp.getType(), ResponseType.SUCCESS);
+    resp.release();
+    resp = client.get("http://localhost:9002/couchdb");
+    Document<Feed> feed_doc = resp.getDocument();
+    Feed feed = feed_doc.getRoot();
+    assertEquals(feed.getEntries().size(), 0);
+    resp.release();
+  }
+}

Added: incubator/abdera/java/trunk/adapters/couchdb/src/test/resources/abdera/adapter/couchdb.properties
URL: http://svn.apache.org/viewvc/incubator/abdera/java/trunk/adapters/couchdb/src/test/resources/abdera/adapter/couchdb.properties?rev=620186&view=auto
==============================================================================
--- incubator/abdera/java/trunk/adapters/couchdb/src/test/resources/abdera/adapter/couchdb.properties (added)
+++ incubator/abdera/java/trunk/adapters/couchdb/src/test/resources/abdera/adapter/couchdb.properties Sat Feb  9 12:13:43 2008
@@ -0,0 +1,9 @@
+subUri=couchdb
+adapterClassName=org.apache.abdera.protocol.server.adapters.couchdb.CouchDbAdapter
+title=CouchDB Feed
+author=james
+configFile=dummyFileLocation
+
+# uncomment to override the defaults
+#couchdb.host=localhost
+#couchdb.port=5984
\ No newline at end of file