All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wink.common.utils.ProviderUtils Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * 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.wink.common.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Providers;

import org.apache.wink.common.RuntimeContext;
import org.apache.wink.common.internal.MultivaluedMapImpl;
import org.apache.wink.common.internal.application.ApplicationExceptionAttribute;
import org.apache.wink.common.internal.http.AcceptCharset;
import org.apache.wink.common.internal.log.LogUtils;
import org.apache.wink.common.internal.utils.SoftConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ProviderUtils {
    private static final Logger                       logger            =
                                                                            LoggerFactory
                                                                                .getLogger(ProviderUtils.class);

    private static final String                       DEFAULT_CHARSET   = "UTF-8"; //$NON-NLS-1$

    private static SoftConcurrentMap validCharsets     =
                                                                            new SoftConcurrentMap();
    private static SoftConcurrentMap  preferredCharsets =
                                                                            new SoftConcurrentMap();
    
    public static enum PROVIDER_EXCEPTION_ORIGINATOR {
        isReadable,
        readFrom,
        getSize,
        isWriteable,
        writeTo,
        getContext
    }

    public static String getCharsetOrNull(MediaType m) {
        String name = (m == null) ? null : m.getParameters().get("charset"); //$NON-NLS-1$
        return (name == null) ? null : name;
    }

    public static String getCharset(MediaType m) {
        return getCharset(m, null);
    }

    /**
     * Returns the charset on the chosen media type or, if no charset parameter
     * exists on the chosen media type, the most acceptable charset based on the
     * request headers.
     * 
     * @param m the chosen media type
     * @param requestHeaders the request headers to inspect
     * @return the charset
     */
    public static String getCharset(MediaType m, HttpHeaders requestHeaders) {
        logger.trace("getCharset({}, {})", m, requestHeaders); //$NON-NLS-1$
        String name = (m == null) ? null : m.getParameters().get("charset"); //$NON-NLS-1$
        if (name != null) {
            logger.trace("getCharset() returning {} since parameter was set", name); //$NON-NLS-1$
            return name;
        }
        if (requestHeaders == null) {
            logger
                .trace("getCharset() returning {} since requestHeaders was null", DEFAULT_CHARSET); //$NON-NLS-1$
            return DEFAULT_CHARSET;
        }

        List acceptableCharsets =
            requestHeaders.getRequestHeader(HttpHeaders.ACCEPT_CHARSET);
        if (acceptableCharsets == null || acceptableCharsets.isEmpty()) {
            // HTTP spec says that no Accept-Charset header indicates that any
            // charset is acceptable so we'll stick with UTF-8 by default.
            logger.trace("getCharset() returning {} since no Accept-Charset header", //$NON-NLS-1$
                         DEFAULT_CHARSET);
            return DEFAULT_CHARSET;
        }

        StringBuilder acceptCharsetsTemp = new StringBuilder();
        acceptCharsetsTemp.append(acceptableCharsets.get(0));
        for (int c = 1; c < acceptableCharsets.size(); ++c) {
            acceptCharsetsTemp.append(","); //$NON-NLS-1$
            acceptCharsetsTemp.append(acceptableCharsets.get(c));
        }
        String acceptCharsets = acceptCharsetsTemp.toString();
        logger.trace("acceptCharsets combined value is {}", acceptCharsets); //$NON-NLS-1$
        String cached = preferredCharsets.get(acceptCharsets);
        if (cached != null) {
            return cached;
        }
        AcceptCharset charsets = AcceptCharset.valueOf(acceptCharsets);

        if (charsets.isAnyCharsetAllowed()) {
            preferredCharsets.put(acceptCharsets, DEFAULT_CHARSET);
            return DEFAULT_CHARSET;
        }

        List orderedCharsets = charsets.getAcceptableCharsets();
        logger.trace("orderedCharsets is {}", orderedCharsets); //$NON-NLS-1$
        if (!orderedCharsets.isEmpty()) {
            for (int c = 0; c < orderedCharsets.size(); ++c) {
                String charset = orderedCharsets.get(c);
                try {
                    Boolean b = validCharsets.get(charset);
                    if (b != null && b.booleanValue()) {
                        logger
                            .trace("getCharset() returning {} since highest Accept-Charset value", //$NON-NLS-1$
                                   charset);
                        preferredCharsets.put(acceptCharsets, charset);
                        return charset;
                    }
                    Charset.forName(charset);
                    validCharsets.put(charset, Boolean.TRUE);
                    logger.trace("getCharset() returning {} since highest Accept-Charset value", //$NON-NLS-1$
                                 charset);
                    preferredCharsets.put(acceptCharsets, charset);
                    return charset;
                } catch (IllegalCharsetNameException e) {
                    logger.trace("IllegalCharsetNameException for {}", charset, e); //$NON-NLS-1$
                    validCharsets.put(charset, Boolean.FALSE);
                } catch (UnsupportedCharsetException e) {
                    logger.trace("UnsupportedCharsetException for {}", charset, e); //$NON-NLS-1$
                    validCharsets.put(charset, Boolean.FALSE);
                } catch (IllegalArgumentException e) {
                    logger.trace("IllegalArgumentException for {}", charset, e); //$NON-NLS-1$
                    validCharsets.put(charset, Boolean.FALSE);
                }
            }
        }
        // At this point, it's either any charset is allowed (i.e. wildcard "*"
        // has a higher quality value than any other charset sent in the
        // Accept-Charset header), or we only have banned charsets. If there are
        // any banned charsets, then technically we should pick a non-banned
        // charset.
        logger.trace("getCharset() returning {} since no explicit charset required", //$NON-NLS-1$
                     DEFAULT_CHARSET);
        preferredCharsets.put(acceptCharsets, DEFAULT_CHARSET);
        return DEFAULT_CHARSET;
    }

    public static Reader createReader(InputStream stream, MediaType mediaType) {
        Charset charset = Charset.forName(getCharset(mediaType));
        return new InputStreamReader(stream, charset);
    }

    public static Writer createWriter(OutputStream stream, MediaType mediaType) {
        Charset charset = Charset.forName(getCharset(mediaType));
        return new OutputStreamWriter(stream, charset);
    }

    private static ByteArrayOutputStream readFromStream(InputStream stream) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
        copyStream(stream, os);
        return os;
    }

    public static byte[] readFromStreamAsBytes(InputStream stream) throws IOException {
        ByteArrayOutputStream os = readFromStream(stream);
        return os.toByteArray();
    }

    public static String readFromStreamAsString(InputStream stream, MediaType mt)
        throws IOException {
        ByteArrayOutputStream os = readFromStream(stream);
        return os.toString(ProviderUtils.getCharset(mt));
    }

    public static void writeToStream(String string, OutputStream os, MediaType mt)
        throws IOException {
        os.write(string.getBytes(getCharset(mt)));
    }

    public static void copyStream(InputStream src, OutputStream dst) throws IOException {
        byte[] bytes = new byte[1024];
        int read = 0;
        while ((read = src.read(bytes)) != -1) {
            dst.write(bytes, 0, read);
        }
    }

    public static String writeToString(Providers providers, Object object, MediaType mediaType)
        throws IOException {
        return writeToString(providers, object, object.getClass(), mediaType);
    }

    public static String writeToString(Providers providers,
                                       Object object,
                                       Class type,
                                       MediaType mediaType) throws IOException {
        return writeToString(providers, object, type, type, mediaType);
    }

    public static String writeToString(Providers providers,
                                       Object object,
                                       Class type,
                                       Type genericType,
                                       MediaType mediaType) throws IOException {
        return writeToString(providers,
                             object,
                             type,
                             type,
                             new MultivaluedMapImpl(),
                             mediaType);
    }

    @SuppressWarnings("unchecked")
    public static String writeToString(Providers providers,
                                       Object object,
                                       Class type,
                                       Type genericType,
                                       MultivaluedMap httpHeaders,
                                       MediaType mediaType) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        MessageBodyWriter writer =
            providers.getMessageBodyWriter(type, genericType, null, mediaType);
        if (writer == null) {
            return null;
        }
        writer.writeTo(object, type, genericType, new Annotation[0], mediaType, httpHeaders, os);
        String contentString = os.toString(getCharset(mediaType));
        return contentString;
    }

    public static  T readFromString(Providers providers,
                                       String input,
                                       Class type,
                                       MediaType mediaType) throws IOException {
        return readFromString(providers, input, type, type, mediaType);
    }

    public static  T readFromString(Providers providers,
                                       String input,
                                       Class type,
                                       Type genericType,
                                       MediaType mediaType) throws IOException {
        return readFromString(providers,
                              input,
                              type,
                              genericType,
                              new MultivaluedMapImpl(),
                              mediaType);
    }

    public static  T readFromString(Providers providers,
                                       String input,
                                       Class type,
                                       Type genericType,
                                       MultivaluedMap httpHeaders,
                                       MediaType mediaType) throws IOException {
        ByteArrayInputStream is = new ByteArrayInputStream(input.getBytes(getCharset(mediaType)));
        MessageBodyReader reader =
            providers.getMessageBodyReader(type, genericType, null, mediaType);
        if (reader == null) {
            return null;
        }
        return reader.readFrom(type, genericType, new Annotation[0], mediaType, httpHeaders, is);
    }
    
    public static void logUserProviderException(RuntimeException e,
            Object obj, // MessageBodyReader or MessageBodyWriter
            PROVIDER_EXCEPTION_ORIGINATOR originator,
            Object[] methodParams,
            RuntimeContext context) {
        
        try {
        
            if (context.getAttribute(ApplicationExceptionAttribute.class) != null) {
                // exception from application code has already been recorded to the RuntimeContext, don't record it again.
                return;
            }

            List dataToFormattedString = new ArrayList();
            dataToFormattedString.add(e.getClass().getName());
            dataToFormattedString.add(e.getMessage());
            dataToFormattedString.add(obj.getClass().getName());
            dataToFormattedString.add(originator);
            for (int i = 0; i < methodParams.length; i++) {
                dataToFormattedString.add(methodParams[i]);
            }
            // send exception through stackToString because it may have been intentionally thrown
            // from provider method; we don't want to scare the log readers, so it's recorded as DEBUG
            dataToFormattedString.add(LogUtils.stackToDebugString(e));

            String debugMsgFormat = "%s with message \"%s\" was encountered during invocation of method %s.%s( ";
            for (int i = 0; i < methodParams.length; i++) {
                debugMsgFormat += "%s";
                if (i < methodParams.length) {
                    debugMsgFormat += ", ";
                }
            }
            String newLine = System.getProperty("line.separator"); //$NON-NLS-1$
            debugMsgFormat += " )" + newLine + "%s";

            String debugMsg = String.format(debugMsgFormat, dataToFormattedString.toArray(new Object[]{}));
            context.setAttribute(ApplicationExceptionAttribute.class, new ApplicationExceptionAttribute(debugMsg));
        } catch (Throwable t) {
            // just to be extra super duper cautious.  It'll still be logged, just not via the format above.
            logger.trace("Could not format log output for exception originating in provider.", t);
        }
    }
}