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; import java.io.IOException; import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.List; import javax.activation.MimeType; import javax.xml.namespace.QName; import org.apache.abdera.Abdera; import org.apache.abdera.i18n.iri.IRI; import org.apache.abdera.i18n.text.Localizer; import org.apache.abdera.i18n.text.Sanitizer; import org.apache.abdera.model.AtomDate; import org.apache.abdera.model.Base; import org.apache.abdera.model.Content; import org.apache.abdera.model.Document; import org.apache.abdera.model.Element; import org.apache.abdera.model.Entry; import org.apache.abdera.model.ExtensibleElement; import org.apache.abdera.model.Feed; import org.apache.abdera.model.Link; import org.apache.abdera.protocol.error.Error; import org.apache.abdera.protocol.server.context.AbstractResponseContext; import org.apache.abdera.protocol.server.context.BaseResponseContext; import org.apache.abdera.protocol.server.context.EmptyResponseContext; import org.apache.abdera.protocol.server.context.StreamWriterResponseContext; import org.apache.abdera.util.Constants; import org.apache.abdera.util.EntityTag; import org.apache.abdera.util.MimeTypeHelper; import org.apache.abdera.writer.NamedWriter; import org.apache.abdera.writer.StreamWriter; import org.apache.abdera.writer.WriterFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Provides support methods for {@link Provider} */ public class ProviderHelper { private final static Log log = LogFactory.getLog(ProviderHelper.class); private ProviderHelper() { } public static int getPageSize(RequestContext request, String pagesizeparam, int defaultpagesize) { int size = defaultpagesize; try { String _ps = request.getParameter(pagesizeparam); size = (_ps != null) ? Math.min(Math.max(Integer.parseInt(_ps), 0), defaultpagesize) : defaultpagesize; } catch (Exception e) { } log.debug(Localizer.sprintf("PAGE.SIZE", size)); return size; } public static int getOffset(RequestContext request, String pageparam, int pageSize) { int offset = 0; try { String _page = request.getParameter(pageparam); int page = (_page != null) ? Integer.parseInt(_page) : 1; page = Math.max(page, 1) - 1; offset = pageSize * page; } catch (Exception e) { } log.debug(Localizer.sprintf("OFFSET", offset)); return offset; } /** * Returns an Error document based on the StreamWriter */ public static AbstractResponseContext createErrorResponse(Abdera abdera, final int code, final String message) { return createErrorResponse(abdera, code, message, null); } /** * Returns an Error document based on the StreamWriter */ public static AbstractResponseContext createErrorResponse(Abdera abdera, final int code, final String message, final Throwable t) { AbstractResponseContext rc = new StreamWriterResponseContext(abdera) { protected void writeTo(StreamWriter sw) throws IOException { Error.create(sw, code, message, t); } }; rc.setStatus(code); rc.setStatusText(message); return rc; } /** * Return a server error */ public static ResponseContext servererror(RequestContext request, String reason, Throwable t) { log.info(Localizer.get("SERVER_ERROR"), t); return createErrorResponse(request.getAbdera(), 500, reason, t); } /** * Return a server error */ public static ResponseContext servererror(RequestContext request, Throwable t) { return servererror(request, "Server Error", t); } /** * Return an unauthorized error */ public static ResponseContext unauthorized(RequestContext request, String reason) { log.debug(Localizer.get("UNAUTHORIZED")); return createErrorResponse(request.getAbdera(), 401, reason); } public static ResponseContext unauthorized(RequestContext request) { return unauthorized(request, "Unauthorized"); } /** * Return an unauthorized error */ public static ResponseContext forbidden(RequestContext request, String reason) { log.debug(Localizer.get("FORBIDDEN")); return createErrorResponse(request.getAbdera(), 403, reason); } public static ResponseContext forbidden(RequestContext request) { return forbidden(request, "Forbidden"); } /** * Return a 204 No Content response */ public static ResponseContext nocontent(String reason) { return new EmptyResponseContext(204, reason); } public static ResponseContext nocontent() { return nocontent("Not Content"); } /** * Return a 404 not found error */ public static ResponseContext notfound(RequestContext request, String reason) { log.debug(Localizer.get("UNKNOWN")); return createErrorResponse(request.getAbdera(), 404, reason); } public static ResponseContext notfound(RequestContext request) { return notfound(request, "Not Found"); } /** * Return a 405 method not allowed error */ public static ResponseContext notallowed(RequestContext request, String reason, String... methods) { log.debug(Localizer.get("NOT.ALLOWED")); AbstractResponseContext resp = createErrorResponse(request.getAbdera(), 405, reason); resp.setAllow(methods); return resp; } public static ResponseContext notallowed(RequestContext request, String... methods) { return notallowed(request, "Method Not Allowed", methods); } public static ResponseContext notallowed(RequestContext request) { return notallowed(request, getDefaultMethods(request)); } /** * Return a 400 bad request error */ public static ResponseContext badrequest(RequestContext request, String reason) { log.debug(Localizer.get("BAD.REQUEST")); return createErrorResponse(request.getAbdera(), 400, reason); } public static ResponseContext badrequest(RequestContext request) { return badrequest(request, "Bad Request"); } /** * Return a 409 conflict error */ public static ResponseContext conflict(RequestContext request, String reason) { log.debug(Localizer.get("CONFLICT")); return createErrorResponse(request.getAbdera(), 409, reason); } public static ResponseContext conflict(RequestContext request) { return conflict(request, "Conflict"); } /** * Return a service unavailable error */ public static ResponseContext unavailable(RequestContext request, String reason) { log.debug(Localizer.get("UNAVAILABLE")); return createErrorResponse(request.getAbdera(), 503, reason); } public static ResponseContext unavailable(RequestContext request) { return unavailable(request, "Service Unavailable"); } public static ResponseContext notmodified(RequestContext request, String reason) { log.debug(Localizer.get("NOT.MODIFIED")); return new EmptyResponseContext(304, reason); } public static ResponseContext notmodified(RequestContext request) { return notmodified(request, "Not Modified"); } public static ResponseContext preconditionfailed(RequestContext request, String reason) { log.debug(Localizer.get("PRECONDITION.FAILED")); return createErrorResponse(request.getAbdera(), 412, reason); } public static ResponseContext preconditionfailed(RequestContext request) { return preconditionfailed(request, "Precondition Failed"); } /** * Return a 415 media type not-supported error */ public static ResponseContext notsupported(RequestContext request, String reason) { log.debug(Localizer.get("NOT.SUPPORTED")); return createErrorResponse(request.getAbdera(), 415, reason); } public static ResponseContext notsupported(RequestContext request) { return notsupported(request, "Media Type Not Supported"); } /** * Return a 423 locked error */ public static ResponseContext locked(RequestContext request, String reason) { log.debug(Localizer.get("LOCKED")); return createErrorResponse(request.getAbdera(), 423, reason); } public static ResponseContext locked(RequestContext request) { return locked(request, "Locked"); } /** * Return a document */ @SuppressWarnings("unchecked") public static ResponseContext returnBase(Base base, int status, Date lastModified) { log.debug(Localizer.get("RETURNING.DOCUMENT")); BaseResponseContext response = new BaseResponseContext(base); response.setStatus(status); if (lastModified != null) response.setLastModified(lastModified); // response.setContentType(MimeTypeHelper.getMimeType(base)); Document doc = base instanceof Document ? (Document) base : ((Element) base).getDocument(); if (doc.getEntityTag() != null) { response.setEntityTag(doc.getEntityTag()); } else if (doc.getLastModified() != null) { response.setLastModified(doc.getLastModified()); } return response; } /** * Sanitize the value of a Slug header. Any non alphanumeric characters in the slug are replaced with an underscore */ public static String sanitizeSlug(String slug) { if (slug == null) throw new IllegalArgumentException(Localizer.get("SLUG.NOT.NULL")); String sanitized = Sanitizer.sanitize(slug); log.debug(Localizer.sprintf("SLUG.SANITIZED", slug, sanitized)); return sanitized; } /** * Check to see if the entry is minimally valid according to RFC4287. This is not a complete check. It just verifies * that the appropriate elements are present and that their values can be accessed. */ public static boolean isValidEntry(Entry entry) { try { IRI id = entry.getId(); if (id == null || id.toString().trim().length() == 0 || !id.isAbsolute()) return false; if (entry.getTitle() == null) return false; if (entry.getUpdated() == null) return false; if (entry.getAuthor() == null && (entry.getSource() != null && entry.getSource().getAuthor() == null)) return false; Content content = entry.getContentElement(); if (content == null) { if (entry.getAlternateLink() == null) return false; } else { if ((content.getSrc() != null || content.getContentType() == Content.Type.MEDIA) && entry.getSummaryElement() == null) { log.debug(Localizer.sprintf("CHECKING.VALID.ENTRY", false)); return false; } } } catch (Exception e) { log.debug(Localizer.sprintf("CHECKING.VALID.ENTRY", false)); return false; } log.debug(Localizer.sprintf("CHECKING.VALID.ENTRY", true)); return true; } /** * Return false if the element contains any extension elements that are not supported */ public static boolean checkElementNamespaces(Element element, List<String> ignore) { List<QName> attrs = element.getExtensionAttributes(); for (QName qname : attrs) { String ns = qname.getNamespaceURI(); if (!ignore.contains(ns)) return false; } if (element instanceof ExtensibleElement) { ExtensibleElement ext = (ExtensibleElement) element; List<Element> extensions = ext.getExtensions(); for (Element el : extensions) { QName qname = el.getQName(); String ns = qname.getNamespaceURI(); if (!ignore.contains(ns)) return false; if (!checkElementNamespaces(el, ignore)) return false; } } return true; } public static boolean beforeOrEqual(Date d1, Date d2) { long l1 = d1.getTime() / 1000; // drop milliseconds long l2 = d2.getTime() / 1000; // drop milliseconds return l1 <= l2; } public static IRI resolveBase(RequestContext request) { return request.getBaseUri().resolve(request.getUri()); } public static String combine(String... vals) { StringBuilder buf = new StringBuilder(); for (String val : vals) { if (buf.length() > 0) buf.append(", "); buf.append(val); } return buf.toString(); } public static String[] getDefaultMethods(RequestContext request) { TargetType type = request.getTarget().getType(); if (type == null) return new String[0]; if (type == TargetType.TYPE_COLLECTION) return new String[] { "GET", "HEAD", "OPTIONS", "POST" }; if (type == TargetType.TYPE_CATEGORIES) return new String[] { "GET", "HEAD", "OPTIONS" }; if (type == TargetType.TYPE_ENTRY) return new String[] { "DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT" }; if (type == TargetType.TYPE_MEDIA) return new String[] { "DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT" }; if (type == TargetType.TYPE_SERVICE) return new String[] { "GET", "HEAD", "OPTIONS" }; return new String[] { "GET", "HEAD", "OPTIONS" }; } public static boolean defaultCheckMethod(RequestContext request, String[] methods) { return (java.util.Arrays.binarySearch(methods, request.getMethod()) >= 0); } public static boolean isAtom(RequestContext request) { MimeType mt = request.getContentType(); String ctype = (mt != null) ? mt.toString() : null; return ctype != null && MimeTypeHelper.isAtom(ctype); } private static class QTokenComparator implements Comparator<QToken> { public int compare(QToken o1, QToken o2) { if (o1.qvalue > o2.qvalue) return -1; if (o1.qvalue < o2.qvalue) return 1; return 0; } } private static class QToken { String token; double qvalue = 1.0; QToken(String token, double qvalue) { this.token = token; this.qvalue = qvalue; } } /** * <p> * Utility method for parsing HTTP content negotiation headers and sorting their tokens according to their q * parameter values. * </p> * <p> * e.g. Accept: audio/*; q=0.2, audio/basic, audio/mpeg; q=0.1 * </p> * <p> * would sort into: * </p> * * <pre> * audio/basic * audio/* * audio/mpeg * </pre> */ public static String[] orderByQ(String header) { if (header == null || header.length() == 0) return new String[0]; String[] tokens = header.split(","); QToken[] qtokens = new QToken[tokens.length]; for (int i = 0; i < tokens.length; i++) { String token = tokens[i]; String[] qvalues = token.trim().split(";"); String t = qvalues[0]; if (qvalues.length > 1) { for (int n = 1; n < qvalues.length; n++) { String[] v = qvalues[n].trim().split("="); if (v[0].trim().equals("q")) { double qv = Double.parseDouble(v[1]); qtokens[i] = new QToken(t, qv); break; } } } if (qtokens[i] == null) qtokens[i] = new QToken(t, 1.0); } Arrays.sort(qtokens, new QTokenComparator()); tokens = new String[qtokens.length]; for (int n = 0; n < qtokens.length; n++) { tokens[n] = qtokens[n].token; } return tokens; } /** * Returns an appropriate NamedWriter instance given an appropriately formatted HTTP Accept header. The header will * be parsed and sorted according to it's q parameter values. The first named writer capable of supporting the * specified type, in order of q-value preference, will be returned. The results on this are not always predictable. * For instance, if the Accept header says "application/*" it could end up with either the JSON writer or the * PrettyXML writer, or any other writer that supports any writer that supports a specific form of "application/*". * It's always best to be very specific in the Accept headers. */ public static NamedWriter getAcceptableNamedWriter(Abdera abdera, String accept_header) { String[] sorted_accepts = orderByQ(accept_header); WriterFactory factory = abdera.getWriterFactory(); if (factory == null) return null; for (String accept : sorted_accepts) { NamedWriter writer = (NamedWriter) factory.getWriterByMediaType(accept); if (writer != null) return writer; } return null; } public static NamedWriter getNamedWriter(Abdera abdera, String mediatype) { WriterFactory factory = abdera.getWriterFactory(); if (factory == null) return null; NamedWriter writer = (NamedWriter) factory.getWriterByMediaType(mediatype); return writer; } public static EntityTag calculateEntityTag(Base base) { String id = null; String modified = null; if (base instanceof Entry) { Entry entry = (Entry) base; id = entry.getId().toString(); modified = AtomDate.format(entry.getEdited() != null ? entry.getEdited() : entry.getUpdated()); } else if (base instanceof Feed) { Feed feed = (Feed) base; id = feed.getId().toString(); modified = AtomDate.format(feed.getUpdated()); } else if (base instanceof Document) { return calculateEntityTag(((Document<?>) base).getRoot()); } return EntityTag.generate(id, modified); } public static String getEditUriFromEntry(Entry entry) { String editUri = null; List<Link> editLinks = entry.getLinks("edit"); if (editLinks != null) { for (Link link : editLinks) { // if there is more than one edit link, we should not automatically // assume that it's always going to point to an Atom document // representation. if (link.getMimeType() != null) { if (MimeTypeHelper.isMatch(link.getMimeType().toString(), Constants.ATOM_MEDIA_TYPE)) { editUri = link.getResolvedHref().toString(); break; } } else { // edit link with no type attribute is the right one to use editUri = link.getResolvedHref().toString(); break; } } } return editUri; } public static String[] getAcceptableTypes(RequestContext request) { String accept = request.getAccept(); return orderByQ(accept); } public static boolean isPreferred(RequestContext request, String s1, String s2) { return isPreferred(getAcceptableTypes(request), s1, s2); } public static boolean isPreferred(String[] accepts, String s1, String s2) { int i1 = accepts.length, i2 = accepts.length; for (int n = 0; n < accepts.length; n++) { if (MimeTypeHelper.isMatch(s1, accepts[n])) { i1 = n; break; } } for (int n = 0; n < accepts.length; n++) { if (MimeTypeHelper.isMatch(s2, accepts[n])) { i2 = n; break; } } return i1 < i2; } }