| /******************************************************************************* |
| * 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.odata2.client.core.ep.deserializer; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.UnsupportedEncodingException; |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import org.apache.olingo.odata2.api.ep.EntityProviderException; |
| import org.apache.olingo.odata2.api.processor.ODataErrorContext; |
| import org.apache.olingo.odata2.core.commons.ContentType; |
| import org.apache.olingo.odata2.core.ep.util.FormatJson; |
| |
| import com.google.gson.stream.JsonReader; |
| import com.google.gson.stream.JsonToken; |
| |
| /** |
| * Consuming (read / deserialization) for OData error document in JSON format. |
| */ |
| public class JsonErrorDocumentDeserializer { |
| /** Default used charset for reader */ |
| private static final String DEFAULT_CHARSET = "UTF-8"; |
| private static final String FOUND = "' found."; |
| /** |
| * Map containing language code (language - country) to Locale mapping |
| * based on Locale.getAvailableLocales() |
| * */ |
| private static final Map<String, Locale> AVAILABLE_LOCALES = new HashMap<String, Locale>(); |
| static { |
| Locale[] locales = Locale.getAvailableLocales(); |
| for (Locale l : locales) { |
| AVAILABLE_LOCALES.put(l.getLanguage() + "-" + l.getCountry(), l); |
| } |
| } |
| |
| /** |
| * Deserialize / read OData error document in ODataErrorContext. |
| * |
| * @param errorDocument OData error document in JSON format |
| * @return created ODataErrorContext based on input stream content. |
| * @throws EntityProviderException if an exception during read / deserialization occurs. |
| */ |
| public ODataErrorContext readError(final InputStream errorDocument) throws EntityProviderException { |
| JsonReader reader = createJsonReader(errorDocument); |
| try { |
| return parseJson(reader); |
| } catch (IOException e) { |
| throw new EntityProviderException( |
| EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getMessage()), e); |
| } |
| } |
| |
| /** |
| * |
| * @param reader |
| * @return ODataErrorContext |
| * @throws IOException |
| * @throws EntityProviderException |
| */ |
| private ODataErrorContext parseJson(final JsonReader reader) throws IOException, EntityProviderException { |
| ODataErrorContext errorContext; |
| |
| if (reader.hasNext()) { |
| reader.beginObject(); |
| String currentName = reader.nextName(); |
| if (FormatJson.ERROR.equals(currentName)) { |
| errorContext = parseError(reader); |
| } else { |
| throw new EntityProviderException(EntityProviderException.INVALID_STATE.addContent( |
| "Invalid object with name '" + currentName + FOUND)); |
| } |
| } else { |
| throw new EntityProviderException(EntityProviderException.INVALID_STATE.addContent( |
| "No content to parse found.")); |
| } |
| |
| errorContext.setContentType(ContentType.APPLICATION_JSON.toContentTypeString()); |
| return errorContext; |
| } |
| |
| /** |
| * |
| * @param reader |
| * @return ODataErrorContext |
| * @throws IOException |
| * @throws EntityProviderException |
| */ |
| private ODataErrorContext parseError(final JsonReader reader) throws IOException, EntityProviderException { |
| ODataErrorContext errorContext = new ODataErrorContext(); |
| String currentName; |
| reader.beginObject(); |
| boolean messageFound = false; |
| boolean codeFound = false; |
| |
| while (reader.hasNext()) { |
| currentName = reader.nextName(); |
| if (FormatJson.CODE.equals(currentName)) { |
| codeFound = true; |
| errorContext.setErrorCode(getValue(reader)); |
| } else if (FormatJson.MESSAGE.equals(currentName)) { |
| messageFound = true; |
| parseMessage(reader, errorContext); |
| } else if (FormatJson.INNER_ERROR.equals(currentName)) { |
| parseInnerError(reader, errorContext); |
| } else { |
| throw new EntityProviderException(EntityProviderException.INVALID_STATE.addContent( |
| "Invalid name '" + currentName + FOUND)); |
| } |
| } |
| |
| if (!codeFound) { |
| throw new EntityProviderException( |
| EntityProviderException.MISSING_PROPERTY.addContent("Mandatory 'code' property not found.'")); |
| } |
| if (!messageFound) { |
| throw new EntityProviderException( |
| EntityProviderException.MISSING_PROPERTY.addContent("Mandatory 'message' property not found.'")); |
| } |
| |
| reader.endObject(); |
| return errorContext; |
| } |
| |
| /** |
| * |
| * @param reader |
| * @param errorContext |
| * @throws IOException |
| */ |
| private void parseInnerError(final JsonReader reader, final ODataErrorContext errorContext) throws IOException { |
| JsonToken token = reader.peek(); |
| if (token == JsonToken.STRING) { |
| // implementation for parse content as provided by JsonErrorDocumentProducer |
| String innerError = reader.nextString(); |
| errorContext.setInnerError(innerError); |
| } else if (token == JsonToken.BEGIN_OBJECT) { |
| // implementation for OData v2 Section 2.2.8.1.2 JSON Error Response |
| // (RFC4627 Section 2.2 -> https://www.ietf.org/rfc/rfc4627.txt)) |
| // currently partial provided |
| errorContext.setInnerError(readJson(reader)); |
| } |
| } |
| |
| /** |
| * |
| * @param reader |
| * @return String |
| * @throws IOException |
| */ |
| private String readJson(final JsonReader reader) throws IOException { |
| StringBuilder sb = new StringBuilder(); |
| |
| while (reader.hasNext()) { |
| JsonToken token = reader.peek(); |
| if (token == JsonToken.NAME) { |
| if (sb.length() > 0) { |
| sb.append(","); |
| } |
| String name = reader.nextName(); |
| sb.append("\"").append(name).append("\"").append(":"); |
| } else if (token == JsonToken.BEGIN_OBJECT) { |
| reader.beginObject(); |
| sb.append("{") |
| .append(readJson(reader)) |
| .append("}"); |
| reader.endObject(); |
| } else if (token == JsonToken.BEGIN_ARRAY) { |
| reader.beginArray(); |
| sb.append("[") |
| .append(readJson(reader)) |
| .append("]"); |
| reader.endArray(); |
| } else { |
| sb.append("\"") |
| .append(reader.nextString()) |
| .append("\""); |
| } |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * |
| * @param reader |
| * @param errorContext |
| * @throws IOException |
| * @throws EntityProviderException |
| */ |
| private void parseMessage(final JsonReader reader, final ODataErrorContext errorContext) |
| throws IOException, EntityProviderException { |
| |
| reader.beginObject(); |
| boolean valueFound = false; |
| boolean langFound = false; |
| String currentName; |
| |
| while (reader.hasNext()) { |
| currentName = reader.nextName(); |
| if (FormatJson.LANG.equals(currentName)) { |
| langFound = true; |
| String langValue = getValue(reader); |
| if (langValue != null) { |
| errorContext.setLocale(getLocale(langValue)); |
| } |
| } else if (FormatJson.VALUE.equals(currentName)) { |
| valueFound = true; |
| errorContext.setMessage(getValue(reader)); |
| } else { |
| throw new EntityProviderException(EntityProviderException.INVALID_STATE.addContent("Invalid name '" + |
| currentName + FOUND)); |
| } |
| } |
| |
| if (!langFound) { |
| throw new EntityProviderException( |
| EntityProviderException.MISSING_PROPERTY.addContent("Mandatory 'lang' property not found.'")); |
| } |
| if (!valueFound) { |
| throw new EntityProviderException( |
| EntityProviderException.MISSING_PROPERTY.addContent("Mandatory 'value' property not found.'")); |
| } |
| reader.endObject(); |
| } |
| |
| private Locale getLocale(final String langValue) { |
| return AVAILABLE_LOCALES.get(langValue); |
| } |
| |
| /** |
| * Read the string value from the JsonReader or 'null' if no value is available. |
| * |
| * @param reader to read from |
| * @return the string value or 'null' |
| * @throws IOException if an exception occurs |
| */ |
| private String getValue(final JsonReader reader) throws IOException { |
| JsonToken token = reader.peek(); |
| if (JsonToken.NULL == token) { |
| reader.skipValue(); |
| return null; |
| } |
| return reader.nextString(); |
| } |
| |
| private JsonReader createJsonReader(final InputStream in) throws EntityProviderException { |
| if (in == null) { |
| throw new EntityProviderException(EntityProviderException.INVALID_STATE |
| .addContent(("Got not supported NULL object as content to de-serialize."))); |
| } |
| try { |
| return new JsonReader(new InputStreamReader(in, DEFAULT_CHARSET)); |
| } catch (final UnsupportedEncodingException e) { |
| throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass() |
| .getSimpleName()), e); |
| } |
| } |
| } |