| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. 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. |
| */ |
| package org.apache.olingo.commons.api.format; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| /** |
| * Internally used {@link ContentType} for OData library. |
| * |
| * For more details on format and content of a {@link ContentType} see |
| * <em>Media Type</em> format as defined in |
| * <a href="http://www.ietf.org/rfc/rfc7231.txt">RFC 7231</a>, chapter 3.1.1.1. |
| * <pre> |
| * media-type = type "/" subtype *( OWS ";" OWS parameter ) |
| * type = token |
| * subtype = token |
| * OWS = *( SP / HTAB ) ; optional whitespace |
| * parameter = token "=" ( token / quoted-string ) |
| * </pre> |
| * |
| * Once created a {@link ContentType} is <b>IMMUTABLE</b>. |
| */ |
| public final class ContentType { |
| |
| private static final String APPLICATION = "application"; |
| private static final String TEXT = "text"; |
| private static final String MULTIPART = "multipart"; |
| |
| public static final String PARAMETER_CHARSET = "charset"; |
| public static final String PARAMETER_IEEE754_COMPATIBLE = "IEEE754Compatible"; |
| public static final String PARAMETER_ODATA_METADATA = "odata.metadata"; |
| |
| public static final String VALUE_ODATA_METADATA_NONE = "none"; |
| public static final String VALUE_ODATA_METADATA_MINIMAL = "minimal"; |
| public static final String VALUE_ODATA_METADATA_FULL = "full"; |
| |
| public static final ContentType APPLICATION_JSON = new ContentType(APPLICATION, "json", null); |
| public static final ContentType JSON = create(ContentType.APPLICATION_JSON, |
| PARAMETER_ODATA_METADATA, VALUE_ODATA_METADATA_MINIMAL); |
| public static final ContentType JSON_NO_METADATA = create(ContentType.APPLICATION_JSON, |
| PARAMETER_ODATA_METADATA, VALUE_ODATA_METADATA_NONE); |
| public static final ContentType JSON_FULL_METADATA = create(ContentType.APPLICATION_JSON, |
| PARAMETER_ODATA_METADATA, VALUE_ODATA_METADATA_FULL); |
| |
| public static final ContentType APPLICATION_XML = new ContentType(APPLICATION, "xml", null); |
| public static final ContentType APPLICATION_ATOM_XML = new ContentType(APPLICATION, "atom+xml", null); |
| public static final ContentType APPLICATION_ATOM_XML_ENTRY = create(APPLICATION_ATOM_XML, "type", "entry"); |
| public static final ContentType APPLICATION_ATOM_XML_ENTRY_UTF8 = create(APPLICATION_ATOM_XML_ENTRY, |
| PARAMETER_CHARSET, "utf-8"); |
| public static final ContentType APPLICATION_ATOM_XML_FEED = create(APPLICATION_ATOM_XML, "type", "feed"); |
| public static final ContentType APPLICATION_ATOM_XML_FEED_UTF8 = create(APPLICATION_ATOM_XML_FEED, |
| PARAMETER_CHARSET, "utf-8"); |
| public static final ContentType APPLICATION_ATOM_SVC = new ContentType(APPLICATION, "atomsvc+xml", null); |
| |
| public static final ContentType APPLICATION_OCTET_STREAM = new ContentType(APPLICATION, "octet-stream", null); |
| |
| public static final ContentType APPLICATION_XHTML_XML = new ContentType(APPLICATION, "xhtml+xml", null); |
| public static final ContentType TEXT_HTML = new ContentType(TEXT, "html", null); |
| public static final ContentType TEXT_XML = new ContentType(TEXT, "xml", null); |
| public static final ContentType TEXT_PLAIN = new ContentType(TEXT, "plain", null); |
| |
| public static final ContentType APPLICATION_SVG_XML = new ContentType(APPLICATION, "svg+xml", null); |
| |
| public static final ContentType APPLICATION_FORM_URLENCODED = |
| new ContentType(APPLICATION, "x-www-form-urlencoded", null); |
| |
| public static final ContentType APPLICATION_HTTP = new ContentType(APPLICATION, "http", null); |
| |
| public static final ContentType MULTIPART_MIXED = new ContentType(MULTIPART, "mixed", null); |
| public static final ContentType MULTIPART_FORM_DATA = new ContentType(MULTIPART, "form-data", null); |
| |
| private final String type; |
| private final String subtype; |
| private final Map<String, String> parameters; |
| |
| /** |
| * Creates a content type from type, subtype, and parameters. |
| * @param type type |
| * @param subtype subtype |
| * @param parameters parameters as map from names to values |
| */ |
| private ContentType(final String type, final String subtype, final Map<String, String> parameters) { |
| this.type = validateType(type); |
| this.subtype = validateType(subtype); |
| |
| if (parameters == null) { |
| this.parameters = Collections.emptyMap(); |
| } else { |
| this.parameters = TypeUtil.createParameterMap(); |
| this.parameters.putAll(parameters); |
| } |
| } |
| |
| private String validateType(final String type) throws IllegalArgumentException { |
| if (type == null || type.isEmpty() || "*".equals(type)) { |
| throw new IllegalArgumentException("Illegal type '" + type + "'."); |
| } |
| if (type.indexOf(TypeUtil.WHITESPACE_CHAR) >= 0) { |
| throw new IllegalArgumentException("Illegal whitespace found for type '" + type + "'."); |
| } |
| return type; |
| } |
| |
| /** |
| * Creates a content type from an existing content type and an additional parameter as key-value pair. |
| * @param contentType an existing content type |
| * @param parameterName the name of the additional parameter |
| * @param parameterValue the value of the additional parameter |
| * @return a new {@link ContentType} object |
| */ |
| public static ContentType create(final ContentType contentType, |
| final String parameterName, final String parameterValue) throws IllegalArgumentException { |
| TypeUtil.validateParameterNameAndValue(parameterName, parameterValue); |
| |
| ContentType type = new ContentType(contentType.type, contentType.subtype, contentType.parameters); |
| type.parameters.put(parameterName.toLowerCase(Locale.ROOT), parameterValue); |
| return type; |
| } |
| |
| /** |
| * Creates a {@link ContentType} based on given input string (<code>format</code>). Supported format is |
| * <code>Media Type</code> format as defined in RFC 7231, chapter 3.1.1.1. |
| * |
| * @param format a string in format as defined in RFC 7231, chapter 3.1.1.1 |
| * @return a new {@link ContentType} object |
| * @throws IllegalArgumentException if input string is not parseable |
| */ |
| public static ContentType create(final String format) throws IllegalArgumentException { |
| if (format == null) { |
| throw new IllegalArgumentException("Parameter format MUST NOT be NULL."); |
| } |
| List<String> typeSubtype = new ArrayList<String>(); |
| Map<String, String> parameters = new HashMap<String, String>(); |
| parse(format, typeSubtype, parameters); |
| return new ContentType(typeSubtype.get(0), typeSubtype.get(1), parameters); |
| } |
| |
| /** |
| * Parses the given input string (<code>format</code>) and returns created {@link ContentType} if input was valid or |
| * return <code>NULL</code> if input was not parseable. |
| * |
| * For the definition of the supported format see {@link #create(String)}. |
| * |
| * @param format a string in format as defined in RFC 7231, chapter 3.1.1.1 |
| * @return a new <code>ContentType</code> object |
| */ |
| public static ContentType parse(final String format) { |
| try { |
| return ContentType.create(format); |
| } catch (IllegalArgumentException e) { |
| return null; |
| } |
| } |
| |
| private static void parse(final String format, List<String> typeSubtype, Map<String, String> parameters) |
| throws IllegalArgumentException { |
| final String[] typesAndParameters = format.split(TypeUtil.PARAMETER_SEPARATOR, 2); |
| final String types = typesAndParameters[0]; |
| final String params = (typesAndParameters.length > 1 ? typesAndParameters[1] : null); |
| |
| if (types.contains(TypeUtil.TYPE_SUBTYPE_SEPARATOR)) { |
| final String[] tokens = types.split(TypeUtil.TYPE_SUBTYPE_SEPARATOR); |
| if (tokens.length == 2) { |
| if (tokens[0] == null || tokens[0].isEmpty()) { |
| throw new IllegalArgumentException("No type found in format '" + format + "'."); |
| } else if (tokens[1] == null || tokens[1].isEmpty()) { |
| throw new IllegalArgumentException("No subtype found in format '" + format + "'."); |
| } else { |
| typeSubtype.add(tokens[0]); |
| typeSubtype.add(tokens[1]); |
| } |
| } else { |
| throw new IllegalArgumentException( |
| "Too many '" + TypeUtil.TYPE_SUBTYPE_SEPARATOR + "' in format '" + format + "'."); |
| } |
| } else { |
| throw new IllegalArgumentException("No separator '" + TypeUtil.TYPE_SUBTYPE_SEPARATOR |
| + "' was found in format '" + format + "'."); |
| } |
| |
| TypeUtil.parseParameters(params, parameters); |
| } |
| |
| /** |
| * Uses the first MIME type from the accept header to determine the content type. |
| * |
| * @param accept The accept header content, e.g. text/html,application/xhtml+xml,application/xml, may be null |
| * @return The content type according to the accept header's first MIME type. Defaults to application/json if the |
| * accept header does not contain valid information. Never null. |
| */ |
| public static ContentType fromAcceptHeader(String accept) { |
| if (accept == null || accept.trim().isEmpty()) { |
| return JSON; |
| } |
| String acceptType = accept.split(",")[0]; |
| if (acceptType == null || acceptType.trim().isEmpty()) { |
| return JSON; |
| } |
| int semicolonIndex = acceptType.indexOf(';'); |
| String cleanedAcceptType; |
| if (semicolonIndex == -1) { |
| cleanedAcceptType = acceptType.trim(); |
| } else { |
| cleanedAcceptType = acceptType.trim().substring(0, semicolonIndex).trim(); |
| if (cleanedAcceptType.trim().isEmpty()) { |
| return JSON; |
| } |
| } |
| |
| try { |
| return create(cleanedAcceptType); |
| } catch (Exception exception) { |
| return accept.contains("xml") ? APPLICATION_ATOM_XML : JSON; |
| } |
| } |
| |
| /** Gets the type of this content type. */ |
| public String getType() { |
| return type; |
| } |
| |
| /** Gets the subtype of this content type. */ |
| public String getSubtype() { |
| return subtype; |
| } |
| |
| /** |
| * Gets the parameters of this content type. |
| * @return parameters of this {@link ContentType} as unmodifiable map |
| */ |
| public Map<String, String> getParameters() { |
| return Collections.unmodifiableMap(parameters); |
| } |
| |
| /** |
| * Returns the value of a given parameter. |
| * If the parameter does not exist the method returns null. |
| * @param name the name of the parameter to get (case-insensitive) |
| * @return the value of the parameter or <code>null</code> if the parameter is not present |
| */ |
| public String getParameter(final String name) { |
| return parameters.get(name.toLowerCase(Locale.ROOT)); |
| } |
| |
| @Override |
| public int hashCode() { |
| return 1; |
| } |
| |
| /** |
| * {@link ContentType}s are equal if <code>type</code>, <code>subtype</code>, and all <code>parameters</code> |
| * have the same value. |
| */ |
| @Override |
| public boolean equals(final Object obj) { |
| // basic checks |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| |
| final ContentType other = (ContentType) obj; |
| |
| // type/subtype checks |
| if (!isCompatible(other)) { |
| return false; |
| } |
| |
| // parameter checks |
| if (parameters.size() == other.parameters.size()) { |
| final Iterator<Entry<String, String>> entries = parameters.entrySet().iterator(); |
| final Iterator<Entry<String, String>> otherEntries = other.parameters.entrySet().iterator(); |
| while (entries.hasNext()) { |
| final Entry<String, String> e = entries.next(); |
| final Entry<String, String> oe = otherEntries.next(); |
| if (!areEqual(e.getKey(), oe.getKey()) |
| || !areEqual(e.getValue(), oe.getValue())) { |
| return false; |
| } |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * <p>{@link ContentType}s are <b>compatible</b> |
| * if <code>type</code> and <code>subtype</code> have the same value.</p> |
| * <p>The set <code>parameters</code> are <b>always</b> ignored |
| * (for compare with parameters see {@link #equals(Object)}).</p> |
| * @return <code>true</code> if both instances are compatible (see definition above), otherwise <code>false</code>. |
| */ |
| public boolean isCompatible(final ContentType other) { |
| return type.equalsIgnoreCase(other.type) && subtype.equalsIgnoreCase(other.subtype); |
| } |
| |
| /** |
| * Checks whether both strings are equal ignoring the case of the strings. |
| * @param first first string |
| * @param second second string |
| * @return <code>true</code> if both strings are equal (ignoring the case), otherwise <code>false</code> |
| */ |
| private static boolean areEqual(final String first, final String second) { |
| return first == null && second == null || (first != null && first.equalsIgnoreCase(second)); |
| } |
| |
| /** |
| * Gets {@link ContentType} as string as defined in |
| * <a href="http://www.ietf.org/rfc/rfc7231.txt">RFC 7231</a>, chapter 3.1.1.1: Media Type. |
| * @return string representation of {@link ContentType} object |
| */ |
| public String toContentTypeString() { |
| final StringBuilder sb = new StringBuilder(); |
| |
| sb.append(type).append(TypeUtil.TYPE_SUBTYPE_SEPARATOR).append(subtype); |
| |
| for (Entry<String, String> entry : parameters.entrySet()) { |
| sb.append(TypeUtil.PARAMETER_SEPARATOR).append(entry.getKey()) |
| .append(TypeUtil.PARAMETER_KEY_VALUE_SEPARATOR).append(entry.getValue()); |
| } |
| return sb.toString(); |
| } |
| |
| @Override |
| public String toString() { |
| return toContentTypeString(); |
| } |
| } |