/*
* 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;
/**
* The CouchDB Adapter uses a CouchDB database as the backend storage for an Atompub Collection. As an extension of the
* ManagedCollectionAdapter class, the Adapter is intended to be used with implementations of the ManagedProvider and
* are configured using /abdera/adapter/*.properties files. By default, the CouchDbAdapter assumes that CouchDB is
* running on localhost on port 5984. The host name and port can be changed by setting the couchdb.host and couchdb.port
* values in the Adapter instance *.properties file. The Adapter uses the Abdera Atom-to-JSON converter to store
* Collection entries as JSON documents in a CouchDB database.
*/
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) {
// error if the db is not found, ignore the error,
// we're going to create it
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 = null;
try {
doc = db.getDocument(entry);
} catch (Exception e) {
// error if the doc isn't found, we deal with the null result below
}
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) {
// error if the doc isn't found, we deal with the null result below
}
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 = null;
try {
doc = db.getDocument(entry);
} catch (Exception e) {
// error if the doc isn't found, we deal with the null result below
}
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) {
// error if the doc isn't found, we deal with the null result below
}
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;
}
}
}