blob: 44c84ce5bb02b8105c9b6210755c294deca0c3d9 [file] [log] [blame]
/*
* 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.rss.provider;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;
import org.apache.commons.codec.binary.Base64;
import org.apache.tuscany.sca.data.collection.Item;
import org.apache.tuscany.sca.databinding.Mediator;
import org.apache.tuscany.sca.interfacedef.DataType;
import org.apache.tuscany.sca.interfacedef.Operation;
import org.apache.tuscany.sca.interfacedef.impl.DataTypeImpl;
import org.apache.tuscany.sca.interfacedef.util.XMLType;
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.Invocable;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.feed.synd.SyndLink;
import com.sun.syndication.feed.synd.SyndLinkImpl;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedOutput;
/**
* An RSS binding listener, implemented as a Servlet and
* registered in a Servlet host provided by the SCA hosting runtime.
*
* @version $Rev$ $Date$
*/
class RSSBindingListenerServlet extends HttpServlet {
private static final Logger logger = Logger.getLogger(RSSBindingListenerServlet.class.getName());
private static final long serialVersionUID = 1L;
private Invocable wire;
private Invoker getFeedInvoker;
private Invoker getAllInvoker;
private Invoker queryInvoker;
private MessageFactory messageFactory;
private Mediator mediator;
private DataType<?> itemClassType;
private DataType<?> itemXMLType;
private boolean supportsFeedEntries;
/**
* Constructs a new binding listener.
*
* @param wire
* @param messageFactory
*/
RSSBindingListenerServlet(Invocable wire, MessageFactory messageFactory, Mediator mediator) {
this.wire = wire;
this.messageFactory = messageFactory;
this.mediator = mediator;
// Get the invokers for the supported operations
Operation getOperation = null;
for (InvocationChain invocationChain : this.wire.getInvocationChains()) {
invocationChain.setAllowsPassByReference(true);
Operation operation = invocationChain.getTargetOperation();
String operationName = operation.getName();
if (operationName.equals("getFeed")) {
getFeedInvoker = invocationChain.getHeadInvoker();
} else if (operationName.equals("getAll")) {
getAllInvoker = invocationChain.getHeadInvoker();
} else if (operationName.equals("query")) {
queryInvoker = invocationChain.getHeadInvoker();
} else if (operationName.equals("get")) {
getOperation = operation;
}
}
// Determine the collection item type
if (getOperation != null) {
itemXMLType = new DataTypeImpl<Class<?>>(String.class.getName(), String.class, String.class);
Class<?> itemClass = getOperation.getOutputType().getPhysical();
if (itemClass == SyndEntry.class) {
supportsFeedEntries = true;
}
DataType<XMLType> outputType = getOperation.getOutputType();
QName qname = outputType.getLogical().getElementName();
qname = new QName(qname.getNamespaceURI(), itemClass.getSimpleName());
itemClassType = new DataTypeImpl<XMLType>("java:complexType", itemClass, new XMLType(qname, null));
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// No authentication required for a get request
// Get the request path
String path = URLDecoder.decode(request.getRequestURI().substring(request.getServletPath().length()), "UTF-8");
logger.fine("get " + request.getRequestURI());
// Handle an RSS request
if (path == null || path.length() == 0 || path.equals("/")) {
// Return an RSS feed containing the entries in the collection
SyndFeed feed = null;
if (supportsFeedEntries) {
// The service implementation supports feed entries, invoke its getFeed operation
Message requestMessage = messageFactory.createMessage();
Message responseMessage = getFeedInvoker.invoke(requestMessage);
if (responseMessage.isFault()) {
throw new ServletException((Throwable)responseMessage.getBody());
}
feed = (SyndFeed)responseMessage.getBody();
} else {
// The service implementation does not support feed entries, invoke its
// getAll operation to get the data item collection. then create feed entries
// from the data items
Message requestMessage = messageFactory.createMessage();
Message responseMessage;
if (request.getQueryString() != null) {
requestMessage.setBody(new Object[] {request.getQueryString()});
responseMessage = queryInvoker.invoke(requestMessage);
} else {
responseMessage = getAllInvoker.invoke(requestMessage);
}
if (responseMessage.isFault()) {
throw new ServletException((Throwable)responseMessage.getBody());
}
org.apache.tuscany.sca.data.collection.Entry<Object, Object>[] collection =
(org.apache.tuscany.sca.data.collection.Entry<Object, Object>[])responseMessage.getBody();
if (collection != null) {
// Create the feed
feed = new SyndFeedImpl();
feed.setTitle("Feed");
feed.setDescription("Feed description");
for (org.apache.tuscany.sca.data.collection.Entry<Object, Object> entry: collection) {
SyndEntry feedEntry = createFeedEntry(entry);
feed.getEntries().add(feedEntry);
}
}
}
// Convert to an RSS feed
if (feed != null) {
response.setContentType("application/rss+xml; charset=utf-8");
feed.setFeedType("rss_2.0");
feed.setLink(path);
SyndFeedOutput syndOutput = new SyndFeedOutput();
try {
syndOutput.output(feed, getWriter(response));
} catch (FeedException e) {
throw new ServletException(e);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
/**
* Create an RSS entry from a data collection entry.
* @param entry
* @return
*/
private SyndEntry createFeedEntry(org.apache.tuscany.sca.data.collection.Entry<Object, Object> entry) {
Object key = entry.getKey();
Object data = entry.getData();
if (data instanceof Item) {
Item item = (Item)data;
SyndEntry feedEntry = new SyndEntryImpl();
feedEntry.setUri(key.toString());
feedEntry.setTitle(item.getTitle());
String value = item.getContents();
if (value != null) {
SyndContent content = new SyndContentImpl();
content.setType("text/xml");
content.setValue(value);
List<SyndContent> contents = new ArrayList<SyndContent>();
contents.add(content);
feedEntry.setContents(contents);
}
String href = item.getLink();
if (href == null) {
href = key.toString();
}
SyndLink link = new SyndLinkImpl();
link.setRel("edit");
link.setHref(href);
feedEntry.getLinks().add(link);
link = new SyndLinkImpl();
link.setRel("alternate");
link.setHref(href);
feedEntry.getLinks().add(link);
feedEntry.setLink(href);
Date date = item.getDate();
if (date == null) {
date = new Date();
}
feedEntry.setPublishedDate(date);
return feedEntry;
} else if (data != null) {
SyndEntry feedEntry = new SyndEntryImpl();
feedEntry.setUri(key.toString());
feedEntry.setTitle("item");
// Convert the item to XML
String value = mediator.mediate(data, itemClassType, itemXMLType, null).toString();
SyndContent content = new SyndContentImpl();
content.setType("text/xml");
content.setValue(value);
List<SyndContent> contents = new ArrayList<SyndContent>();
contents.add(content);
feedEntry.setContents(contents);
SyndLink link = new SyndLinkImpl();
link.setRel("edit");
link.setHref(key.toString());
feedEntry.getLinks().add(link);
link = new SyndLinkImpl();
link.setRel("alternate");
link.setHref(key.toString());
feedEntry.getLinks().add(link);
feedEntry.setPublishedDate(new Date());
return feedEntry;
} else {
return null;
}
}
/**
* Create a data collection entry from an RSS entry.
* @param feedEntry
* @return
*/
private org.apache.tuscany.sca.data.collection.Entry<Object, Object> createEntry(SyndEntry feedEntry) {
if (feedEntry != null) {
if (itemClassType.getPhysical() == Item.class) {
String key = feedEntry.getUri();
Item item = new Item();
item.setTitle(feedEntry.getTitle());
List<?> contents = feedEntry.getContents();
if (!contents.isEmpty()) {
SyndContent content = (SyndContent)contents.get(0);
String value = content.getValue();
item.setContents(value);
}
for (Object l : feedEntry.getLinks()) {
SyndLink link = (SyndLink)l;
if (link.getRel() == null || "edit".equals(link.getRel())) {
String href = link.getHref();
if (href.startsWith("null/")) {
href = href.substring(5);
}
item.setLink(href);
break;
}
}
item.setDate(feedEntry.getPublishedDate());
return new org.apache.tuscany.sca.data.collection.Entry<Object, Object>(key, item);
} else {
String key = feedEntry.getUri();
// Create the item from XML
List<?> contents = feedEntry.getContents();
if (contents.isEmpty()) {
return null;
}
SyndContent content = (SyndContent)contents.get(0);
String value = content.getValue();
Object data = mediator.mediate(value, itemXMLType, itemClassType, null);
return new org.apache.tuscany.sca.data.collection.Entry<Object, Object>(key, data);
}
} else {
return null;
}
}
private Writer getWriter(HttpServletResponse response) throws UnsupportedEncodingException, IOException {
Writer writer = new OutputStreamWriter(response.getOutputStream(), "UTF-8");
return writer;
}
/**
* 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);
}
}