Java tutorial
/* * 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) { return createMediaEntry(request); } @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) { return createNonMediaEntry(request); } protected String getLink(T entryObj, IRI feedIri, RequestContext request) throws ResponseContextException { return getLink(entryObj, feedIri, request, false); } protected String getLink(T entryObj, IRI feedIri, RequestContext request, boolean absolute) throws ResponseContextException { return getLink(getName(entryObj), entryObj, feedIri, request, absolute); } protected String getLink(String name, T entryObj, IRI feedIri, RequestContext request) { return getLink(name, entryObj, feedIri, request, false); } protected String getLink(String name, T entryObj, IRI feedIri, RequestContext request, boolean absolute) { feedIri = feedIri.trailingSlash(); IRI entryIri = feedIri.resolve(UrlEncoding.encode(name, Profile.PATH.filter())); if (absolute) { entryIri = request.getResolvedUri().resolve(entryIri); } 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); String location = getLink(entryObj, feedUri, request, true); return buildPostMediaEntryResponse(location, 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) { 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"); String location = getLink(entryObj, feedUri, request, true); return buildCreateEntryResponse(location, 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; } }