001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. The ASF licenses this file to You
004: * under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License. For additional information regarding
015: * copyright in this work, please see the NOTICE file in the top level
016: * directory of this distribution.
017: */
018: package org.apache.roller.webservices.atomprotocol;
019:
020: import java.io.IOException;
021: import java.io.InputStreamReader;
022: import java.io.Reader;
023: import java.io.Writer;
024: import java.util.ArrayList;
025: import java.util.Iterator;
026: import java.util.List;
027:
028: import javax.servlet.ServletException;
029: import javax.servlet.http.HttpServlet;
030: import javax.servlet.http.HttpServletRequest;
031: import javax.servlet.http.HttpServletResponse;
032:
033: import org.apache.commons.lang.StringUtils;
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036: import org.jdom.Document;
037: import org.jdom.Element;
038: import org.jdom.JDOMException;
039: import org.jdom.input.SAXBuilder;
040: import org.jdom.output.Format;
041: import org.jdom.output.XMLOutputter;
042:
043: import com.sun.syndication.feed.atom.Entry;
044: import com.sun.syndication.feed.atom.Feed;
045: import com.sun.syndication.feed.atom.Link;
046: import com.sun.syndication.io.FeedException;
047: import com.sun.syndication.io.WireFeedInput;
048: import com.sun.syndication.io.WireFeedOutput;
049: import java.io.BufferedReader;
050: import java.io.StringWriter;
051: import org.jdom.Namespace;
052: import org.apache.roller.config.RollerConfig;
053:
054: /**
055: * Atom Servlet implements Atom by calling a Roller independent handler.
056: * @web.servlet name="AtomServlet"
057: * @web.servlet-mapping url-pattern="/roller-services/app/*"
058: * @author David M Johnson
059: */
060: public class AtomServlet extends HttpServlet {
061: public static final String FEED_TYPE = "atom_1.0";
062:
063: private static Log mLogger = LogFactory.getFactory().getInstance(
064: AtomServlet.class);
065:
066: //-----------------------------------------------------------------------------
067: /**
068: * Create an Atom request handler.
069: * TODO: make AtomRequestHandler implementation configurable.
070: */
071: private AtomHandler createAtomRequestHandler(
072: HttpServletRequest request) throws ServletException {
073: boolean enabled = RollerConfig
074: .getBooleanProperty("webservices.atomprotocol.enabled");
075: if (!enabled) {
076: throw new ServletException(
077: "ERROR: Atom protocol not enabled");
078: }
079: return new RollerAtomHandler(request);
080: }
081:
082: //-----------------------------------------------------------------------------
083: /**
084: * Handles an Atom GET by calling handler and writing results to response.
085: */
086: protected void doGet(HttpServletRequest req, HttpServletResponse res)
087: throws ServletException, IOException {
088: AtomHandler handler = createAtomRequestHandler(req);
089: String userName = handler.getAuthenticatedUsername();
090: if (userName != null) {
091: String[] pathInfo = getPathInfo(req);
092: try {
093: if (handler.isIntrospectionURI(pathInfo)) {
094: // return an Atom Service document
095: AtomService service = handler.getIntrospection();
096: Document doc = AtomService
097: .serviceToDocument(service);
098: res
099: .setContentType("application/atomserv+xml; charset=utf-8");
100: Writer writer = res.getWriter();
101: XMLOutputter outputter = new XMLOutputter();
102: outputter.setFormat(Format.getPrettyFormat());
103: outputter.output(doc, writer);
104: writer.close();
105: res.setStatus(HttpServletResponse.SC_OK);
106: } else if (handler.isCollectionURI(pathInfo)) {
107: // return a collection
108: Feed col = handler.getCollection(pathInfo);
109: col.setFeedType(FEED_TYPE);
110: WireFeedOutput wireFeedOutput = new WireFeedOutput();
111: Document feedDoc = wireFeedOutput.outputJDom(col);
112: res
113: .setContentType("application/atom+xml; charset=utf-8");
114: Writer writer = res.getWriter();
115: XMLOutputter outputter = new XMLOutputter();
116: outputter.setFormat(Format.getPrettyFormat());
117: outputter.output(feedDoc, writer);
118: writer.close();
119: res.setStatus(HttpServletResponse.SC_OK);
120: } else if (handler.isEntryURI(pathInfo)) {
121: // return an entry
122: Entry entry = handler.getEntry(pathInfo);
123: if (entry != null) {
124: res
125: .setContentType("application/atom+xml; charset=utf-8");
126: Writer writer = res.getWriter();
127: serializeEntry(entry, writer);
128: writer.close();
129: } else {
130: res.setStatus(HttpServletResponse.SC_NOT_FOUND);
131: }
132: } else {
133: res.setStatus(HttpServletResponse.SC_NOT_FOUND);
134: }
135: } catch (AtomException ae) {
136: res.setStatus(ae.getStatus());
137: mLogger.debug(ae);
138: } catch (Exception ae) {
139: res
140: .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
141: mLogger.debug(ae);
142: }
143: } else {
144: res.setHeader("WWW-Authenticate", "BASIC realm=\"Roller\"");
145: res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
146: }
147: }
148:
149: //-----------------------------------------------------------------------------
150: /**
151: * Handles an Atom POST by calling handler to identify URI, reading/parsing
152: * data, calling handler and writing results to response.
153: */
154: protected void doPost(HttpServletRequest req,
155: HttpServletResponse res) throws ServletException,
156: IOException {
157: AtomHandler handler = createAtomRequestHandler(req);
158: String userName = handler.getAuthenticatedUsername();
159: if (userName != null) {
160: String[] pathInfo = getPathInfo(req);
161: try {
162: if (handler.isCollectionURI(pathInfo)) {
163:
164: if (req.getContentType().startsWith(
165: "application/atom+xml")) {
166:
167: // parse incoming entry
168: Entry unsavedEntry = parseEntry(new BufferedReader(
169: new InputStreamReader(req
170: .getInputStream(), "UTF-8")));
171:
172: // call handler to post it
173: Entry savedEntry = handler.postEntry(pathInfo,
174: unsavedEntry);
175:
176: // return alternate link as Location header
177: Iterator links = savedEntry.getOtherLinks()
178: .iterator();
179: while (links.hasNext()) {
180: Link link = (Link) links.next();
181: if (link.getRel().equals("edit")
182: || link.getRel() == null) {
183: res.addHeader("Location", link
184: .getHref());
185: break;
186: }
187: }
188: // write entry back out to response
189: res.setStatus(HttpServletResponse.SC_CREATED);
190: res
191: .setContentType("application/atom+xml; charset=utf-8");
192: Writer writer = res.getWriter();
193: serializeEntry(savedEntry, writer);
194: writer.close();
195:
196: } else if (req.getContentType() != null) {
197: // get incoming title and slug from HTTP header
198: String title = req.getHeader("Title");
199: String slug = req.getHeader("Slug");
200:
201: // hand input stream of to hander to post file
202: Entry resource = handler.postMedia(pathInfo,
203: title, slug, req.getContentType(), req
204: .getInputStream());
205:
206: res.setStatus(HttpServletResponse.SC_CREATED);
207: com.sun.syndication.feed.atom.Content content = (com.sun.syndication.feed.atom.Content) resource
208: .getContents().get(0);
209:
210: // return alternate link as Location header
211: Iterator links = resource.getOtherLinks()
212: .iterator();
213: while (links.hasNext()) {
214: Link link = (Link) links.next();
215: if (link.getRel().equals("edit")
216: || link.getRel() == null) {
217: res.addHeader("Location", link
218: .getHref());
219: break;
220: }
221: }
222: Writer writer = res.getWriter();
223: serializeEntry(resource, writer);
224: writer.close();
225: } else {
226: res
227: .setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
228: }
229:
230: } else {
231: res.setStatus(HttpServletResponse.SC_NOT_FOUND);
232: }
233: } catch (AtomException ae) {
234: res.setStatus(ae.getStatus());
235: mLogger.debug(ae);
236: } catch (Exception ae) {
237: res
238: .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
239: mLogger.debug(ae);
240: }
241: } else {
242: res.setHeader("WWW-Authenticate", "BASIC realm=\"Roller\"");
243: res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
244: }
245: }
246:
247: //-----------------------------------------------------------------------------
248: /**
249: * Handles an Atom PUT by calling handler to identify URI, reading/parsing
250: * data, calling handler and writing results to response.
251: */
252: protected void doPut(HttpServletRequest req, HttpServletResponse res)
253: throws ServletException, IOException {
254: AtomHandler handler = createAtomRequestHandler(req);
255: String userName = handler.getAuthenticatedUsername();
256: if (userName != null) {
257: String[] pathInfo = getPathInfo(req);
258: try {
259: if (handler.isEntryURI(pathInfo)) {
260:
261: // parse incoming entry
262: Entry unsavedEntry = parseEntry(new BufferedReader(
263: new InputStreamReader(req.getInputStream(),
264: "UTF-8")));
265:
266: // call handler to put entry
267: Entry updatedEntry = handler.putEntry(pathInfo,
268: unsavedEntry);
269:
270: // write entry back out to response
271: res
272: .setContentType("application/atom+xml; charset=utf-8");
273: Writer writer = res.getWriter();
274: serializeEntry(updatedEntry, writer);
275: res.setStatus(HttpServletResponse.SC_OK);
276: writer.close();
277:
278: } else if (handler.isMediaEditURI(pathInfo)) {
279:
280: // hand input stream to handler
281: Entry updatedEntry = handler.putMedia(pathInfo, req
282: .getContentType(), req.getInputStream());
283:
284: // write entry back out to response
285: res
286: .setContentType("application/atom+xml; charset=utf-8");
287: Writer writer = res.getWriter();
288: serializeEntry(updatedEntry, writer);
289: writer.close();
290: res.setStatus(HttpServletResponse.SC_OK);
291:
292: } else {
293: res.setStatus(HttpServletResponse.SC_NOT_FOUND);
294: }
295: } catch (AtomException ae) {
296: res.setStatus(ae.getStatus());
297: mLogger.debug(ae);
298: } catch (Exception ae) {
299: res
300: .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
301: mLogger.debug(ae);
302: }
303: } else {
304: res.setHeader("WWW-Authenticate", "BASIC realm=\"Roller\"");
305: res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
306: }
307: }
308:
309: //-----------------------------------------------------------------------------
310: /**
311: * Handle Atom DELETE by calling appropriate handler.
312: */
313: protected void doDelete(HttpServletRequest req,
314: HttpServletResponse res) throws ServletException,
315: IOException {
316: AtomHandler handler = createAtomRequestHandler(req);
317: String userName = handler.getAuthenticatedUsername();
318: if (userName != null) {
319: String[] pathInfo = getPathInfo(req);
320: try {
321: if (handler.isEntryURI(pathInfo)) {
322: handler.deleteEntry(pathInfo);
323: res.setStatus(HttpServletResponse.SC_OK);
324: } else {
325: res.setStatus(HttpServletResponse.SC_NOT_FOUND);
326: }
327: } catch (AtomException ae) {
328: res.setStatus(ae.getStatus());
329: mLogger.debug(ae);
330: } catch (Exception ae) {
331: res
332: .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
333: mLogger.debug(ae);
334: }
335: } else {
336: res.setHeader("WWW-Authenticate", "BASIC realm=\"Roller\"");
337: res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
338: }
339: }
340:
341: //-----------------------------------------------------------------------------
342: /**
343: * Convenience method to return the PathInfo from the request.
344: */
345: protected String[] getPathInfo(HttpServletRequest request) {
346: String mPathInfo = request.getPathInfo();
347: mPathInfo = (mPathInfo != null) ? mPathInfo : "";
348: return StringUtils.split(mPathInfo, "/");
349: }
350:
351: /**
352: * Serialize entry to writer.
353: */
354: public static void serializeEntry(Entry entry, Writer writer)
355: throws IllegalArgumentException, FeedException, IOException {
356: // Build a feed containing only the entry
357: List entries = new ArrayList();
358: entries.add(entry);
359: Feed feed1 = new Feed();
360: feed1.setFeedType(AtomServlet.FEED_TYPE);
361: feed1.setEntries(entries);
362:
363: // Get Rome to output feed as a JDOM document
364: WireFeedOutput wireFeedOutput = new WireFeedOutput();
365: Document feedDoc = wireFeedOutput.outputJDom(feed1);
366:
367: // Grab entry element from feed and get JDOM to serialize it
368: Element entryElement = (Element) feedDoc.getRootElement()
369: .getChildren().get(0);
370:
371: // Add our own namespaced element, so we can determine if we can
372: // count on client to preserve foreign markup as it should.
373: Element rollerElement = new Element("atom-draft",
374: "http://rollerweblogger.org/namespaces/app");
375: rollerElement.setText("9");
376: entryElement.addContent(rollerElement);
377:
378: XMLOutputter outputter = new XMLOutputter();
379: outputter.setFormat(Format.getPrettyFormat());
380:
381: if (mLogger.isDebugEnabled()) {
382: StringWriter sw = new StringWriter();
383: outputter.output(entryElement, sw);
384: mLogger.debug(sw.toString());
385: writer.write(sw.toString());
386: } else {
387: outputter.output(entryElement, writer);
388: }
389: }
390:
391: /**
392: * Parse entry from reader.
393: */
394: public static Entry parseEntry(Reader rd) throws JDOMException,
395: IOException, IllegalArgumentException, FeedException {
396: // Parse entry into JDOM tree
397: SAXBuilder builder = new SAXBuilder();
398: Document entryDoc = builder.build(rd);
399: Element fetchedEntryElement = entryDoc.getRootElement();
400: fetchedEntryElement.detach();
401:
402: // Put entry into a JDOM document with 'feed' root so that Rome can handle it
403: Feed feed = new Feed();
404: feed.setFeedType(FEED_TYPE);
405: WireFeedOutput wireFeedOutput = new WireFeedOutput();
406: Document feedDoc = wireFeedOutput.outputJDom(feed);
407: feedDoc.getRootElement().addContent(fetchedEntryElement);
408:
409: // Check for our special namespaced element. If it's there, then we
410: // know that client is not preserving foreign markup.
411: Namespace ns = Namespace
412: .getNamespace("http://rollerweblogger.org/namespaces/app");
413: Element rollerElement = fetchedEntryElement.getChild(
414: "atom-draft", ns);
415: if (rollerElement == null) {
416: mLogger.debug("Client is NOT preserving foreign markup");
417: }
418:
419: WireFeedInput input = new WireFeedInput();
420: Feed parsedFeed = (Feed) input.build(feedDoc);
421: return (Entry) parsedFeed.getEntries().get(0);
422: }
423: }
|