/** * An implementation of {@link MessageBodyWriter} that wraps the response body in a JavaScript * function call, as specified by JSON with Padding (JSONP). *
* This class only takes care of the padding -- it delegates all other work to suitable body workers for MediaType * {@code application/json}. *
* Honestly, most of that approach and its implementation is copied over from {@link JSONWithPaddingProvider}. * However, this class works for arbitrary object types (as long as they can be written by other body workers) * and does not rely on usage of the {@link JSONWithPadding} class. This can make Service implementations * that want to support both JSON and JSONP more straight forward. */ @Provider @Getter @Setter public class JSONPObjectProvider extends AbstractMessageReaderWriterProvider { /** */ private static final Logger log = Logger.getLogger( JSONPObjectProvider.class ); /** */ public static final String DEFAULT_CALLBACK_NAME = "callback"; /** */ private String callbackName = DEFAULT_CALLBACK_NAME; /** */ private Map> javascriptTypes; /** */ @Context private MessageBodyWorkers bodyWorkers; /** * */ public JSONPObjectProvider() { javascriptTypes = new HashMap>(); javascriptTypes.put( "application", new HashSet( Arrays.asList( "x-javascript", "ecmascript", "javascript" ) ) ); javascriptTypes.put( "text", new HashSet( Arrays.asList( "ecmascript", "jscript" ) ) ); } /** * */ public boolean isJavascript( MediaType m ) { Set subtypes = javascriptTypes.get( m.getType() ); if( subtypes == null ) return false; return subtypes.contains( m.getSubtype() ); } /** * */ @Override public boolean isReadable( Class type, Type genericType, Annotation [] annotations, MediaType mediaType ) { return false; } /** * */ @Override public Object readFrom( Class type, Type genericType, Annotation [] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream ) throws IOException, WebApplicationException { throw new UnsupportedOperationException( "Not supported by design." ); } /** * */ @Override public boolean isWriteable( Class type, Type genericType, Annotation [] annotations, MediaType mediaType ) { return isJavascript( mediaType ) && ( type.getAnnotation( XmlRootElement.class ) != null ); } /** * */ @SuppressWarnings( { "rawtypes", "unchecked" } ) @Override public void writeTo( Object object, Class type, Type genericType, Annotation [] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream ) throws IOException, WebApplicationException { final boolean isJavaScript = isJavascript( mediaType ); final MediaType workerMediaType = isJavaScript ? MediaType.APPLICATION_JSON_TYPE : mediaType; final MessageBodyWriter bw = bodyWorkers.getMessageBodyWriter( type, genericType, annotations, workerMediaType ); if( bw == null ) { log.error( ImplMessages.ERROR_NONGE_JSONP_MSG_BODY_WRITER_NOT_FOUND( object, workerMediaType ) ); throw new WebApplicationException( 500 ); } if( isJavaScript ) { writeCallbackName( entityStream ); entityStream.write( '(' ); } bw.writeTo( object, type, genericType, annotations, workerMediaType, httpHeaders, entityStream ); if( isJavaScript ) { entityStream.write( ')' ); } } /** * */ protected void writeCallbackName( OutputStream entityStream ) throws IOException { // XXX We should not rely on system locale or on UTF8 when determining the Charset to use for writing the callback name // TODO Determine Charset through HTTP content negotiation? entityStream.write( getActualCallbackName().getBytes( UTF8 ) ); } /** * */ protected String getActualCallbackName() { return getCallbackName(); } }