/**
* 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();
}
}