0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. The ASF licenses this file to You
0004: * under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License. For additional information regarding
0015: * copyright in this work, please see the NOTICE file in the top level
0016: * directory of this distribution.
0017: */
0018: package org.apache.roller.webservices.atomprotocol;
0019:
0020: import java.io.File;
0021: import java.io.FileInputStream;
0022: import java.io.FileOutputStream;
0023: import java.io.InputStream;
0024: import java.sql.Timestamp;
0025: import java.util.ArrayList;
0026: import java.util.Date;
0027: import java.util.Iterator;
0028: import java.util.List;
0029: import java.util.StringTokenizer;
0030: import java.util.Collections;
0031: import javax.activation.MimetypesFileTypeMap;
0032:
0033: import javax.servlet.http.HttpServletRequest;
0034:
0035: import org.apache.commons.codec.binary.Base64;
0036: import org.apache.commons.logging.Log;
0037: import org.apache.commons.logging.LogFactory;
0038: import org.apache.roller.business.FileManager;
0039: import org.apache.roller.business.Roller;
0040: import org.apache.roller.business.RollerFactory;
0041: import org.apache.roller.pojos.UserData;
0042: import org.apache.roller.pojos.PermissionsData;
0043: import org.apache.roller.pojos.WeblogCategoryData;
0044: import org.apache.roller.pojos.WeblogEntryData;
0045: import org.apache.roller.pojos.WebsiteData;
0046: import org.apache.roller.ui.core.RollerContext;
0047: import org.apache.roller.util.RollerMessages;
0048: import org.apache.roller.util.Utilities;
0049: import org.apache.roller.util.WSSEUtilities;
0050:
0051: import com.sun.syndication.feed.atom.Content;
0052: import com.sun.syndication.feed.atom.Category;
0053: import com.sun.syndication.feed.atom.Entry;
0054: import com.sun.syndication.feed.atom.Feed;
0055: import com.sun.syndication.feed.atom.Link;
0056: import com.sun.syndication.feed.atom.Person;
0057: import java.io.IOException;
0058: import java.text.SimpleDateFormat;
0059: import java.util.Comparator;
0060: import java.util.Map;
0061: import java.util.SortedSet;
0062: import java.util.TreeSet;
0063: import javax.activation.FileTypeMap;
0064: import org.apache.commons.lang.StringUtils;
0065: import org.apache.roller.RollerException;
0066: import org.apache.roller.config.RollerConfig;
0067: import org.apache.roller.config.RollerRuntimeConfig;
0068: import org.apache.roller.business.WeblogManager;
0069: import org.apache.roller.pojos.RollerPropertyData;
0070: import org.apache.roller.pojos.WeblogResource;
0071: import org.apache.roller.util.URLUtilities;
0072: import org.apache.roller.util.cache.CacheManager;
0073:
0074: /**
0075: * Roller's Atom Protocol implementation.
0076: * <pre>
0077: * Each Roller workspace has two collections, one that accepts entries and
0078: * that accepts everything. The entries collection represents the weblog
0079: * entries in a single weblog and the everything collection represents that
0080: * weblog's uploaded-files.
0081: *
0082: * Here are the APP URIs suppored by Roller:
0083: *
0084: * /roller-services/app
0085: * Introspection doc
0086: *
0087: * /roller-services/app/<weblog-handle>/entries
0088: * Entry collection for a blog
0089: *
0090: * /roller-services/app/<weblog-handle>/entries/<offset>
0091: * Entry collection for a blog, with offset
0092: *
0093: * /roller-services/app/<weblog-handle>/entry/<id>
0094: * Individual entry (i.e. edit URI)
0095: *
0096: * /roller-services/app/<weblog-handle>/resources
0097: * Resource (i.e. file-uploads) collection for a blog
0098: *
0099: * /roller-services/app/<weblog-handle>/resources/<offset>
0100: * Resource collection for a blog, with offset
0101: *
0102: * /roller-services/app/<weblog-handle>/resource/*.media-link<name>
0103: * Individual resource metadata (i.e. edit URI)
0104: *
0105: * /roller-services/app/<weblog-handle>/resource/<name>
0106: * Individual resource data (i.e. media-edit URI)
0107: *
0108: * </pre>
0109: *
0110: * @author David M Johnson
0111: */
0112: public class RollerAtomHandler implements AtomHandler {
0113: private HttpServletRequest mRequest;
0114: private Roller mRoller;
0115: private RollerContext mRollerContext;
0116: private UserData user;
0117: private int mMaxEntries = 20;
0118: //private MessageDigest md5Helper = null;
0119: //private MD5Encoder md5Encoder = new MD5Encoder();
0120:
0121: private static Log mLogger = LogFactory.getFactory().getInstance(
0122: RollerAtomHandler.class);
0123:
0124: //---------------------------------------------------------------- construction
0125:
0126: /**
0127: * Create Atom handler for a request and attempt to authenticate user.
0128: * If user is authenticated, then getAuthenticatedUsername() will return
0129: * then user's name, otherwise it will return null.
0130: */
0131: public RollerAtomHandler(HttpServletRequest request) {
0132: mRequest = request;
0133: mRoller = RollerFactory.getRoller();
0134: mRollerContext = RollerContext.getRollerContext();
0135:
0136: // TODO: decide what to do about authentication, is WSSE going to fly?
0137: //String userName = authenticateWSSE(request);
0138: String userName = authenticateBASIC(request);
0139: if (userName != null) {
0140: try {
0141: this .user = mRoller.getUserManager().getUserByUserName(
0142: userName);
0143: } catch (Exception neverHappen) {
0144: mLogger.debug("ERROR: getting user", neverHappen);
0145: }
0146: }
0147: }
0148:
0149: /**
0150: * Return weblogHandle of authenticated user or null if there is none.
0151: */
0152: public String getAuthenticatedUsername() {
0153: String ret = null;
0154: if (this .user != null) {
0155: ret = user.getUserName();
0156: }
0157: return ret;
0158: }
0159:
0160: //---------------------------------------------------------------- introspection
0161:
0162: /**
0163: * Return Atom service document for site, getting blog-name from pathInfo.
0164: * The workspace will contain collections for entries, categories and resources.
0165: */
0166: public AtomService getIntrospection() throws AtomException {
0167: AtomService service = new AtomService();
0168: List perms = null;
0169: try {
0170: perms = mRoller.getUserManager().getAllPermissions(user);
0171:
0172: } catch (RollerException re) {
0173: throw new AtomException("ERROR: getting user's weblogs", re);
0174: }
0175: String accept = null;
0176: try {
0177: accept = getAcceptedContentTypeRange();
0178: } catch (RollerException re) {
0179: throw new AtomException(
0180: "ERROR: getting site's accept range", re);
0181: }
0182: if (perms != null) {
0183: for (Iterator iter = perms.iterator(); iter.hasNext();) {
0184: PermissionsData perm = (PermissionsData) iter.next();
0185: String handle = perm.getWebsite().getHandle();
0186: AtomService.Workspace workspace = new AtomService.Workspace();
0187: workspace.setTitle(Utilities.removeHTML(perm
0188: .getWebsite().getName()));
0189: service.addWorkspace(workspace);
0190:
0191: AtomService.Collection entryCol = new AtomService.Collection();
0192: entryCol.setTitle("Weblog Entries");
0193: entryCol.setAccept("entry");
0194: entryCol.setHref(URLUtilities.getAtomProtocolURL(true)
0195: + "/" + handle + "/entries");
0196: try {
0197: AtomService.Categories cats = new AtomService.Categories();
0198: cats.setFixed(true);
0199: cats.setScheme(URLUtilities.getWeblogURL(perm
0200: .getWebsite(), null, true));
0201: List rollerCats = mRoller.getWeblogManager()
0202: .getWeblogCategories(perm.getWebsite(),
0203: false);
0204: for (Iterator it = rollerCats.iterator(); it
0205: .hasNext();) {
0206: WeblogCategoryData rollerCat = (WeblogCategoryData) it
0207: .next();
0208: AtomService.Category cat = new AtomService.Category();
0209: cat.setTerm(rollerCat.getPath());
0210: cat.setLabel(rollerCat.getName());
0211: cats.addCategory(cat);
0212: }
0213: entryCol.addCategories(cats);
0214: } catch (Exception e) {
0215: throw new AtomException(
0216: "ERROR fetching weblog categories");
0217: }
0218: workspace.addCollection(entryCol);
0219:
0220: AtomService.Collection uploadCol = new AtomService.Collection();
0221: uploadCol.setTitle("Media Files");
0222: uploadCol.setAccept(accept);
0223: uploadCol.setHref(URLUtilities.getAtomProtocolURL(true)
0224: + "/" + handle + "/resources");
0225: workspace.addCollection(uploadCol);
0226: }
0227: }
0228: return service;
0229: }
0230:
0231: /**
0232: * Build accept range by taking things that appear to be content-type rules
0233: * from site's file-upload allowed extensions.
0234: */
0235: private String getAcceptedContentTypeRange() throws RollerException {
0236: StringBuffer sb = new StringBuffer();
0237: Roller roller = RollerFactory.getRoller();
0238: Map config = roller.getPropertiesManager().getProperties();
0239: String allows = ((RollerPropertyData) config
0240: .get("uploads.types.allowed")).getValue();
0241: String[] rules = StringUtils.split(StringUtils
0242: .deleteWhitespace(allows), ",");
0243: for (int i = 0; i < rules.length; i++) {
0244: if (rules[i].indexOf("/") == -1)
0245: continue;
0246: if (sb.length() != 0) {
0247: sb.append(",");
0248: }
0249: sb.append(rules[i]);
0250: }
0251: return sb.toString();
0252: }
0253:
0254: //----------------------------------------------------------------- collections
0255:
0256: /**
0257: * Return collection specified by pathinfo.
0258: * <pre>
0259: * Supports these URI forms:
0260: * /<blog-name>/entries
0261: * /<blog-name>/entries/offset
0262: * /<blog-name>/resources
0263: * /<blog-name>/resources/offset
0264: * </pre>
0265: */
0266: public Feed getCollection(String[] pathInfo) throws AtomException {
0267: int start = 0;
0268: if (pathInfo.length > 2) {
0269: try {
0270: String s = pathInfo[2].trim();
0271: start = Integer.parseInt(s);
0272: } catch (Throwable t) {
0273: mLogger.warn("Unparsable range: " + pathInfo[2]);
0274: }
0275: }
0276: if (pathInfo.length > 0 && pathInfo[1].equals("entries")) {
0277: return getCollectionOfEntries(pathInfo, start, mMaxEntries);
0278: } else if (pathInfo.length > 0
0279: && pathInfo[1].equals("resources")) {
0280: return getCollectionOfResources(pathInfo, start,
0281: mMaxEntries);
0282: }
0283: throw new AtomNotFoundException(
0284: "ERROR: cannot find collection specified");
0285: }
0286:
0287: /**
0288: * Helper method that returns collection of entries, called by getCollection().
0289: */
0290: public Feed getCollectionOfEntries(String[] pathInfo, int start,
0291: int max) throws AtomException {
0292: try {
0293: String handle = pathInfo[0];
0294: String absUrl = RollerRuntimeConfig.getAbsoluteContextURL();
0295: WebsiteData website = mRoller.getUserManager()
0296: .getWebsiteByHandle(handle);
0297: if (website == null) {
0298: throw new AtomNotFoundException(
0299: "ERROR: cannot find specified weblog");
0300: }
0301: List entries = null;
0302: if (canView(website)) {
0303: entries = mRoller.getWeblogManager().getWeblogEntries(
0304: website, // website
0305: null, // user
0306: null, // startDate
0307: null, // endDate
0308: null, // catName
0309: null, // tags
0310: null, // status
0311: "updateTime", // sortby
0312: null, // locale
0313: start, // offset (for range paging)
0314: max + 1); // maxEntries
0315: Feed feed = new Feed();
0316: feed.setId(URLUtilities.getAtomProtocolURL(true) + "/"
0317: + website.getHandle() + "/entries/" + start);
0318: feed.setTitle(website.getName());
0319:
0320: Link link = new Link();
0321: link.setHref(absUrl + "/" + website.getHandle());
0322: link.setRel("alternate");
0323: link.setType("text/html");
0324: feed.setAlternateLinks(Collections.singletonList(link));
0325:
0326: List atomEntries = new ArrayList();
0327: int count = 0;
0328: for (Iterator iter = entries.iterator(); iter.hasNext()
0329: && count < mMaxEntries; count++) {
0330: WeblogEntryData rollerEntry = (WeblogEntryData) iter
0331: .next();
0332: Entry entry = createAtomEntry(rollerEntry);
0333: atomEntries.add(entry);
0334: if (count == 0) {
0335: // first entry is most recent
0336: feed.setUpdated(entry.getUpdated());
0337: }
0338: }
0339: List links = new ArrayList();
0340: if (entries.size() > max) { // add next link
0341: int nextOffset = start + max;
0342: String url = URLUtilities.getAtomProtocolURL(true)
0343: + "/" + website.getHandle() + "/entries/"
0344: + nextOffset;
0345: Link nextLink = new Link();
0346: nextLink.setRel("next");
0347: nextLink.setHref(url);
0348: links.add(nextLink);
0349: }
0350: if (start > 0) { // add previous link
0351: int prevOffset = start > max ? start - max : 0;
0352: String url = URLUtilities.getAtomProtocolURL(true)
0353: + "/" + website.getHandle() + "/entries/"
0354: + prevOffset;
0355: Link prevLink = new Link();
0356: prevLink.setRel("previous");
0357: prevLink.setHref(url);
0358: links.add(prevLink);
0359: }
0360: if (links.size() > 0)
0361: feed.setOtherLinks(links);
0362: // Use collection URI as id
0363: feed.setEntries(atomEntries);
0364: return feed;
0365: }
0366: throw new AtomNotAuthorizedException(
0367: "ERROR: not authorized to access website");
0368:
0369: } catch (RollerException re) {
0370: throw new AtomException("ERROR: getting entry collection");
0371: }
0372: }
0373:
0374: /**
0375: * Helper method that returns collection of resources, called by getCollection().
0376: */
0377: public Feed getCollectionOfResources(String[] pathInfo, int start,
0378: int max) throws AtomException {
0379: try {
0380: String handle = pathInfo[0];
0381: String absUrl = RollerRuntimeConfig.getAbsoluteContextURL();
0382: WebsiteData website = mRoller.getUserManager()
0383: .getWebsiteByHandle(handle);
0384: if (website == null) {
0385: throw new AtomNotFoundException(
0386: "ERROR: cannot find specified weblog");
0387: }
0388: FileManager fmgr = mRoller.getFileManager();
0389: WeblogResource[] files = fmgr.getFiles(website, null);
0390:
0391: if (canView(website)) {
0392: Feed feed = new Feed();
0393: feed.setId(URLUtilities.getAtomProtocolURL(true) + "/"
0394: + website.getHandle() + "/entries/" + start);
0395: feed.setTitle(website.getName());
0396:
0397: Link link = new Link();
0398: link.setHref(absUrl + "/" + website.getHandle());
0399: link.setRel("alternate");
0400: link.setType("text/html");
0401: feed.setAlternateLinks(Collections.singletonList(link));
0402:
0403: SortedSet sortedSet = new TreeSet(new Comparator() {
0404: public int compare(Object o1, Object o2) {
0405: WeblogResource f1 = (WeblogResource) o1;
0406: WeblogResource f2 = (WeblogResource) o2;
0407: if (f1.getLastModified() < f2.getLastModified())
0408: return 1;
0409: else if (f1.getLastModified() == f2
0410: .getLastModified())
0411: return 0;
0412: else
0413: return -1;
0414: }
0415:
0416: public boolean equals(Object obj) {
0417: return false;
0418: }
0419: });
0420: List atomEntries = new ArrayList();
0421: if (files != null && start < files.length) {
0422: for (int i = 0; i < files.length; i++) {
0423: sortedSet.add(files[i]);
0424: }
0425: }
0426: int count = 0;
0427: WeblogResource[] sortedArray = (WeblogResource[]) sortedSet
0428: .toArray(new WeblogResource[sortedSet.size()]);
0429: for (int i = start; i < (start + max)
0430: && i < (sortedArray.length); i++) {
0431: Entry entry = createAtomResourceEntry(website,
0432: sortedArray[i]);
0433: atomEntries.add(entry);
0434: if (count == 0) {
0435: // first entry is most recent
0436: feed.setUpdated(entry.getUpdated());
0437: }
0438: count++;
0439: }
0440: if (start + count < files.length) { // add next link
0441: int nextOffset = start + max;
0442: String url = URLUtilities.getAtomProtocolURL(true)
0443: + "/" + website.getHandle() + "/resources/"
0444: + nextOffset;
0445: Link nextLink = new Link();
0446: nextLink.setRel("next");
0447: nextLink.setHref(url);
0448: List next = new ArrayList();
0449: next.add(nextLink);
0450: feed.setOtherLinks(next);
0451: }
0452: if (start > 0) { // add previous link
0453: int prevOffset = start > max ? start - max : 0;
0454: String url = URLUtilities.getAtomProtocolURL(true)
0455: + "/" + website.getHandle() + "/resources/"
0456: + prevOffset;
0457: Link prevLink = new Link();
0458: prevLink.setRel("previous");
0459: prevLink.setHref(url);
0460: List prev = new ArrayList();
0461: prev.add(prevLink);
0462: feed.setOtherLinks(prev);
0463: }
0464: feed.setEntries(atomEntries);
0465: return feed;
0466: }
0467: throw new AtomNotAuthorizedException(
0468: "ERROR: not authorized to access website");
0469:
0470: } catch (RollerException re) {
0471: throw new AtomException(
0472: "ERROR: getting resource collection");
0473: }
0474: }
0475:
0476: //--------------------------------------------------------------------- entries
0477:
0478: /**
0479: * Create entry in the entry collection (a Roller blog has only one).
0480: */
0481: public Entry postEntry(String[] pathInfo, Entry entry)
0482: throws AtomException {
0483: try {
0484: // authenticated client posted a weblog entry
0485: String handle = pathInfo[0];
0486: WebsiteData website = mRoller.getUserManager()
0487: .getWebsiteByHandle(handle);
0488: if (website == null) {
0489: throw new AtomNotFoundException(
0490: "ERROR: cannot find specified weblog");
0491: }
0492: if (canEdit(website)) {
0493: // Save it and commit it
0494: WeblogManager mgr = mRoller.getWeblogManager();
0495: WeblogEntryData rollerEntry = createRollerEntry(
0496: website, entry);
0497: rollerEntry.setCreator(this .user);
0498: mgr.saveWeblogEntry(rollerEntry);
0499: mRoller.flush();
0500:
0501: // Throttle one entry per second
0502: // (MySQL timestamp has 1 sec resolution, damnit)
0503: try {
0504: Thread.sleep(1000);
0505: } catch (Exception ignored) {
0506: }
0507:
0508: CacheManager.invalidate(website);
0509: if (rollerEntry.isPublished()) {
0510: mRoller.getIndexManager().addEntryReIndexOperation(
0511: rollerEntry);
0512: }
0513: return createAtomEntry(rollerEntry);
0514: }
0515: throw new AtomNotAuthorizedException(
0516: "ERROR: not authorized to access website");
0517:
0518: } catch (RollerException re) {
0519: throw new AtomException("ERROR: posting entry");
0520: }
0521: }
0522:
0523: /**
0524: * Retrieve entry, URI like this /blog-name/entry/id
0525: */
0526: public Entry getEntry(String[] pathInfo) throws AtomException {
0527: try {
0528: if (pathInfo.length == 3) // URI is /blogname/entries/entryid
0529: {
0530: if (pathInfo[1].equals("entry")) {
0531: WeblogEntryData entry = mRoller.getWeblogManager()
0532: .getWeblogEntry(pathInfo[2]);
0533: if (entry == null) {
0534: throw new AtomNotFoundException(
0535: "ERROR: cannot find specified entry/resource");
0536: }
0537: if (!canView(entry)) {
0538: throw new AtomNotAuthorizedException(
0539: "ERROR: not authorized to view entry");
0540: } else {
0541: return createAtomEntry(entry);
0542: }
0543: } else if (pathInfo[1].equals("resource")
0544: && pathInfo[2].endsWith(".media-link")) {
0545: String fileName = pathInfo[2].substring(0,
0546: pathInfo[2].length()
0547: - ".media-link".length());
0548: String handle = pathInfo[0];
0549: WebsiteData website = mRoller.getUserManager()
0550: .getWebsiteByHandle(handle);
0551:
0552: // TODO: this must have broken with 3.0, but i don't know
0553: // how it's supposed to work so i can't really fix it
0554: // it's unlikely this was working properly before because
0555: // it was using an invalid path
0556: // File resource =
0557: // new File("/resources" + File.separator + fileName);
0558: // return createAtomResourceEntry(website, resource);
0559: return null;
0560: }
0561: }
0562: throw new AtomNotFoundException(
0563: "ERROR: cannot find specified entry/resource");
0564: } catch (RollerException re) {
0565: throw new AtomException("ERROR: getting entry");
0566: }
0567: }
0568:
0569: /**
0570: * Update entry, URI like this /blog-name/entry/id
0571: */
0572: public Entry putEntry(String[] pathInfo, Entry entry)
0573: throws AtomException {
0574: try {
0575: if (pathInfo.length == 3) // URI is /blogname/entries/entryid
0576: {
0577: WeblogEntryData rollerEntry = mRoller
0578: .getWeblogManager().getWeblogEntry(pathInfo[2]);
0579: if (rollerEntry == null) {
0580: throw new AtomNotFoundException(
0581: "ERROR: cannot find specified entry/resource");
0582: }
0583: if (canEdit(rollerEntry)) {
0584: WeblogManager mgr = mRoller.getWeblogManager();
0585:
0586: WeblogEntryData rawUpdate = createRollerEntry(
0587: rollerEntry.getWebsite(), entry);
0588: rollerEntry.setPubTime(rawUpdate.getPubTime());
0589: rollerEntry
0590: .setUpdateTime(rawUpdate.getUpdateTime());
0591: rollerEntry.setText(rawUpdate.getText());
0592: rollerEntry.setStatus(rawUpdate.getStatus());
0593: rollerEntry.setCategory(rawUpdate.getCategory());
0594: rollerEntry.setTitle(rawUpdate.getTitle());
0595:
0596: mgr.saveWeblogEntry(rollerEntry);
0597: mRoller.flush();
0598:
0599: CacheManager.invalidate(rollerEntry.getWebsite());
0600: if (rollerEntry.isPublished()) {
0601: mRoller.getIndexManager()
0602: .addEntryReIndexOperation(rollerEntry);
0603: }
0604: return createAtomEntry(rollerEntry);
0605: }
0606: throw new AtomNotAuthorizedException(
0607: "ERROR not authorized to update entry");
0608: }
0609: throw new AtomNotFoundException(
0610: "ERROR: cannot find specified entry/resource");
0611:
0612: } catch (RollerException re) {
0613: throw new AtomException("ERROR: updating entry");
0614: }
0615: }
0616:
0617: /**
0618: * Delete entry, URI like this /blog-name/entry/id
0619: */
0620: public void deleteEntry(String[] pathInfo) throws AtomException {
0621: try {
0622: if (pathInfo.length == 3) // URI is /blogname/entry/entryid
0623: {
0624: if (pathInfo[1].equals("entry")) {
0625: WeblogEntryData rollerEntry = mRoller
0626: .getWeblogManager().getWeblogEntry(
0627: pathInfo[2]);
0628: if (rollerEntry == null) {
0629: throw new AtomNotFoundException(
0630: "ERROR: cannot find specified entry/resource");
0631: }
0632: if (canEdit(rollerEntry)) {
0633: WeblogManager mgr = mRoller.getWeblogManager();
0634: mgr.removeWeblogEntry(rollerEntry);
0635: mRoller.flush();
0636: CacheManager.invalidate(rollerEntry
0637: .getWebsite());
0638: mRoller.getIndexManager()
0639: .removeEntryIndexOperation(rollerEntry);
0640: return;
0641: }
0642: } else if (pathInfo[1].equals("resource")) {
0643: String handle = pathInfo[0];
0644: WebsiteData website = mRoller.getUserManager()
0645: .getWebsiteByHandle(handle);
0646: if (website == null) {
0647: throw new AtomNotFoundException(
0648: "ERROR: cannot find specified weblog");
0649: }
0650: if (canEdit(website) && pathInfo.length > 1) {
0651: try {
0652: String fileName = pathInfo[2];
0653: if (pathInfo[2].endsWith(".media-link")) {
0654: fileName = fileName.substring(0,
0655: pathInfo[2].length()
0656: - ".media-link"
0657: .length());
0658: }
0659: FileManager fmgr = mRoller.getFileManager();
0660: fmgr.deleteFile(website, fileName);
0661: } catch (Exception e) {
0662: String msg = "ERROR in atom.deleteResource";
0663: mLogger.error(msg, e);
0664: throw new AtomException(msg);
0665: }
0666: return;
0667: }
0668: }
0669: throw new AtomNotAuthorizedException(
0670: "ERROR not authorized to delete entry");
0671: }
0672: throw new AtomNotFoundException(
0673: "ERROR: cannot find specified entry/resource");
0674:
0675: } catch (RollerException re) {
0676: throw new AtomException("ERROR: deleting entry");
0677: }
0678: }
0679:
0680: //-------------------------------------------------------------------- resources
0681:
0682: /**
0683: * Create new resource in generic collection (a Roller blog has only one).
0684: * TODO: can we avoid saving temporary file?
0685: * TODO: do we need to handle mutli-part MIME uploads?
0686: * TODO: use Jakarta Commons File-upload?
0687: */
0688: public Entry postMedia(String[] pathInfo, String title,
0689: String slug, String contentType, InputStream is)
0690: throws AtomException {
0691: try {
0692: // authenticated client posted a weblog entry
0693: File tempFile = null;
0694: RollerMessages msgs = new RollerMessages();
0695: String handle = pathInfo[0];
0696: WebsiteData website = mRoller.getUserManager()
0697: .getWebsiteByHandle(handle);
0698: if (canEdit(website) && pathInfo.length > 1) {
0699: // save to temp file
0700: String fileName = createFileName(website,
0701: (slug != null) ? slug : title, contentType);
0702: try {
0703: FileManager fmgr = mRoller.getFileManager();
0704: tempFile = File.createTempFile(fileName, "tmp");
0705: FileOutputStream fos = new FileOutputStream(
0706: tempFile);
0707: Utilities.copyInputToOutput(is, fos);
0708: fos.close();
0709:
0710: // Try saving file
0711: FileInputStream fis = new FileInputStream(tempFile);
0712: fmgr.saveFile(website, fileName, contentType,
0713: tempFile.length(), fis);
0714: fis.close();
0715:
0716: WeblogResource resource = fmgr.getFile(website,
0717: fileName);
0718: return createAtomResourceEntry(website, resource);
0719:
0720: } catch (IOException e) {
0721: String msg = "ERROR reading posted file";
0722: mLogger.error(msg, e);
0723: throw new AtomException(msg, e);
0724: } finally {
0725: if (tempFile != null)
0726: tempFile.delete();
0727: }
0728: }
0729: // TODO: AtomUnsupportedMediaType and AtomRequestEntityTooLarge needed?
0730: throw new AtomException("File upload denied because:"
0731: + msgs.toString());
0732:
0733: } catch (RollerException re) {
0734: throw new AtomException("ERROR: posting media");
0735: }
0736: }
0737:
0738: /**
0739: * Creates a file name for a file based on a weblog, title string and a
0740: * content-type.
0741: *
0742: * @param weblog Weblog for which file name is being created
0743: * @param title Title to be used as basis for file name (or null)
0744: * @param contentType Content type of file (must not be null)
0745: *
0746: * If a title is specified, the method will apply the same create-anchor
0747: * logic we use for weblog entries to create a file name based on the title.
0748: *
0749: * If title is null, the base file name will be the weblog handle plus a
0750: * YYYYMMDDHHSS timestamp.
0751: *
0752: * The extension will be formed by using the part of content type that
0753: * comes after he slash.
0754: *
0755: * For example:
0756: * weblog.handle = "daveblog"
0757: * title = "Port Antonio"
0758: * content-type = "image/jpg"
0759: * Would result in port_antonio.jpg
0760: *
0761: * Another example:
0762: * weblog.handle = "daveblog"
0763: * title = null
0764: * content-type = "image/jpg"
0765: * Might result in daveblog-200608201034.jpg
0766: */
0767: private String createFileName(WebsiteData weblog, String title,
0768: String contentType) {
0769:
0770: if (weblog == null)
0771: throw new IllegalArgumentException("weblog cannot be null");
0772: if (contentType == null)
0773: throw new IllegalArgumentException(
0774: "contentType cannot be null");
0775:
0776: String fileName = null;
0777:
0778: // Determine the extension based on the contentType. This is a hack.
0779: // The info we need to map from contentType to file extension is in
0780: // JRE/lib/content-type.properties, but Java Activation doesn't provide
0781: // a way to do a reverse mapping or to get at the data.
0782: String[] typeTokens = contentType.split("/");
0783: String ext = typeTokens[1];
0784:
0785: if (title != null && !title.trim().equals("")) {
0786: // We've got a title, so use it to build file name
0787: String base = Utilities.replaceNonAlphanumeric(title, ' ');
0788: StringTokenizer toker = new StringTokenizer(base);
0789: String tmp = null;
0790: int count = 0;
0791: while (toker.hasMoreTokens() && count < 5) {
0792: String s = toker.nextToken();
0793: s = s.toLowerCase();
0794: tmp = (tmp == null) ? s : tmp + "_" + s;
0795: count++;
0796: }
0797: fileName = tmp + "." + ext;
0798:
0799: } else {
0800: // No title or text, so instead we'll use the item's date
0801: // in YYYYMMDD format to form the file name
0802: SimpleDateFormat sdf = new SimpleDateFormat();
0803: sdf.applyPattern("yyyyMMddHHSS");
0804: fileName = weblog.getHandle() + "-"
0805: + sdf.format(new Date()) + "." + ext;
0806: }
0807:
0808: return fileName;
0809: }
0810:
0811: /**
0812: * Update resource specified by pathInfo using data from input stream.
0813: * Expects pathInfo of form /blog-name/resource/name
0814: */
0815: public Entry putMedia(String[] pathInfo, String contentType,
0816: InputStream is) throws AtomException {
0817: if (pathInfo.length > 2) {
0818: String name = pathInfo[2];
0819: return postMedia(pathInfo, name, name, contentType, is);
0820: }
0821: throw new AtomException("ERROR: bad pathInfo");
0822: }
0823:
0824: //------------------------------------------------------------------ URI testers
0825:
0826: /**
0827: * True if URL is the introspection URI.
0828: */
0829: public boolean isIntrospectionURI(String[] pathInfo) {
0830: if (pathInfo.length == 0)
0831: return true;
0832: return false;
0833: }
0834:
0835: /**
0836: * True if URL is a entry URI.
0837: */
0838: public boolean isEntryURI(String[] pathInfo) {
0839: if (pathInfo.length > 2 && pathInfo[1].equals("entry"))
0840: return true;
0841: if (pathInfo.length > 2 && pathInfo[1].equals("resource"))
0842: return true;
0843: return false;
0844: }
0845:
0846: /**
0847: * True if URL is media edit URI. Media can be udpated, but not metadata.
0848: */
0849: public boolean isMediaEditURI(String[] pathInfo) {
0850: if (pathInfo.length > 1 && pathInfo[1].equals("resource"))
0851: return true;
0852: return false;
0853: }
0854:
0855: /**
0856: * True if URL is a category URI.
0857: */
0858: public boolean isCategoryURI(String[] pathInfo) {
0859: if (pathInfo.length > 1 && pathInfo[1].equals("category"))
0860: return true;
0861: return false;
0862: }
0863:
0864: /**
0865: * True if URL is a collection URI of any sort.
0866: */
0867: public boolean isCollectionURI(String[] pathInfo) {
0868: if (pathInfo.length > 1 && pathInfo[1].equals("entries"))
0869: return true;
0870: if (pathInfo.length > 1 && pathInfo[1].equals("resources"))
0871: return true;
0872: if (pathInfo.length > 1 && pathInfo[1].equals("categories"))
0873: return true;
0874: return false;
0875: }
0876:
0877: //------------------------------------------------------------------ permissions
0878:
0879: /**
0880: * Return true if user is allowed to edit an entry.
0881: */
0882: private boolean canEdit(WeblogEntryData entry) {
0883: try {
0884: return entry.hasWritePermissions(this .user);
0885: } catch (Exception e) {
0886: mLogger.error("ERROR: checking website.canSave()");
0887: }
0888: return false;
0889: }
0890:
0891: /**
0892: * Return true if user is allowed to create/edit weblog entries and file uploads in a website.
0893: */
0894: private boolean canEdit(WebsiteData website) {
0895: try {
0896: return website.hasUserPermissions(this .user,
0897: PermissionsData.AUTHOR);
0898: } catch (Exception e) {
0899: mLogger
0900: .error("ERROR: checking website.hasUserPermissions()");
0901: }
0902: return false;
0903: }
0904:
0905: /**
0906: * Return true if user is allowed to view an entry.
0907: */
0908: private boolean canView(WeblogEntryData entry) {
0909: return canEdit(entry);
0910: }
0911:
0912: /**
0913: * Return true if user is allowed to view a website.
0914: */
0915: private boolean canView(WebsiteData website) {
0916: return canEdit(website);
0917: }
0918:
0919: //-------------------------------------------------------------- authentication
0920:
0921: /**
0922: * Perform WSSE authentication based on information in request.
0923: * Will not work if Roller password encryption is turned on.
0924: */
0925: protected String authenticateWSSE(HttpServletRequest request) {
0926: String wsseHeader = request.getHeader("X-WSSE");
0927: if (wsseHeader == null)
0928: return null;
0929:
0930: String ret = null;
0931: String userName = null;
0932: String created = null;
0933: String nonce = null;
0934: String passwordDigest = null;
0935: String[] tokens = wsseHeader.split(",");
0936: for (int i = 0; i < tokens.length; i++) {
0937: int index = tokens[i].indexOf('=');
0938: if (index != -1) {
0939: String key = tokens[i].substring(0, index).trim();
0940: String value = tokens[i].substring(index + 1).trim();
0941: value = value.replaceAll("\"", "");
0942: if (key.startsWith("UsernameToken")) {
0943: userName = value;
0944: } else if (key.equalsIgnoreCase("nonce")) {
0945: nonce = value;
0946: } else if (key.equalsIgnoreCase("passworddigest")) {
0947: passwordDigest = value;
0948: } else if (key.equalsIgnoreCase("created")) {
0949: created = value;
0950: }
0951: }
0952: }
0953: String digest = null;
0954: try {
0955: UserData user = mRoller.getUserManager().getUserByUserName(
0956: userName);
0957: digest = WSSEUtilities.generateDigest(WSSEUtilities
0958: .base64Decode(nonce), created.getBytes("UTF-8"),
0959: user.getPassword().getBytes("UTF-8"));
0960: if (digest.equals(passwordDigest)) {
0961: ret = userName;
0962: }
0963: } catch (Exception e) {
0964: mLogger.error("ERROR in wsseAuthenticataion: "
0965: + e.getMessage(), e);
0966: }
0967: return ret;
0968: }
0969:
0970: /**
0971: * BASIC authentication.
0972: */
0973: public String authenticateBASIC(HttpServletRequest request) {
0974: boolean valid = false;
0975: String userID = null;
0976: String password = null;
0977: try {
0978: String authHeader = request.getHeader("Authorization");
0979: if (authHeader != null) {
0980: StringTokenizer st = new StringTokenizer(authHeader);
0981: if (st.hasMoreTokens()) {
0982: String basic = st.nextToken();
0983: if (basic.equalsIgnoreCase("Basic")) {
0984: String credentials = st.nextToken();
0985: String userPass = new String(Base64
0986: .decodeBase64(credentials.getBytes()));
0987: int p = userPass.indexOf(":");
0988: if (p != -1) {
0989: userID = userPass.substring(0, p);
0990: UserData user = mRoller.getUserManager()
0991: .getUserByUserName(userID);
0992: boolean enabled = user.getEnabled()
0993: .booleanValue();
0994: if (enabled) {
0995: // are passwords encrypted?
0996: RollerContext rollerContext = RollerContext
0997: .getRollerContext();
0998: String encrypted = RollerConfig
0999: .getProperty("passwds.encryption.enabled");
1000: password = userPass.substring(p + 1);
1001: if ("true".equalsIgnoreCase(encrypted)) {
1002: password = Utilities
1003: .encodePassword(
1004: password,
1005: RollerConfig
1006: .getProperty("passwds.encryption.algorithm"));
1007: }
1008: valid = user.getPassword().equals(
1009: password);
1010: }
1011: }
1012: }
1013: }
1014: }
1015: } catch (Exception e) {
1016: mLogger.debug(e);
1017: }
1018: if (valid)
1019: return userID;
1020: return null;
1021: }
1022:
1023: //----------------------------------------------------------- internal utilities
1024:
1025: /**
1026: * Create a Rome Atom entry based on a Roller entry.
1027: * Content is escaped.
1028: * Link is stored as rel=alternate link.
1029: */
1030: private Entry createAtomEntry(WeblogEntryData entry) {
1031: Entry atomEntry = new Entry();
1032: Content content = new Content();
1033: content.setType(Content.HTML);
1034: content.setValue(entry.getText());
1035: List contents = new ArrayList();
1036: contents.add(content);
1037:
1038: String absUrl = RollerRuntimeConfig.getAbsoluteContextURL();
1039: atomEntry.setId(absUrl + entry.getPermaLink());
1040: atomEntry.setTitle(entry.getTitle());
1041: atomEntry.setContents(contents);
1042: atomEntry.setPublished(entry.getPubTime());
1043: atomEntry.setUpdated(entry.getUpdateTime());
1044:
1045: UserData creator = entry.getCreator();
1046: Person author = new Person();
1047: author.setName(creator.getUserName());
1048: author.setEmail(creator.getEmailAddress());
1049: atomEntry.setAuthors(Collections.singletonList(author));
1050:
1051: List categories = new ArrayList();
1052: Category atomCat = new Category();
1053: atomCat.setTerm(entry.getCategory().getPath());
1054: categories.add(atomCat);
1055: atomEntry.setCategories(categories);
1056:
1057: Link altlink = new Link();
1058: altlink.setRel("alternate");
1059: altlink.setHref(absUrl + entry.getPermaLink());
1060: List altlinks = new ArrayList();
1061: altlinks.add(altlink);
1062: atomEntry.setAlternateLinks(altlinks);
1063:
1064: Link editlink = new Link();
1065: editlink.setRel("edit");
1066: editlink.setHref(URLUtilities.getAtomProtocolURL(true) + "/"
1067: + entry.getWebsite().getHandle() + "/entry/"
1068: + entry.getId());
1069: List otherlinks = new ArrayList();
1070: otherlinks.add(editlink);
1071: atomEntry.setOtherLinks(otherlinks);
1072:
1073: List modules = new ArrayList();
1074: PubControlModule pubControl = new PubControlModuleImpl();
1075: pubControl.setDraft(!WeblogEntryData.PUBLISHED.equals(entry
1076: .getStatus()));
1077: modules.add(pubControl);
1078: atomEntry.setModules(modules);
1079:
1080: return atomEntry;
1081: }
1082:
1083: private Entry createAtomResourceEntry(WebsiteData website,
1084: WeblogResource file) {
1085: String absUrl = RollerRuntimeConfig.getAbsoluteContextURL();
1086: String editURI = URLUtilities.getAtomProtocolURL(true) + "/"
1087: + website.getHandle() + "/resource/" + file.getPath()
1088: + ".media-link";
1089: String editMediaURI = URLUtilities.getAtomProtocolURL(true)
1090: + "/" + website.getHandle() + "/resource/"
1091: + file.getPath();
1092: String viewURI = absUrl + "/resources/" + website.getHandle()
1093: + "/" + file.getPath();
1094:
1095: FileTypeMap map = FileTypeMap.getDefaultFileTypeMap();
1096: // TODO: figure out why PNG is missing from Java MIME types
1097: if (map instanceof MimetypesFileTypeMap) {
1098: try {
1099: ((MimetypesFileTypeMap) map)
1100: .addMimeTypes("image/png png PNG");
1101: } catch (Exception ignored) {
1102: }
1103: }
1104: String contentType = map.getContentType(file.getName());
1105:
1106: Entry entry = new Entry();
1107: entry.setId(editMediaURI);
1108: entry.setTitle(file.getName());
1109: entry.setUpdated(new Date(file.getLastModified()));
1110:
1111: List otherlinks = new ArrayList();
1112: entry.setOtherLinks(otherlinks);
1113: Link editlink = new Link();
1114: editlink.setRel("edit");
1115: editlink.setHref(editURI);
1116: otherlinks.add(editlink);
1117: Link editMedialink = new Link();
1118: editMedialink.setRel("edit-media");
1119: editMedialink.setHref(editMediaURI);
1120: otherlinks.add(editMedialink);
1121:
1122: Content content = new Content();
1123: content.setSrc(viewURI);
1124: content.setType(contentType);
1125: List contents = new ArrayList();
1126: contents.add(content);
1127: entry.setContents(contents);
1128:
1129: return entry;
1130: }
1131:
1132: /**
1133: * Create a Roller weblog entry based on a Rome Atom entry object
1134: */
1135: private WeblogEntryData createRollerEntry(WebsiteData website,
1136: Entry entry) throws RollerException {
1137:
1138: Timestamp current = new Timestamp(System.currentTimeMillis());
1139: Timestamp pubTime = current;
1140: Timestamp updateTime = current;
1141: if (entry.getPublished() != null) {
1142: pubTime = new Timestamp(entry.getPublished().getTime());
1143: }
1144: if (entry.getUpdated() != null) {
1145: updateTime = new Timestamp(entry.getUpdated().getTime());
1146: }
1147: WeblogEntryData rollerEntry = new WeblogEntryData();
1148: rollerEntry.setTitle(entry.getTitle());
1149: if (entry.getContents() != null
1150: && entry.getContents().size() > 0) {
1151: Content content = (Content) entry.getContents().get(0);
1152: rollerEntry.setText(content.getValue());
1153: }
1154: rollerEntry.setPubTime(pubTime);
1155: rollerEntry.setUpdateTime(updateTime);
1156: rollerEntry.setWebsite(website);
1157:
1158: PubControlModule control = (PubControlModule) entry
1159: .getModule("http://purl.org/atom/app#");
1160: if (control != null && control.getDraft()) {
1161: rollerEntry.setStatus(WeblogEntryData.DRAFT);
1162: } else {
1163: rollerEntry.setStatus(WeblogEntryData.PUBLISHED);
1164: }
1165:
1166: // Atom supports multiple cats, Roller supports one/entry
1167: // so here we take accept the first category that exists
1168: List categories = entry.getCategories();
1169: if (categories != null && categories.size() > 0) {
1170: for (int i = 0; i < categories.size(); i++) {
1171: Category cat = (Category) categories.get(i);
1172: // Caller has no way of knowing our categories, so be lenient here
1173: String catString = cat.getTerm() != null ? cat
1174: .getTerm() : cat.getLabel();
1175: if (catString != null) {
1176: WeblogCategoryData rollerCat = mRoller
1177: .getWeblogManager()
1178: .getWeblogCategoryByPath(website, catString);
1179: if (rollerCat != null) {
1180: // Found a valid category, so break out
1181: rollerEntry.setCategory(rollerCat);
1182: break;
1183: }
1184: }
1185: }
1186: }
1187: if (rollerEntry.getCategory() == null) {
1188: // no category? fall back to the default Blogger API category
1189: rollerEntry.setCategory(website.getBloggerCategory());
1190: }
1191: return rollerEntry;
1192: }
1193:
1194: }
|