PageRenderTime 31ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/plugin/src/main/java/com/atlassian/plugin/remotable/plugin/DefaultRemotablePluginAccessorFactory.java

https://bitbucket.org/rodogu/remotable-plugins
Java | 398 lines | 315 code | 43 blank | 40 comment | 9 complexity | cb0ac521c4a76bb328a7f19348478d66 MD5 | raw file
  1. package com.atlassian.plugin.remotable.plugin;
  2. import com.atlassian.applinks.api.ApplicationLink;
  3. import com.atlassian.applinks.api.event.ApplicationLinkAddedEvent;
  4. import com.atlassian.applinks.api.event.ApplicationLinkDeletedEvent;
  5. import com.atlassian.event.api.EventListener;
  6. import com.atlassian.event.api.EventPublisher;
  7. import com.atlassian.fugue.Iterables;
  8. import com.atlassian.oauth.ServiceProvider;
  9. import com.atlassian.plugin.ModuleDescriptor;
  10. import com.atlassian.plugin.Plugin;
  11. import com.atlassian.plugin.PluginAccessor;
  12. import com.atlassian.plugin.event.PluginEventListener;
  13. import com.atlassian.plugin.event.events.PluginDisabledEvent;
  14. import com.atlassian.plugin.event.events.PluginEnabledEvent;
  15. import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
  16. import com.atlassian.plugin.remotable.plugin.loader.universalbinary.UBDispatchFilter;
  17. import com.atlassian.plugin.remotable.plugin.module.applinks.RemotePluginContainerModuleDescriptor;
  18. import com.atlassian.plugin.remotable.plugin.util.function.MapFunctions;
  19. import com.atlassian.plugin.remotable.plugin.util.http.CachingHttpContentRetriever;
  20. import com.atlassian.plugin.remotable.plugin.util.http.ContentRetrievalException;
  21. import com.atlassian.plugin.remotable.spi.PermissionDeniedException;
  22. import com.atlassian.plugin.remotable.spi.RemotablePluginAccessor;
  23. import com.atlassian.plugin.remotable.spi.RemotablePluginAccessorFactory;
  24. import com.atlassian.plugin.remotable.spi.applinks.RemotePluginContainerApplicationType;
  25. import com.atlassian.plugin.remotable.spi.http.AuthorizationGenerator;
  26. import com.atlassian.sal.api.ApplicationProperties;
  27. import com.atlassian.uri.Uri;
  28. import com.atlassian.uri.UriBuilder;
  29. import com.atlassian.util.concurrent.CopyOnWriteMap;
  30. import com.atlassian.util.concurrent.Promise;
  31. import com.google.common.base.Function;
  32. import com.google.common.base.Predicate;
  33. import com.google.common.base.Supplier;
  34. import com.google.common.base.Suppliers;
  35. import com.google.common.collect.Maps;
  36. import net.oauth.OAuth;
  37. import org.springframework.beans.factory.DisposableBean;
  38. import org.springframework.beans.factory.annotation.Autowired;
  39. import org.springframework.stereotype.Component;
  40. import javax.annotation.Nullable;
  41. import javax.ws.rs.HttpMethod;
  42. import java.net.URI;
  43. import java.util.Arrays;
  44. import java.util.List;
  45. import java.util.Map;
  46. import static com.google.common.base.Preconditions.checkNotNull;
  47. import static com.google.common.collect.Maps.newHashMap;
  48. import static com.google.common.collect.Maps.transformValues;
  49. import static java.util.Collections.singletonList;
  50. @Component
  51. public final class DefaultRemotablePluginAccessorFactory implements RemotablePluginAccessorFactory, DisposableBean
  52. {
  53. private final ApplicationLinkAccessor applicationLinkAccessor;
  54. private final OAuthLinkManager oAuthLinkManager;
  55. private final CachingHttpContentRetriever httpContentRetriever;
  56. private final PluginAccessor pluginAccessor;
  57. private final ApplicationProperties applicationProperties;
  58. private final UBDispatchFilter ubDispatchFilter;
  59. private final EventPublisher eventPublisher;
  60. private final Map<String,RemotablePluginAccessor> accessors;
  61. @Autowired
  62. public DefaultRemotablePluginAccessorFactory(ApplicationLinkAccessor applicationLinkAccessor,
  63. OAuthLinkManager oAuthLinkManager,
  64. CachingHttpContentRetriever httpContentRetriever,
  65. PluginAccessor pluginAccessor,
  66. ApplicationProperties applicationProperties, UBDispatchFilter ubDispatchFilter,
  67. EventPublisher eventPublisher
  68. )
  69. {
  70. this.applicationLinkAccessor = applicationLinkAccessor;
  71. this.oAuthLinkManager = oAuthLinkManager;
  72. this.httpContentRetriever = httpContentRetriever;
  73. this.pluginAccessor = pluginAccessor;
  74. this.applicationProperties = applicationProperties;
  75. this.ubDispatchFilter = ubDispatchFilter;
  76. this.eventPublisher = eventPublisher;
  77. this.eventPublisher.register(this);
  78. this.accessors = CopyOnWriteMap.newHashMap();
  79. }
  80. /**
  81. * Clear accessor if a new application link is discovered
  82. * @param event
  83. */
  84. @EventListener
  85. public void onApplicationLinkCreated(ApplicationLinkAddedEvent event)
  86. {
  87. if (event.getApplicationType() instanceof RemotePluginContainerApplicationType)
  88. {
  89. // we clear the whole cache because the plugin key isn't set as a property yet and there
  90. // is no event for that action
  91. accessors.clear();
  92. }
  93. }
  94. /**
  95. * Clear accessor if a application link is deleted
  96. * @param event
  97. */
  98. @EventListener
  99. public void onApplicationLinkRemoved(ApplicationLinkDeletedEvent event)
  100. {
  101. if (event.getApplicationType() instanceof RemotePluginContainerApplicationType)
  102. {
  103. accessors.remove(getPluginKey(event.getApplicationLink()));
  104. }
  105. }
  106. /**
  107. * Clear accessor if a plugin is enabled
  108. * @param event
  109. */
  110. @EventListener
  111. public void onPluginEnabled(PluginEnabledEvent event)
  112. {
  113. accessors.remove(event.getPlugin().getKey());
  114. }
  115. /**
  116. * Clear accessor if a plugin is disabled
  117. * @param event
  118. */
  119. @EventListener
  120. public void onPluginDisabled(PluginDisabledEvent event)
  121. {
  122. accessors.remove(event.getPlugin().getKey());
  123. }
  124. @PluginEventListener
  125. public void onPluginModuleEnabled(PluginModuleEnabledEvent event)
  126. {
  127. accessors.remove(event.getModule().getPluginKey());
  128. }
  129. private String getPluginKey(ApplicationLink link)
  130. {
  131. return String.valueOf(link.getProperty(RemotePluginContainerModuleDescriptor.PLUGIN_KEY_PROPERTY));
  132. }
  133. /**
  134. * Supplies an accessor for remote plugin operations. Instances are only meant to be used for the
  135. * current operation and should not be cached across operations.
  136. *
  137. * @param pluginKey The plugin key
  138. * @return An accessor for either local or remote plugin operations
  139. */
  140. public RemotablePluginAccessor get(String pluginKey)
  141. {
  142. // this will potentially create multiple instances if called quickly, but we don't really
  143. // care as they shouldn't be cached
  144. final RemotablePluginAccessor accessor;
  145. if (accessors.containsKey(pluginKey))
  146. {
  147. accessor = accessors.get(pluginKey);
  148. }
  149. else
  150. {
  151. accessor = create(pluginKey, getDisplayUrl(pluginKey));
  152. accessors.put(pluginKey, accessor);
  153. }
  154. return accessor;
  155. }
  156. private Supplier<URI> getDisplayUrl(final String pluginKey)
  157. {
  158. final ApplicationLink link = applicationLinkAccessor.getApplicationLink(pluginKey);
  159. if (link != null)
  160. {
  161. return Suppliers.ofInstance(link.getDisplayUrl());
  162. }
  163. else if (isRemotable(pluginKey))
  164. {
  165. return Suppliers.compose(ToUriFunction.INSTANCE,
  166. new Supplier<String>()
  167. {
  168. @Override
  169. public String get()
  170. {
  171. return ubDispatchFilter.getLocalMountBaseUrl(pluginKey);
  172. }
  173. }
  174. );
  175. }
  176. else
  177. {
  178. return Suppliers.compose(ToUriFunction.INSTANCE,
  179. new Supplier<String>()
  180. {
  181. @Override
  182. public String get()
  183. {
  184. return applicationProperties.getBaseUrl();
  185. }
  186. }
  187. );
  188. }
  189. }
  190. private boolean isRemotable(String pluginKey)
  191. {
  192. return !Iterables.findFirst(pluginAccessor.getPlugin(pluginKey).getModuleDescriptors(), new Predicate<ModuleDescriptor<?>>()
  193. {
  194. @Override
  195. public boolean apply(@Nullable ModuleDescriptor<?> input)
  196. {
  197. return input instanceof RemotePluginContainerModuleDescriptor;
  198. }
  199. }).isEmpty();
  200. }
  201. /**
  202. * Supplies an accessor for remote plugin operations but always creates a new one. Instances are
  203. * still only meant to be used for the current operation and should not be cached across
  204. * operations.
  205. *
  206. * This method is useful for when the display url is known but the application link has not yet
  207. * been created
  208. * @param pluginKey The plugin key
  209. * @param displayUrl The display url
  210. * @return An accessor for a remote plugin
  211. */
  212. public RemotablePluginAccessor create(String pluginKey, Supplier<URI> displayUrl)
  213. {
  214. final Plugin plugin = pluginAccessor.getPlugin(pluginKey);
  215. checkNotNull(plugin, "Plugin not found: '%s'", pluginKey);
  216. // don't need to get the actual provider as it doesn't really matter
  217. return new OAuthSigningRemotablePluginAccessor(pluginKey, plugin.getName(), displayUrl, getDummyServiceProvider());
  218. }
  219. private ServiceProvider getDummyServiceProvider()
  220. {
  221. URI dummyUri = URI.create("http://localhost");
  222. return new ServiceProvider(dummyUri, dummyUri, dummyUri);
  223. }
  224. @Override
  225. public void destroy() throws Exception
  226. {
  227. eventPublisher.unregister(this);
  228. }
  229. private class OAuthSigningRemotablePluginAccessor implements RemotablePluginAccessor
  230. {
  231. private final String key;
  232. private final String name;
  233. private final Supplier<URI> displayUrl;
  234. private final ServiceProvider serviceProvider;
  235. private OAuthSigningRemotablePluginAccessor(String key,
  236. String name,
  237. Supplier<URI> displayUrl,
  238. ServiceProvider serviceProvider)
  239. {
  240. this.key = key;
  241. this.name = name;
  242. this.displayUrl = displayUrl;
  243. this.serviceProvider = serviceProvider;
  244. }
  245. @Override
  246. public String getKey()
  247. {
  248. return key;
  249. }
  250. @Override
  251. public URI getDisplayUrl()
  252. {
  253. return displayUrl.get();
  254. }
  255. @Override
  256. public String signGetUrl(URI targetPath, Map<String, String[]> params)
  257. {
  258. return signGetUrlForType(serviceProvider, getTargetUrl(targetPath), params);
  259. }
  260. @Override
  261. public String createGetUrl(URI targetPath, Map<String, String[]> params)
  262. {
  263. return executeCreateGetUrl(getTargetUrl(targetPath), params);
  264. }
  265. @Override
  266. public Promise<String> executeAsyncGet(String username, URI path, Map<String, String> params, Map<String, String> headers) throws ContentRetrievalException
  267. {
  268. return executeAsyncGetForType(new OAuthAuthorizationGenerator(serviceProvider),
  269. getTargetUrl(path), username, params, headers, key);
  270. }
  271. private URI getTargetUrl(URI targetPath)
  272. {
  273. return URI.create(displayUrl.get().toString() + targetPath.getPath());
  274. }
  275. @Override
  276. public AuthorizationGenerator getAuthorizationGenerator()
  277. {
  278. return new OAuthAuthorizationGenerator(serviceProvider);
  279. }
  280. @Override
  281. public String getName()
  282. {
  283. return name;
  284. }
  285. }
  286. private String executeCreateGetUrl(URI targetUrl, Map<String, String[]> params)
  287. {
  288. return new UriBuilder(Uri.fromJavaUri(targetUrl)).addQueryParameters(transformValues(params, MapFunctions.STRING_ARRAY_TO_STRING)).toString();
  289. }
  290. private Promise<String> executeAsyncGetForType(AuthorizationGenerator authorizationGenerator, URI targetUrl, String username,
  291. Map<String, String> params, Map<String, String> headers, String pluginKey)
  292. {
  293. return httpContentRetriever.getAsync(authorizationGenerator, username, targetUrl,
  294. Maps.transformValues(params, MapFunctions.OBJECT_TO_STRING),
  295. headers, pluginKey);
  296. }
  297. private String signGetUrlForType(ServiceProvider serviceProvider, URI targetUrl, Map<String, String[]> params) throws PermissionDeniedException
  298. {
  299. final UriBuilder uriBuilder = new UriBuilder(Uri.fromJavaUri(targetUrl));
  300. // adding all the parameters of the signed request
  301. for (Map.Entry<String, String> param : signRequest(serviceProvider, targetUrl, params, HttpMethod.GET))
  302. {
  303. final String value = param.getValue() == null ? "" : param.getValue();
  304. uriBuilder.addQueryParameter(param.getKey(), value);
  305. }
  306. return uriBuilder.toString();
  307. }
  308. private List<Map.Entry<String, String>> signRequest(ServiceProvider serviceProvider,
  309. URI url,
  310. Map<String, String[]> queryParams,
  311. String method
  312. )
  313. {
  314. String timestamp = System.currentTimeMillis() / 1000 + "";
  315. String nonce = System.nanoTime() + "";
  316. String signatureMethod = OAuth.RSA_SHA1;
  317. String oauthVersion = "1.0";
  318. Map<String,List<String>> params = newHashMap(transformValues(queryParams, new Function<String[], List<String>>()
  319. {
  320. @Override
  321. public List<String> apply(String[] from)
  322. {
  323. return Arrays.asList(from);
  324. }
  325. }));
  326. params.put(OAuth.OAUTH_SIGNATURE_METHOD, singletonList(signatureMethod));
  327. params.put(OAuth.OAUTH_NONCE, singletonList(nonce));
  328. params.put(OAuth.OAUTH_VERSION, singletonList(oauthVersion));
  329. params.put(OAuth.OAUTH_TIMESTAMP, singletonList(timestamp));
  330. return oAuthLinkManager.signAsParameters(serviceProvider, method, url, params);
  331. }
  332. private class OAuthAuthorizationGenerator implements AuthorizationGenerator
  333. {
  334. private final ServiceProvider serviceProvider;
  335. private OAuthAuthorizationGenerator(ServiceProvider serviceProvider)
  336. {
  337. this.serviceProvider = serviceProvider;
  338. }
  339. @Override
  340. public String generate(String method, URI url, Map<String, List<String>> parameters)
  341. {
  342. return oAuthLinkManager.generateAuthorizationHeader(method, serviceProvider, url,
  343. parameters);
  344. }
  345. }
  346. private static enum ToUriFunction implements Function<String, URI>
  347. {
  348. INSTANCE;
  349. @Override
  350. public URI apply(String uri)
  351. {
  352. return URI.create(uri);
  353. }
  354. }
  355. }