/*
* 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.filesystem;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.abdera.Abdera;
import org.apache.abdera.i18n.templates.Template;
import org.apache.abdera.i18n.text.Normalizer;
import org.apache.abdera.i18n.text.Sanitizer;
import org.apache.abdera.model.Document;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;
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;
/**
* Simple Filesystem Adapter that uses a local directory to store Atompub collection entries. 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. The *.properties file MUST specify the fs.root property to
* specify the root directory used by the Adapter.
*/
public class FilesystemAdapter extends ManagedCollectionAdapter {
private final File root;
private final static FileSorter sorter = new FileSorter();
private final static Template paging_template = new Template("?{-join|&|count,page}");
public FilesystemAdapter(Abdera abdera, FeedConfiguration config) {
super(abdera, config);
this.root = getRoot();
}
private File getRoot() {
try {
String root = (String)config.getProperty("fs.root");
File file = new File(root);
if (!file.exists())
file.mkdirs();
if (!file.isDirectory())
throw new RuntimeException("Root must be a directory");
return file;
} catch (Exception e) {
if (e instanceof RuntimeException)
throw (RuntimeException)e;
throw new RuntimeException(e);
}
}
private Entry getEntry(File entryFile) {
if (!entryFile.exists() || !entryFile.isFile())
throw new RuntimeException();
try {
FileInputStream fis = new FileInputStream(entryFile);
Document<Entry> doc = abdera.getParser().parse(fis);
Entry entry = doc.getRoot();
return entry;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void addPagingLinks(RequestContext request, Feed feed, int currentpage, int count) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("count", count);
params.put("page", currentpage + 1);
String next = paging_template.expand(params);
next = request.getResolvedUri().resolve(next).toString();
feed.addLink(next, "next");
if (currentpage > 0) {
params.put("page", currentpage - 1);
String prev = paging_template.expand(params);
prev = request.getResolvedUri().resolve(prev).toString();
feed.addLink(prev, "previous");
}
params.put("page", 0);
String current = paging_template.expand(params);
current = request.getResolvedUri().resolve(current).toString();
feed.addLink(current, "current");
}
private void getEntries(RequestContext request, Feed feed, File root) {
File[] files = root.listFiles();
Arrays.sort(files, sorter);
int length = ProviderHelper.getPageSize(request, "count", 25);
int offset = ProviderHelper.getOffset(request, "page", length);
String _page = request.getParameter("page");
int page = (_page != null) ? Integer.parseInt(_page) : 0;
addPagingLinks(request, feed, page, length);
if (offset > files.length)
return;
for (int n = offset; n < offset + length && n < files.length; n++) {
File file = files[n];
Entry entry = getEntry(file);
feed.addEntry((Entry)entry.clone());
}
}
public ResponseContext getFeed(RequestContext request) {
Feed feed = abdera.newFeed();
feed.setId(config.getServerConfiguration().getServerUri() + "/" + config.getFeedId());
feed.setTitle(config.getFeedTitle());
feed.addAuthor(config.getFeedAuthor());
feed.addLink(config.getFeedUri());
feed.addLink(config.getFeedUri(), "self");
feed.setUpdated(new Date());
getEntries(request, feed, root);
return ProviderHelper.returnBase(feed.getDocument(), 200, null);
}
public ResponseContext deleteEntry(RequestContext request) {
Target target = request.getTarget();
String key = target.getParameter("entry");
File file = getFile(key, false);
if (file.exists())
file.delete();
return ProviderHelper.nocontent();
}
public ResponseContext getEntry(RequestContext request) {
Target target = request.getTarget();
String key = target.getParameter("entry");
File file = getFile(key, false);
Entry entry = getEntry(file);
if (entry != null)
return ProviderHelper.returnBase(entry.getDocument(), 200, null);
else
return ProviderHelper.notfound(request);
}
public ResponseContext postEntry(RequestContext request) {
if (request.isAtom()) {
try {
Entry entry = (Entry)request.getDocument().getRoot().clone();
String key = createKey(request);
setEditDetail(request, entry, key);
File file = getFile(key);
FileOutputStream out = new FileOutputStream(file);
entry.writeTo(out);
String edit = entry.getEditLinkResolvedHref().toString();
return ProviderHelper.returnBase(entry.getDocument(), 201, null).setLocation(edit);
} catch (Exception e) {
return ProviderHelper.badrequest(request);
}
} else {
return ProviderHelper.notsupported(request);
}
}
private void setEditDetail(RequestContext request, Entry entry, String key) throws IOException {
Target target = request.getTarget();
String feed = target.getParameter("feed");
String id = key;
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);
}
}
private File getFile(String key) {
return getFile(key, true);
}
private File getFile(String key, boolean post) {
File file = new File(root, key);
if (post && file.exists())
throw new RuntimeException("File exists");
return file;
}
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 putEntry(RequestContext request) {
if (request.isAtom()) {
try {
Entry entry = (Entry)request.getDocument().getRoot().clone();
String key = request.getTarget().getParameter("entry");
setEditDetail(request, entry, key);
File file = getFile(key, false);
FileOutputStream out = new FileOutputStream(file);
entry.writeTo(out);
String edit = entry.getEditLinkResolvedHref().toString();
return ProviderHelper.returnBase(entry.getDocument(), 200, null).setLocation(edit);
} catch (Exception e) {
return ProviderHelper.badrequest(request);
}
} else {
return ProviderHelper.notsupported(request);
}
}
private static class FileSorter implements Comparator<File> {
public int compare(File o1, File o2) {
return o1.lastModified() > o2.lastModified() ? -1 : o1.lastModified() < o2.lastModified() ? 1 : 0;
}
}
}