View Javadoc
1   package com.atlassian.plugin.refimpl.webresource;
2   
3   import com.atlassian.plugin.Plugin;
4   import com.atlassian.plugin.PluginAccessor;
5   import com.atlassian.plugin.event.PluginEventListener;
6   import com.atlassian.plugin.event.PluginEventManager;
7   import com.atlassian.plugin.event.events.PluginDisabledEvent;
8   import com.atlassian.plugin.event.events.PluginEnabledEvent;
9   import com.atlassian.plugin.event.events.PluginModuleDisabledEvent;
10  import com.atlassian.plugin.event.events.PluginModuleEnabledEvent;
11  import com.atlassian.plugin.refimpl.ContainerManager;
12  import com.atlassian.plugin.refimpl.ParameterUtils;
13  import com.atlassian.plugin.webresource.UrlMode;
14  import com.atlassian.plugin.webresource.WebResourceIntegration;
15  import com.atlassian.plugin.webresource.cdn.CDNStrategy;
16  import com.atlassian.sal.api.component.ComponentLocator;
17  import com.atlassian.sal.api.features.DarkFeatureManager;
18  import com.atlassian.sal.api.message.I18nResolver;
19  import com.atlassian.sal.api.message.LocaleResolver;
20  import com.atlassian.util.concurrent.ResettableLazyReference;
21  import com.google.common.collect.ImmutableMap;
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  import javax.servlet.ServletContext;
26  import java.io.File;
27  import java.util.HashMap;
28  import java.util.Locale;
29  import java.util.Map;
30  import java.util.Optional;
31  import java.util.Set;
32  import java.util.TreeSet;
33  
34  public class SimpleWebResourceIntegration implements WebResourceIntegration {
35      private static final Logger log = LoggerFactory.getLogger(SimpleWebResourceIntegration.class);
36  
37      /**
38       * Calculates hash of all the plugins with i18n resources.
39       */
40      public static class I18nHasher {
41          private final ResettableLazyReference<String> hashLazyReference;
42  
43          public I18nHasher(PluginAccessor pluginAccessor, PluginEventManager eventManager) {
44              hashLazyReference = new ResettableLazyReference<String>() {
45                  @Override
46                  protected String create() throws Exception {
47                      // Tree Set produces consistent ordering and hashing.
48                      Set<String> versions = new TreeSet<>();
49                      for (Plugin plugin : pluginAccessor.getEnabledPlugins()) {
50                          // It would be better to count only plugins that actually contain i18n resources, but
51                          // for simplicity counting all the plugins.
52                          versions.add(plugin.getKey() + " v. " + plugin.getPluginInformation().getVersion());
53                      }
54                      return "" + versions.hashCode();
55                  }
56              };
57              eventManager.register(this);
58          }
59  
60          public String get() {
61              return hashLazyReference.get();
62          }
63  
64          @PluginEventListener
65          public void onPluginDisabled(final PluginDisabledEvent event) {
66              hashLazyReference.reset();
67          }
68  
69          @PluginEventListener
70          public void onPluginEnabled(final PluginEnabledEvent event) {
71              hashLazyReference.reset();
72          }
73  
74          @PluginEventListener
75          public void onPluginModuleEnabled(final PluginModuleEnabledEvent event) {
76              hashLazyReference.reset();
77          }
78  
79          @PluginEventListener
80          public void onPluginModuleDisabled(final PluginModuleDisabledEvent event) {
81              hashLazyReference.reset();
82          }
83      }
84  
85  
86      private final String systemBuildNumber;
87      private final LocaleResolver localeResolver;
88      private final I18nResolver i18nResolver;
89      private final String refappVersion;
90      private final PluginEventManager eventManager;
91      private final ThreadLocal<Map<String, Object>> requestCache = new ThreadLocal<Map<String, Object>>() {
92          @Override
93          protected Map<String, Object> initialValue() {
94              // if it's null, we just create a new one.. tho this means results from one request will affect the next request
95              // on this same thread because we don't ever clean it up from a filter or anything - definitely not for use in
96              // production!
97              return new HashMap<>();
98          }
99      };
100     private final I18nHasher i18nHasher;
101 
102     public SimpleWebResourceIntegration(final ServletContext servletContext, final LocaleResolver localeResolver,
103                                         final I18nResolver i18nResolver, final PluginEventManager eventManager) {
104         this(servletContext, localeResolver, i18nResolver, eventManager, "(unknown)");
105     }
106 
107     public SimpleWebResourceIntegration(final ServletContext servletContext, final LocaleResolver localeResolver,
108                                         final I18nResolver i18nResolver, final PluginEventManager eventManager, final String refappVersion) {
109         // we fake the build number by using the startup time which will force anything cached by clients to be
110         // reloaded after a restart
111         systemBuildNumber = String.valueOf(System.currentTimeMillis());
112         this.localeResolver = localeResolver;
113         this.i18nResolver = i18nResolver;
114         this.eventManager = eventManager;
115         this.refappVersion = refappVersion;
116         this.i18nHasher = new I18nHasher(ContainerManager.getInstance().getPluginAccessor(), eventManager);
117     }
118 
119     @Override
120     public Locale getLocale() {
121         return localeResolver.getLocale();
122     }
123 
124     @Override
125     public String getI18nText(final Locale locale, final String s) {
126         return i18nResolver.getText(locale, s);
127     }
128 
129     @Override
130     public String getI18nRawText(final Locale locale, final String s) {
131         return i18nResolver.getRawText(locale, s);
132     }
133 
134     @Override
135     public CDNStrategy getCDNStrategy() {
136         return null;  // Null implies that a CDN is not used
137     }
138 
139     public String getBaseUrl() {
140         return getBaseUrl(UrlMode.AUTO);
141     }
142 
143     public String getBaseUrl(UrlMode urlMode) {
144         // The context path is read from a sys property and might be "/" context path-less instances.
145         // for web resources this causes relative paths to be created as //blah so it needs to be replaced by ""
146         String baseUrl = ParameterUtils.getBaseUrl(urlMode);
147         return "/".equals(baseUrl) ? "" : baseUrl;
148     }
149 
150     public PluginAccessor getPluginAccessor() {
151         return ContainerManager.getInstance().getPluginAccessor();
152     }
153 
154     public Map<String, Object> getRequestCache() {
155         return requestCache.get();
156     }
157 
158     public String getSystemBuildNumber() {
159         return systemBuildNumber;
160     }
161 
162     public String getSystemCounter() {
163         return "1";
164     }
165 
166     public String getSuperBatchVersion() {
167         return "1";
168     }
169 
170     public String getStaticResourceLocale() {
171         log.warn("`getStaticResourceLocale` is deprecated and not used anymore!");
172         return getLocale().toString();
173     }
174 
175     @Override
176     public String getI18nStateHash() {
177         return i18nHasher.get();
178     }
179 
180     public File getTemporaryDirectory() {
181         final String tempDir = System.getProperty("java.io.tmpdir");
182         return new File(tempDir);
183     }
184 
185     public Map<String, String> getResourceSubstitutionVariables() {
186         String pdl = System.getProperty("pdl.dir", "");
187 
188         if (pdl.length() > 0 && !pdl.endsWith("/")) { // "pdl.dir" is meant to look like a dir
189             pdl += "/";
190         }
191         return ImmutableMap.of("pdl.dir", pdl);
192     }
193 
194     @Override
195     public boolean useAsyncAttributeForScripts() {
196         final Optional<DarkFeatureManager> darkFeatureManagerMaybe = Optional.of(ComponentLocator.getComponent(DarkFeatureManager.class));
197 
198         return darkFeatureManagerMaybe
199                 .map(dfManager -> dfManager.isFeatureEnabledForAllUsers("ENABLE_ASYNC_SCRIPTS"))
200                 .orElse(false);
201     }
202 
203     @Override
204     public PluginEventManager getPluginEventManager() {
205         return this.eventManager;
206     }
207 
208     @Override
209     public Set<String> allowedCondition1Keys() {
210         return null;
211     }
212 
213     @Override
214     public Set<String> allowedTransform1Keys() {
215         return null;
216     }
217 
218     @Override
219     public boolean forbidCondition1AndTransformer1() {
220         return false;
221     }
222 
223     @Override
224     public String getHostApplicationVersion() {
225         return refappVersion;
226     }
227 
228     @Override
229     public Iterable<Locale> getSupportedLocales() {
230         return localeResolver.getSupportedLocales();
231     }
232 }