1   package com.atlassian.plugins.rest.module.jersey;
2   
3   import com.atlassian.plugin.AutowireCapablePlugin;
4   import com.atlassian.plugins.rest.module.OsgiComponentProviderFactory;
5   import com.atlassian.plugins.rest.module.ResourceConfigManager;
6   import com.sun.jersey.api.core.ResourceConfig;
7   import com.sun.jersey.core.header.InBoundHeaders;
8   import com.sun.jersey.core.header.OutBoundHeaders;
9   import com.sun.jersey.core.spi.component.ProviderFactory;
10  import com.sun.jersey.core.spi.component.ProviderServices;
11  import com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory;
12  import com.sun.jersey.core.spi.component.ioc.IoCProviderFactory;
13  import com.sun.jersey.core.spi.factory.ContextResolverFactory;
14  import com.sun.jersey.core.spi.factory.InjectableProviderFactory;
15  import com.sun.jersey.core.spi.factory.MessageBodyFactory;
16  import com.sun.jersey.spi.MessageBodyWorkers;
17  import com.sun.jersey.spi.inject.SingletonTypeInjectableProvider;
18  import org.osgi.framework.Bundle;
19  
20  import javax.ws.rs.core.Context;
21  import javax.ws.rs.core.GenericEntity;
22  import javax.ws.rs.core.MediaType;
23  import javax.ws.rs.core.MultivaluedMap;
24  import javax.ws.rs.ext.ContextResolver;
25  import javax.ws.rs.ext.ExceptionMapper;
26  import javax.ws.rs.ext.MessageBodyReader;
27  import javax.ws.rs.ext.MessageBodyWriter;
28  import javax.ws.rs.ext.Providers;
29  import java.io.ByteArrayOutputStream;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.UnsupportedEncodingException;
33  import java.lang.annotation.Annotation;
34  import java.lang.reflect.Type;
35  import java.nio.charset.Charset;
36  import java.util.Collections;
37  import java.util.List;
38  import java.util.Map;
39  
40  public class JerseyEntityHandler
41  {
42      private final MessageBodyFactory messageBodyFactory;
43      private final ResourceConfigManager resourceConfigManager;
44  
45      public JerseyEntityHandler(AutowireCapablePlugin plugin, Bundle bundle)
46      {
47          resourceConfigManager = new ResourceConfigManager(plugin, bundle);
48          ResourceConfig config = resourceConfigManager.createResourceConfig(Collections.<String, Object>emptyMap(),
49                  new String[0], Collections.<String>emptySet());
50  
51          IoCComponentProviderFactory provider = new OsgiComponentProviderFactory(config, plugin);
52          InjectableProviderFactory injectableFactory = new InjectableProviderFactory();
53  
54          ProviderFactory componentProviderFactory = new IoCProviderFactory(injectableFactory, provider);
55  
56          ProviderServices providerServices = new ProviderServices(
57                  injectableFactory,
58                  componentProviderFactory,
59                  config.getClasses(),
60                  config.getSingletons());
61  
62          injectableFactory.configure(providerServices);
63  
64          // Obtain all context resolvers
65          final ContextResolverFactory crf = new ContextResolverFactory(providerServices,
66                  injectableFactory);
67  
68          // Obtain all message body readers/writers
69          messageBodyFactory = new MessageBodyFactory(providerServices);
70          // Allow injection of message body context
71          injectableFactory.add(new ContextInjectableProvider<MessageBodyWorkers>(
72                  MessageBodyWorkers.class, messageBodyFactory));
73  
74          // Injection of Providers
75          Providers providers = new Providers()
76          {
77              public <T> MessageBodyReader<T> getMessageBodyReader(Class<T> c, Type t,
78                                                                   Annotation[] as, MediaType m)
79              {
80                  return messageBodyFactory.getMessageBodyReader(c, t, as, m);
81              }
82  
83              public <T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> c, Type t,
84                                                                   Annotation[] as, MediaType m)
85              {
86                  return messageBodyFactory.getMessageBodyWriter(c, t, as, m);
87              }
88  
89              public <T extends Throwable> ExceptionMapper<T> getExceptionMapper(Class<T> c)
90              {
91                  throw new IllegalArgumentException("This method is not supported on the client side");
92              }
93  
94              public <T> ContextResolver<T> getContextResolver(Class<T> ct, MediaType m)
95              {
96                  return crf.resolve(ct, m);
97              }
98          };
99          injectableFactory.add(
100                 new ContextInjectableProvider<Providers>(
101                         Providers.class, providers));
102 
103         // Initiate message body readers/writers
104         messageBodyFactory.init();
105 
106         // Inject on all components
107         componentProviderFactory.injectOnAllComponents();
108         componentProviderFactory.injectOnProviderInstances(config.getSingletons());
109     }
110 
111     public String marshall(Object entity, MediaType mediaType, Charset charset) throws IOException
112     {
113         Type entityType;
114         if (entity instanceof GenericEntity)
115         {
116             final GenericEntity ge = (GenericEntity) entity;
117             entityType = ge.getType();
118             entity = ge.getEntity();
119         }
120         else
121         {
122             entityType = entity.getClass();
123         }
124         final Class entityClass = entity.getClass();
125 
126         MessageBodyWriter writer = messageBodyFactory.getMessageBodyWriter(entityClass, entityType, new Annotation[0], mediaType);
127 
128         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
129         writer.writeTo(entity, entityClass, entityType, new Annotation[0], mediaType, new OutBoundHeaders(), outputStream);
130 
131         return getRequestBody(outputStream, charset);
132     }
133 
134     public <T> T unmarshall(Class<T> entityClass, MediaType mediaType, InputStream entityStream,
135                             Map<String, List<String>> responseHeaders) throws IOException
136     {
137         MessageBodyReader<T> reader = messageBodyFactory.getMessageBodyReader(entityClass, entityClass, new Annotation[0],
138                 mediaType);
139         MultivaluedMap<String, String> headers = new InBoundHeaders();
140         headers.putAll(responseHeaders);
141 
142         return reader.readFrom(entityClass, entityClass, new Annotation[0], mediaType, headers, entityStream);
143     }
144 
145     /**
146      * Get the marshalled request body from the {@link MessageBodyWriter}'s output. Although {@link MessageBodyWriter#writeTo}
147      * indicates that headers will be flushed prior to the marshalled entity, this seems to be inconsistently implemented,
148      * so if no headers are found this method will just return the full content of the supplied outputStream.
149      *
150      * @param outputStream written to by a {@link MessageBodyWriter}
151      * @param charset      the encoding for the produced String
152      * @return the marshalled request body
153      */
154     private String getRequestBody(final ByteArrayOutputStream outputStream, Charset charset)
155     {
156         byte[] bytes = outputStream.toByteArray();
157         byte last = 0;
158         int bodyStartIndex = -1;
159         for (int i = 0; i < bytes.length; i++)
160         {
161             // look for two new lines to indicate end of headers
162             if (last == 10 && bytes[i] == 10)
163             {
164                 bodyStartIndex = i + 1;
165                 break;
166             }
167             last = bytes[i];
168         }
169         if (bodyStartIndex == -1)
170         {
171             //throw new IllegalStateException("Didn't find end of HTTP header!");
172             bodyStartIndex = 0; //no headers - guess this MessageBodyWriter impl didn't flush the headers (!)
173         }
174 
175         try
176         {
177             return new String(bytes, bodyStartIndex, bytes.length - bodyStartIndex, charset.name());
178         }
179         catch (UnsupportedEncodingException e)
180         {
181             throw new RuntimeException("Should never happen.  We only use the String(byte[], int, int, String) method " +
182                                        "to preserve Java 5 compatibility.");
183         }
184     }
185 
186     public void destroy()
187     {
188         resourceConfigManager.destroy();
189     }
190 
191     private static class ContextInjectableProvider<T> extends
192             SingletonTypeInjectableProvider<Context, T>
193     {
194 
195         ContextInjectableProvider(Type type, T instance)
196         {
197             super(type, instance);
198         }
199     }
200 
201 
202 }