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 }