| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. 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. For additional information regarding |
| * copyright in this work, please see the NOTICE file in the top level |
| * directory of this distribution. |
| */ |
| package org.apache.abdera.protocol.server.impl; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.Date; |
| import java.util.List; |
| |
| import javax.activation.MimeType; |
| |
| import org.apache.abdera.Abdera; |
| import org.apache.abdera.factory.Factory; |
| import org.apache.abdera.i18n.iri.IRI; |
| import org.apache.abdera.i18n.text.UrlEncoding; |
| import org.apache.abdera.i18n.text.CharUtils.Profile; |
| import org.apache.abdera.model.AtomDate; |
| import org.apache.abdera.model.Content; |
| import org.apache.abdera.model.Entry; |
| import org.apache.abdera.model.Feed; |
| import org.apache.abdera.model.Person; |
| import org.apache.abdera.model.Text; |
| import org.apache.abdera.parser.ParseException; |
| import org.apache.abdera.protocol.server.ProviderHelper; |
| import org.apache.abdera.protocol.server.RequestContext; |
| import org.apache.abdera.protocol.server.ResponseContext; |
| import org.apache.abdera.protocol.server.context.EmptyResponseContext; |
| import org.apache.abdera.protocol.server.context.MediaResponseContext; |
| import org.apache.abdera.protocol.server.context.ResponseContextException; |
| import org.apache.abdera.util.EntityTag; |
| import org.apache.abdera.util.MimeTypeHelper; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| /** |
| * By extending this class it becomes easy to build Collections which are |
| * backed by a set of entities - such as a database row, domain objects, or |
| * files. |
| * @param <T> The entity that this is backed by. |
| */ |
| public abstract class AbstractEntityCollectionAdapter<T> |
| extends AbstractCollectionAdapter { |
| private final static Log log = LogFactory.getLog(AbstractEntityCollectionAdapter.class); |
| |
| /** |
| * Create a new entry |
| * @param title The title of the entry (assumes that type="text") |
| * @param id The value of the atom:id element |
| * @param summary The summary of the entry |
| * @param updated The value of the atom:updated element |
| * @param authors Listing of atom:author elements |
| * @param context The content of the entry |
| * @param request The request context |
| */ |
| public abstract T postEntry( |
| String title, |
| IRI id, |
| String summary, |
| Date updated, |
| List<Person> authors, |
| Content content, |
| RequestContext request) |
| throws ResponseContextException; |
| |
| @Override |
| public ResponseContext postMedia(RequestContext request) { |
| try { |
| T entryObj = postMedia(request.getContentType(), |
| request.getSlug(), |
| request.getInputStream(), |
| request); |
| |
| IRI feedIri = getFeedIRI(entryObj, request); |
| |
| Entry entry = request.getAbdera().getFactory().newEntry(); |
| |
| addEntryDetails(request, entry, feedIri, entryObj); |
| |
| String mediaLink = addMediaContent(feedIri, entry, entryObj, request); |
| |
| return buildPostMediaEntryResponse(mediaLink, entry); |
| } catch (IOException e) { |
| return new EmptyResponseContext(500); |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| } |
| |
| @Override |
| public ResponseContext putMedia(RequestContext request) { |
| try { |
| String id = getResourceName(request); |
| T entryObj = getEntry(id, request); |
| |
| putMedia(entryObj, request.getContentType(), request.getSlug(), |
| request.getInputStream(), request); |
| |
| return new EmptyResponseContext(200); |
| } catch (IOException e) { |
| return new EmptyResponseContext(500); |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| } |
| |
| /** |
| * Update a media resource. By default this method is not allowed. Implementations |
| * must override this method to support media resource updates |
| * @param entryObj |
| * @param contentType The mime-type of the media resource |
| * @param slug The value of the Slug request header |
| * @param inputStream An input stream providing access to the request payload |
| * @param request The request context |
| */ |
| public void putMedia( |
| T entryObj, |
| MimeType contentType, |
| String slug, |
| InputStream inputStream, |
| RequestContext request) |
| throws ResponseContextException { |
| throw new ResponseContextException(ProviderHelper.notallowed(request)); |
| } |
| |
| public ResponseContext postEntry(RequestContext request) { |
| try { |
| Entry entry = getEntryFromRequest(request); |
| if (entry != null) { |
| if (!ProviderHelper.isValidEntry(entry)) |
| return new EmptyResponseContext(400); |
| |
| entry.setUpdated(new Date()); |
| |
| |
| T entryObj = postEntry(entry.getTitle(), |
| entry.getId(), |
| entry.getSummary(), |
| entry.getUpdated(), |
| entry.getAuthors(), |
| entry.getContentElement(), request); |
| entry.getIdElement().setValue(getId(entryObj)); |
| |
| IRI feedIri = getFeedIRI(entryObj, request); |
| String link = getLink(entryObj, feedIri, request); |
| |
| entry.addLink(link, "edit"); |
| |
| return buildCreateEntryResponse(link, entry); |
| } else { |
| return new EmptyResponseContext(400); |
| } |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| } |
| |
| protected String getLink( |
| T entryObj, |
| IRI feedIri, |
| RequestContext request) |
| throws ResponseContextException { |
| return getLink(getName(entryObj), entryObj, feedIri, request); |
| } |
| |
| protected String getLink( |
| String name, |
| T entryObj, |
| IRI feedIri, |
| RequestContext request) { |
| feedIri = feedIri.trailingSlash(); |
| IRI entryIri = feedIri.resolve(UrlEncoding.encode(name, Profile.PATH.filter())); |
| |
| String link = entryIri.toString(); |
| |
| String qp = getQueryParameters(entryObj, request); |
| if (qp != null && !"".equals(qp)) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(link) |
| .append("?") |
| .append(qp); |
| link = sb.toString(); |
| } |
| |
| return link; |
| } |
| |
| protected String getQueryParameters(T entryObj, RequestContext request) { |
| return null; |
| } |
| |
| /** |
| * Post a new media resource to the collection. By default, this method is |
| * not supported. Implementations must override this method to support posting |
| * media resources |
| * @param mimeType The mime-type of the resource |
| * @param slug The value of the Slug header |
| * @param inputStream An InputStream providing access to the request payload |
| * @param request The request context |
| */ |
| public T postMedia( |
| MimeType mimeType, |
| String slug, |
| InputStream inputStream, |
| RequestContext request) |
| throws ResponseContextException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public ResponseContext deleteEntry(RequestContext request) { |
| String id = getResourceName(request); |
| if (id != null) { |
| |
| try { |
| deleteEntry(id, request); |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| |
| return new EmptyResponseContext(204); |
| } else { |
| // TODO: is this right? |
| return new EmptyResponseContext(404); |
| } |
| } |
| |
| /** |
| * Delete an entry |
| * @param resourceName The entry to delete |
| * @param request The request context |
| */ |
| public abstract void deleteEntry( |
| String resourceName, |
| RequestContext request) |
| throws ResponseContextException; |
| |
| public ResponseContext deleteMedia(RequestContext request) { |
| String resourceName = getResourceName(request); |
| if (resourceName != null) { |
| |
| try { |
| deleteMedia(resourceName, request); |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| |
| return new EmptyResponseContext(204); |
| } else { |
| // TODO: is this right? |
| return new EmptyResponseContext(404); |
| } |
| } |
| |
| /** |
| * Delete a media resource. By default this method is not supported. Implementations |
| * must override this method to support deleting media resources |
| */ |
| public void deleteMedia( |
| String resourceName, |
| RequestContext request) |
| throws ResponseContextException { |
| throw new ResponseContextException(ProviderHelper.notsupported(request)); |
| } |
| |
| /** |
| * Get the authors for an entry. By default this returns null. Implementations |
| * must override in order to providing a listing of authors for an entry |
| */ |
| public List<Person> getAuthors( |
| T entry, |
| RequestContext request) |
| throws ResponseContextException { |
| return null; |
| } |
| |
| /** |
| * Get the content for the entry. |
| */ |
| public abstract Object getContent( |
| T entry, |
| RequestContext request) |
| throws ResponseContextException; |
| |
| // GET, POST, PUT, DELETE |
| |
| /** |
| * Get the content-type for the entry. By default this operation is not supported. |
| */ |
| public String getContentType(T entry) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Get the listing of entries requested |
| */ |
| public abstract Iterable<T> getEntries( |
| RequestContext request) |
| throws ResponseContextException; |
| |
| public ResponseContext getEntry(RequestContext request) { |
| try { |
| Entry entry = getEntryFromCollectionProvider(request); |
| if (entry != null) { |
| return buildGetEntryResponse(request, entry); |
| } else { |
| return new EmptyResponseContext(404); |
| } |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| } |
| |
| /** |
| * Get a specific entry |
| * @param resourceName The entry to get |
| * @param request The request context |
| */ |
| public abstract T getEntry( |
| String resourceName, |
| RequestContext request) |
| throws ResponseContextException; |
| |
| public ResponseContext headEntry(RequestContext request) { |
| try { |
| String resourceName = getResourceName(request); |
| T entryObj = getEntry(resourceName, request); |
| |
| if (entryObj != null) { |
| return buildHeadEntryResponse(request, resourceName, getUpdated(entryObj)); |
| } else { |
| return new EmptyResponseContext(404); |
| } |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| } |
| |
| public ResponseContext headMedia(RequestContext request) { |
| try { |
| String resourceName = getResourceName(request); |
| T entryObj = getEntry(resourceName, request); |
| |
| if (entryObj != null) { |
| return buildHeadEntryResponse(request, resourceName, getUpdated(entryObj)); |
| } else { |
| return new EmptyResponseContext(404); |
| } |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| } |
| public ResponseContext getFeed(RequestContext request) { |
| try { |
| Feed feed = createFeedBase(request); |
| |
| addFeedDetails(feed, request); |
| |
| return buildGetFeedResponse(feed); |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| } |
| |
| /** |
| * Adds the selected entries to the Feed document. By default, this will set |
| * the feed's atom:updated element to the current date and time |
| */ |
| protected void addFeedDetails( |
| Feed feed, |
| RequestContext request) |
| throws ResponseContextException { |
| feed.setUpdated(new Date()); |
| |
| Iterable<T> entries = getEntries(request); |
| if (entries != null) { |
| for (T entryObj : entries) { |
| Entry e = feed.addEntry(); |
| |
| IRI feedIri = new IRI(getFeedIriForEntry(entryObj, request)); |
| addEntryDetails(request, e, feedIri, entryObj); |
| |
| if (isMediaEntry(entryObj)) { |
| addMediaContent(feedIri, e, entryObj, request); |
| } else { |
| addContent(e, entryObj, request); |
| } |
| } |
| } |
| } |
| |
| private IRI getFeedIRI(T entryObj, RequestContext request) { |
| String feedIri = getFeedIriForEntry(entryObj, request); |
| return new IRI(feedIri).trailingSlash(); |
| } |
| |
| /** |
| * Gets the UUID for the specified entry. |
| * @param entry |
| * @return |
| */ |
| public abstract String getId(T entry) throws ResponseContextException; |
| |
| public ResponseContext getMedia(RequestContext request) { |
| try { |
| String resource = getResourceName(request); |
| T entryObj = getEntry(resource, request); |
| |
| if (entryObj == null) { |
| return new EmptyResponseContext(404); |
| } |
| |
| return buildGetMediaResponse(resource, entryObj); |
| } catch (ParseException pe) { |
| return new EmptyResponseContext(415); |
| } catch (ClassCastException cce) { |
| return new EmptyResponseContext(415); |
| } catch (ResponseContextException e) { |
| return e.getResponseContext(); |
| } catch (Exception e) { |
| log.warn(e.getMessage(), e); |
| return new EmptyResponseContext(400); |
| } |
| } |
| |
| /** |
| * Creates a ResponseContext for a GET media request. By default, this returns |
| * a MediaResponseContext containing the media resource. The last-modified |
| * header will be set. |
| */ |
| protected ResponseContext buildGetMediaResponse( |
| String id, |
| T entryObj) |
| throws ResponseContextException { |
| Date updated = getUpdated(entryObj); |
| MediaResponseContext ctx = new MediaResponseContext(getMediaStream(entryObj), |
| updated, |
| 200); |
| ctx.setContentType(getContentType(entryObj)); |
| ctx.setEntityTag(EntityTag.generate(id, AtomDate.format(updated))); |
| return ctx; |
| } |
| |
| /** |
| * Get the name of the media resource. By default this method is unsupported. |
| * Implementations must override. |
| */ |
| public String getMediaName(T entry) throws ResponseContextException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Get an input stream for the media resource. By default this method is unsupported. |
| * Implementations must override. |
| */ |
| public InputStream getMediaStream(T entry) throws ResponseContextException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * Get the name of the entry resource (used to construct links) |
| */ |
| public abstract String getName(T entry) throws ResponseContextException; |
| |
| /** |
| * Get the title fo the entry |
| */ |
| public abstract String getTitle(T entry) throws ResponseContextException; |
| |
| /** |
| * Get the value to use in the atom:updated element |
| */ |
| public abstract Date getUpdated(T entry) throws ResponseContextException; |
| |
| /** |
| * True if this entry is a media-link entry. By default this always returns |
| * false. Implementations must override to support media link entries |
| */ |
| public boolean isMediaEntry(T entry) throws ResponseContextException { |
| return false; |
| } |
| |
| public ResponseContext putEntry(RequestContext request) { |
| try { |
| String id = getResourceName(request); |
| T entryObj = getEntry(id, request); |
| |
| if (entryObj == null) { |
| return new EmptyResponseContext(404); |
| } |
| |
| Entry orig_entry = getEntryFromCollectionProvider(entryObj, new IRI(getFeedIriForEntry(entryObj, request)), request); |
| if (orig_entry != null) { |
| |
| MimeType contentType = request.getContentType(); |
| if (contentType != null && !MimeTypeHelper.isAtom(contentType.toString())) |
| return new EmptyResponseContext(415); |
| |
| Entry entry = getEntryFromRequest(request); |
| if (entry != null) { |
| if (!entry.getId().equals(orig_entry.getId())) |
| return new EmptyResponseContext(409); |
| |
| if (!ProviderHelper.isValidEntry(entry)) |
| return new EmptyResponseContext(400); |
| |
| putEntry(entryObj, entry.getTitle(), new Date(), entry.getAuthors(), |
| entry.getSummary(), entry.getContentElement(), request); |
| return new EmptyResponseContext(204); |
| } else { |
| return new EmptyResponseContext(400); |
| } |
| } else { |
| return new EmptyResponseContext(404); |
| } |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } catch (ParseException pe) { |
| return new EmptyResponseContext(415); |
| } catch (ClassCastException cce) { |
| return new EmptyResponseContext(415); |
| } catch (Exception e) { |
| log.warn(e.getMessage(), e); |
| return new EmptyResponseContext(400); |
| } |
| |
| } |
| |
| /** |
| * Get the Feed IRI |
| */ |
| protected String getFeedIriForEntry( |
| T entryObj, |
| RequestContext request) { |
| return getHref(request); |
| } |
| |
| /** |
| * Update an entry. |
| * @param entry The entry to update |
| * @param title The new title of the entry |
| * @param updated The new value of atom:updated |
| * @param authors To new listing of authors |
| * @param summary The new summary |
| * @param content The new content |
| * @param request The request context |
| */ |
| public abstract void putEntry( |
| T entry, |
| String title, |
| Date updated, |
| List<Person> authors, |
| String summary, |
| Content content, |
| RequestContext request) |
| throws ResponseContextException; |
| |
| /** |
| * Adds the atom:content element to an entry |
| */ |
| protected void addContent( |
| Entry e, |
| T doc, |
| RequestContext request) |
| throws ResponseContextException { |
| Object content = getContent(doc, request); |
| |
| if (content instanceof Content) { |
| e.setContentElement((Content)content); |
| } else if (content instanceof String) { |
| e.setContent((String)content); |
| } |
| } |
| |
| /** |
| * Add the details to an entry |
| * @param request The request context |
| * @param e The entry |
| * @param feedIri The feed IRI |
| * @param entryObj |
| */ |
| protected String addEntryDetails(RequestContext request, |
| Entry e, |
| IRI feedIri, |
| T entryObj) throws ResponseContextException { |
| String link = getLink(entryObj, feedIri, request); |
| |
| e.addLink(link, "edit"); |
| e.setId(getId(entryObj)); |
| e.setTitle(getTitle(entryObj)); |
| e.setUpdated(getUpdated(entryObj)); |
| |
| List<Person> authors = getAuthors(entryObj, request); |
| if (authors != null) { |
| for (Person a : authors) { |
| e.addAuthor(a); |
| } |
| } |
| |
| Text t = getSummary(entryObj, request); |
| if (t != null) { |
| e.setSummaryElement(t); |
| } |
| return link; |
| } |
| |
| /** |
| * Get the summary of the entry. By default this returns null. |
| */ |
| public Text getSummary( |
| T entry, |
| RequestContext request) |
| throws ResponseContextException { |
| return null; |
| } |
| |
| /** |
| * Add media content details to a media-link entry |
| * @param feedIri The feed iri |
| * @param entry The entry object |
| * @param entryObj |
| * @param request The request context |
| */ |
| protected String addMediaContent(IRI feedIri, |
| Entry entry, |
| T entryObj, |
| RequestContext request) throws ResponseContextException { |
| String name = getMediaName(entryObj); |
| |
| IRI mediaIri = new IRI(getLink(name, entryObj, feedIri, request)); |
| String mediaLink = mediaIri.toString(); |
| entry.setContent(mediaIri, getContentType(entryObj)); |
| entry.addLink(mediaLink, "edit-media"); |
| |
| return mediaLink; |
| } |
| |
| /** |
| * Create a media entry |
| * @param request The request context |
| */ |
| protected ResponseContext createMediaEntry(RequestContext request) { |
| try { |
| T entryObj = postMedia(request.getContentType(), |
| request.getSlug(), |
| request.getInputStream(), |
| request); |
| |
| IRI feedUri = getFeedIRI(entryObj, request); |
| |
| Entry entry = request.getAbdera().getFactory().newEntry(); |
| String link = addEntryDetails(request, entry, feedUri, entryObj); |
| addMediaContent(feedUri, entry, entryObj, request); |
| |
| return buildPostMediaEntryResponse(link, entry); |
| } catch (IOException e) { |
| return new EmptyResponseContext(500); |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| } |
| |
| /** |
| * Create a regular entry |
| * @param request The request context |
| */ |
| protected ResponseContext createNonMediaEntry(RequestContext request) throws IOException { |
| try { |
| Entry entry = getEntryFromRequest(request); |
| if (entry != null) { |
| if (!ProviderHelper.isValidEntry(entry)) |
| return new EmptyResponseContext(400); |
| |
| entry.setUpdated(new Date()); |
| |
| T entryObj = postEntry(entry.getTitle(), |
| entry.getId(), |
| entry.getSummary(), |
| entry.getUpdated(), |
| entry.getAuthors(), |
| entry.getContentElement(), |
| request); |
| |
| entry.getIdElement().setValue(getId(entryObj)); |
| |
| IRI feedUri = getFeedIRI(entryObj, request); |
| |
| String link = getLink(entryObj, feedUri, request); |
| entry.addLink(link, "edit"); |
| |
| return buildCreateEntryResponse(link, entry); |
| } else { |
| return new EmptyResponseContext(400); |
| } |
| } catch (ResponseContextException e) { |
| return createErrorResponse(e); |
| } |
| } |
| |
| protected Entry getEntryFromCollectionProvider(RequestContext request) throws ResponseContextException { |
| String id = getResourceName(request); |
| T entryObj = getEntry(id, request); |
| |
| if (entryObj == null) { |
| return null; |
| } |
| |
| IRI feedIri = new IRI(getFeedIriForEntry(entryObj, request)); |
| return getEntryFromCollectionProvider(entryObj, feedIri, request); |
| } |
| |
| Entry getEntryFromCollectionProvider(T entryObj, IRI feedIri, RequestContext request) |
| throws ResponseContextException { |
| Abdera abdera = request.getAbdera(); |
| Factory factory = abdera.getFactory(); |
| Entry entry = factory.newEntry(); |
| |
| return buildEntry(entryObj, entry, feedIri, request); |
| } |
| |
| /** |
| * Build the entry from the source object |
| * @param entryObj The source object |
| * @param entry The entry to build |
| * @param feedIri The feed IRI |
| * @param request The request context |
| */ |
| private Entry buildEntry(T entryObj, Entry entry, IRI feedIri, RequestContext request) |
| throws ResponseContextException { |
| addEntryDetails(request, entry, feedIri, entryObj); |
| |
| if (isMediaEntry(entryObj)) { |
| addMediaContent(feedIri, entry, entryObj, request); |
| } else { |
| addContent(entry, entryObj, request); |
| } |
| |
| return entry; |
| } |
| |
| } |