View Javadoc

1   package com.atlassian.plugins.rest.module.util;
2   
3   import com.atlassian.plugins.rest.common.util.RestUrlBuilder;
4   import com.sun.jersey.core.spi.factory.AbstractRuntimeDelegate;
5   import com.sun.jersey.spi.service.ServiceConfigurationError;
6   import com.sun.jersey.spi.service.ServiceFinder;
7   import org.springframework.util.Assert;
8   
9   import javax.ws.rs.core.Response;
10  import javax.ws.rs.ext.RuntimeDelegate;
11  import java.net.URI;
12  
13  /**
14   * @since 2.2
15   */
16  public class RestUrlBuilderImpl implements RestUrlBuilder {
17      public RestUrlBuilderImpl() {
18          loadServiceFinderClass();
19          /**
20           * IMPLEMENTATION NOTE:
21           *
22           * <p>
23           * We're forcing jsr311 to initialize itself now that the context
24           * classloader is still set to the rest bundle (which has access to
25           * {@link com.sun.ws.rs.ext.RuntimeDelegateImpl}.
26           * </p>
27           * <p>
28           * If we wait for it to lazily initialize itself, we can (and in fact,
29           * we did) run into trouble when
30           * {@link #getUrlFor(java.net.URI, Class)} is called by a different
31           * plugin. In that scenario, by the time the caller then invokes a
32           * method on the returned cglib-generated proxy instance which
33           * triggers initialization of the jsr311 library, we get a
34           * {@link ClassNotFoundException} because
35           * {@link javax.ws.rs.ext.FactoryFinder#newInstance(String, ClassLoader)}
36           * relies on the context classloader to have access to
37           * {@link com.sun.ws.rs.ext.RuntimeDelegateImpl}, which it doesn't
38           * because that invocation is performed inside the calling plugin.
39           * </p>
40           * <p>
41           * It seems likely the above problem only applies to jsr311-1.0 (the
42           * one atlassian.jersey-library ships with) and is fixed in 1.1.1, so
43           * once we upgrade jersey, we should be able to get rid of this hack.
44           */
45          RuntimeDelegate.getInstance();
46      }
47  
48      /**
49       * REST-403: {@link ServiceFinder} needs to be loaded before {@link RuntimeDelegate#getInstance()}
50       *
51       * Reason: {@link RuntimeDelegate#getInstance()} enters synchronisation block over {@link RuntimeDelegate}.
52       * It loads {@link AbstractRuntimeDelegate} which initializes {@link ServiceFinder}.
53       * Deadlock will happen if any plugin starts to initialize {@link ServiceFinder}
54       * before {@link AbstractRuntimeDelegate} does and after it already enters synchronisation block.
55       * That is because {@link ServiceFinder} also enters synchronisation block over {@link RuntimeDelegate}.
56       */
57      private void loadServiceFinderClass() {
58          try {
59              // call any method over ServiceFinder class
60              com.sun.jersey.spi.service.ServiceFinder.find("NOSUCHSERVICE");
61          } catch (ServiceConfigurationError serviceConfigurationError) {
62          }
63      }
64  
65      public URI getURI(Response resource) {
66          if (resource instanceof GeneratedURIResponse) {
67              return ((GeneratedURIResponse) resource).getURI();
68          } else {
69              throw new IllegalArgumentException("Supplied response is not a generated one");
70          }
71      }
72  
73      public <T> T getUrlFor(URI baseUri, Class<T> resourceClass) {
74          Assert.notNull(resourceClass, "resourceClass cannot be null");
75          Assert.notNull(baseUri, "baseUri cannot be null");
76          return ProxyUtils.create(resourceClass, new ResourcePathUrlInvokable(resourceClass, baseUri));
77      }
78  }