/* * 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>(String.class.getName(), String.class, String.class); Class itemClass = getOperation.getOutputType().getPhysical(); if (itemClass == SyndEntry.class) { supportsFeedEntries = true; } DataType outputType = getOperation.getOutputType(); QName qname = outputType.getLogical().getElementName(); qname = new QName(qname.getNamespaceURI(), itemClass.getSimpleName()); itemClassType = new DataTypeImpl("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[] collection = (org.apache.tuscany.sca.data.collection.Entry[])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 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 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 contents = new ArrayList(); 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 contents = new ArrayList(); 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 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(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(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); } }