/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tuscany.sca.binding.feed.provider;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.apache.tuscany.sca.binding.feed.collection.NotFoundException;
import org.apache.tuscany.sca.invocation.InvocationChain;
import org.apache.tuscany.sca.invocation.Invoker;
import org.apache.tuscany.sca.invocation.Message;
import org.apache.tuscany.sca.invocation.MessageFactory;
import org.apache.tuscany.sca.runtime.RuntimeWire;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import com.sun.syndication.feed.atom.Entry;
import com.sun.syndication.feed.atom.Feed;
import com.sun.syndication.feed.atom.Link;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedOutput;
import com.sun.syndication.io.WireFeedOutput;
/**
* A resource collection binding listener, implemented as a servlet and
* registered in a servlet host provided by the SCA hosting runtime.
*/
class FeedBindingListenerServlet extends HttpServlet {
private static final Logger logger = Logger.getLogger(FeedBindingListenerServlet.class.getName());
private static final long serialVersionUID = 1L;
private final static Namespace APP_NS = Namespace.getNamespace("app", "http://purl.org/atom/app#");
private final static Namespace ATOM_NS = Namespace.getNamespace("atom", "http://www.w3.org/2005/Atom");
private RuntimeWire wire;
private Invoker getFeedInvoker;
private Invoker getInvoker;
private Invoker postInvoker;
private Invoker postMediaInvoker;
private Invoker putInvoker;
private Invoker putMediaInvoker;
private Invoker deleteInvoker;
private MessageFactory messageFactory;
private String feedType;
/**
* Constructs a new binding listener.
*
* @param wire
* @param messageFactory
* @param feedType
*/
FeedBindingListenerServlet(RuntimeWire wire, MessageFactory messageFactory, String feedType) {
this.wire = wire;
this.messageFactory = messageFactory;
this.feedType = feedType;
// Get the invokers for the supported operations
for (InvocationChain invocationChain : this.wire.getInvocationChains()) {
String operationName = invocationChain.getSourceOperation().getName();
if (operationName.equals("getFeed")) {
getFeedInvoker = invocationChain.getHeadInvoker();
} else if (operationName.equals("get")) {
getInvoker = invocationChain.getHeadInvoker();
} else if (operationName.equals("put")) {
putInvoker = invocationChain.getHeadInvoker();
} else if (operationName.equals("putMedia")) {
putMediaInvoker = invocationChain.getHeadInvoker();
} else if (operationName.equals("post")) {
postInvoker = invocationChain.getHeadInvoker();
} else if (operationName.equals("postMedia")) {
postMediaInvoker = invocationChain.getHeadInvoker();
} else if (operationName.equals("delete")) {
deleteInvoker = invocationChain.getHeadInvoker();
}
}
}
@Override
public void init(ServletConfig config) {
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// No authentication required for a get request
// Get the request path
String path = request.getPathInfo();
// The feedType parameter is used to override what type of feed is going
// to
// be produced
String requestFeedType = request.getParameter("feedType");
if (requestFeedType == null)
requestFeedType = feedType;
logger.info(">>> FeedEndPointServlet (" + requestFeedType + ") " + request.getRequestURI());
// Handle an Atom request
if (requestFeedType.startsWith("atom_")) {
if (path != null && path.equals("/atomsvc")) {
// Return the Atom service document
response.setContentType("application/atomsvc+xml; charset=utf-8");
Document document = new Document();
Element service = new Element("service", APP_NS);
document.setRootElement(service);
Element workspace = new Element("workspace", APP_NS);
Element title = new Element("title", ATOM_NS);
title.setText("resource");
workspace.addContent(title);
service.addContent(workspace);
Element collection = new Element("collection", APP_NS);
String href = request.getRequestURL().toString();
href = href.substring(0, href.length() - "/atomsvc".length());
collection.setAttribute("href", href);
Element collectionTitle = new Element("title", ATOM_NS);
collectionTitle.setText("entries");
collection.addContent(collectionTitle);
workspace.addContent(collection);
XMLOutputter outputter = new XMLOutputter();
Format format = Format.getPrettyFormat();
format.setEncoding("UTF-8");
outputter.setFormat(format);
outputter.output(document, getWriter(response));
} else if (path == null || path.length() == 0 || path.equals("/")) {
// Return a feed containing the entries in the collection
// Get the Feed from the service implementation
Message requestMessage = messageFactory.createMessage();
Message responseMessage = getFeedInvoker.invoke(requestMessage);
if (responseMessage.isFault()) {
throw new ServletException((Throwable)responseMessage.getBody());
}
Feed feed = (Feed)responseMessage.getBody();
if (feed != null) {
// Write the Atom feed
response.setContentType("application/atom+xml; charset=utf-8");
feed.setFeedType(requestFeedType);
WireFeedOutput feedOutput = new WireFeedOutput();
try {
feedOutput.output(feed, getWriter(response));
} catch (FeedException e) {
throw new ServletException(e);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} else if (path.startsWith("/")) {
// Return a specific entry in the collection
// Get the entry from the service implementation
Message requestMessage = messageFactory.createMessage();
String id = path.substring(1);
requestMessage.setBody(new Object[] {id});
Message responseMessage = getInvoker.invoke(requestMessage);
if (responseMessage.isFault()) {
throw new ServletException((Throwable)responseMessage.getBody());
}
Entry entry = responseMessage.getBody();
// Write the Atom entry
if (entry != null) {
response.setContentType("application/atom+xml; charset=utf-8");
try {
AtomEntryUtil.writeEntry(entry, feedType, getWriter(response));
} catch (FeedException e) {
throw new ServletException(e);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} else {
// Path doesn't match any known pattern
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} else {
// Handle an RSS request
if (path == null || path.length() == 0 || path.equals("/")) {
// Get the Feed from the service
Message requestMessage = messageFactory.createMessage();
Message responseMessage = getFeedInvoker.invoke(requestMessage);
if (responseMessage.isFault()) {
throw new ServletException((Throwable)responseMessage.getBody());
}
Feed feed = (Feed)responseMessage.getBody();
if (feed != null) {
// Convert to an RSS feed
response.setContentType("application/rss+xml; charset=utf-8");
feed.setFeedType("atom_1.0");
SyndFeed syndFeed = new SyndFeedImpl(feed);
syndFeed.setFeedType(requestFeedType);
SyndFeedOutput syndOutput = new SyndFeedOutput();
try {
syndOutput.output(syndFeed, getWriter(response));
} catch (FeedException e) {
throw new ServletException(e);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// Authenticate the user
String user = processAuthorizationHeader(request);
if (user == null) {
unauthorized(response);
return;
}
// Get the request path
String path = request.getPathInfo();
if (path == null || path.length() == 0 || path.equals("/")) {
Entry createdEntry = null;
// Create a new Atom entry
String contentType = request.getContentType();
if (contentType.startsWith("application/atom+xml")) {
// Read the entry from the request
Entry entry;
try {
entry = AtomEntryUtil.readEntry(feedType, request.getReader());
} catch (JDOMException e) {
throw new ServletException(e);
} catch (FeedException e) {
throw new ServletException(e);
}
// Let the component implementation create it
Message requestMessage = messageFactory.createMessage();
requestMessage.setBody(new Object[] {entry});
Message responseMessage = postInvoker.invoke(requestMessage);
if (responseMessage.isFault()) {
throw new ServletException((Throwable)responseMessage.getBody());
}
createdEntry = responseMessage.getBody();
} else if (contentType != null) {
// Create a new media entry
// Get incoming headers
String title = request.getHeader("Title");
String slug = request.getHeader("Slug");
// Let the component implementation create the media entry
Message requestMessage = messageFactory.createMessage();
requestMessage.setBody(new Object[] {title, slug, contentType, request.getInputStream()});
Message responseMessage = postMediaInvoker.invoke(requestMessage);
if (responseMessage.isFault()) {
throw new ServletException((Throwable)responseMessage.getBody());
}
createdEntry = responseMessage.getBody();
} else {
response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
}
// A new entry was created successfully
if (createdEntry != null) {
// Set location of the created entry in the Location header
for (Object l : createdEntry.getOtherLinks()) {
Link link = (Link)l;
if (link.getRel() == null || "edit".equals(link.getRel())) {
response.addHeader("Location", link.getHref());
break;
}
}
// Write the created Atom entry
response.setStatus(HttpServletResponse.SC_CREATED);
response.setContentType("application/atom+xml; charset=utf-8");
try {
AtomEntryUtil.writeEntry(createdEntry, feedType, getWriter(response));
} catch (FeedException e) {
throw new ServletException(e);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
private Writer getWriter(HttpServletResponse response) throws UnsupportedEncodingException, IOException {
Writer writer = new OutputStreamWriter(response.getOutputStream(), "UTF-8");
return writer;
}
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Authenticate the user
String user = processAuthorizationHeader(request);
if (user == null) {
unauthorized(response);
return;
}
// Get the request path
String path = request.getPathInfo();
if (path != null && path.startsWith("/")) {
String id = path.substring(1);
Entry updatedEntry = null;
// Update an Atom entry
String contentType = request.getContentType();
if (contentType.startsWith("application/atom+xml")) {
// Read the entry from the request
Entry entry;
try {
entry = AtomEntryUtil.readEntry(feedType, request.getReader());
} catch (JDOMException e) {
throw new ServletException(e);
} catch (FeedException e) {
throw new ServletException(e);
}
// Let the component implementation create it
Message requestMessage = messageFactory.createMessage();
requestMessage.setBody(new Object[] {id, entry});
Message responseMessage = putInvoker.invoke(requestMessage);
if (responseMessage.isFault()) {
Object body = responseMessage.getBody();
if (body instanceof NotFoundException) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
} else {
throw new ServletException((Throwable)responseMessage.getBody());
}
} else {
updatedEntry = responseMessage.getBody();
}
} else if (contentType != null) {
// Updated a media entry
// Let the component implementation create the media entry
Message requestMessage = messageFactory.createMessage();
requestMessage.setBody(new Object[] {id, contentType, request.getInputStream()});
Message responseMessage = putMediaInvoker.invoke(requestMessage);
Object body = responseMessage.getBody();
if (body instanceof NotFoundException) {
if (body instanceof NotFoundException) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
} else {
throw new ServletException((Throwable)responseMessage.getBody());
}
} else {
updatedEntry = (Entry) body;
}
} else {
response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
}
// The entry was successfully updated
if (updatedEntry != null) {
// Write the updated Atom entry
response.setContentType("application/atom+xml; charset=utf-8");
try {
AtomEntryUtil.writeEntry(updatedEntry, feedType, getWriter(response));
} catch (FeedException e) {
throw new ServletException(e);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// Authenticate the user
String user = processAuthorizationHeader(request);
if (user == null) {
unauthorized(response);
return;
}
// Get the request path
String path = request.getPathInfo();
String id;
if (path != null && path.startsWith("/")) {
id = path.substring(1);
} else {
id = "";
}
// Delete a specific entry from the collection
Message requestMessage = messageFactory.createMessage();
requestMessage.setBody(new Object[] {id});
Message responseMessage = deleteInvoker.invoke(requestMessage);
if (responseMessage.isFault()) {
Object body = responseMessage.getBody();
if (body instanceof NotFoundException) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
} else {
throw new ServletException((Throwable)responseMessage.getBody());
}
}
}
/**
* Process the authorization header
*
* @param request
* @return
* @throws ServletException
*/
private String processAuthorizationHeader(HttpServletRequest request) throws ServletException {
try {
String authorization = request.getHeader("Authorization");
if (authorization != null) {
StringTokenizer tokens = new StringTokenizer(authorization);
if (tokens.hasMoreTokens()) {
String basic = tokens.nextToken();
if (basic.equalsIgnoreCase("Basic")) {
String credentials = tokens.nextToken();
String userAndPassword = new String(Base64.decodeBase64(credentials.getBytes()));
int colon = userAndPassword.indexOf(":");
if (colon != -1) {
String user = userAndPassword.substring(0, colon);
String password = userAndPassword.substring(colon + 1);
// Authenticate the User.
if (authenticate(user, password)) {
return user;
}
}
}
}
}
} catch (Exception e) {
throw new ServletException(e);
}
return null;
}
/**
* Authenticate a user.
*
* @param user
* @param password
* @return
*/
private boolean authenticate(String user, String password) {
// TODO Handle this using SCA security policies
//FIXME Why are we using endsWith instead of equals here??
return ("admin".endsWith(user) && "admin".equals(password));
}
/**
* Reject an unauthorized request.
*
* @param response
*/
private void unauthorized(HttpServletResponse response) throws IOException {
response.setHeader("WWW-Authenticate", "BASIC realm=\"Tuscany\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}