View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.config;
21  
22  import org.apache.myfaces.tobago.application.ProjectStage;
23  import org.apache.myfaces.tobago.config.TobagoConfig;
24  import org.apache.myfaces.tobago.context.Theme;
25  import org.apache.myfaces.tobago.context.ThemeImpl;
26  import org.apache.myfaces.tobago.internal.util.JndiUtils;
27  import org.apache.myfaces.tobago.sanitizer.Sanitizer;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  import javax.faces.application.Application;
32  import javax.faces.context.FacesContext;
33  import javax.naming.Context;
34  import javax.naming.InitialContext;
35  import javax.naming.NamingException;
36  import javax.servlet.ServletContext;
37  import java.util.ArrayList;
38  import java.util.Collections;
39  import java.util.HashMap;
40  import java.util.HashSet;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Set;
44  
45  /**
46   * <p>
47   * Implementation of the Tobago configuration.
48   * </p>
49   * <p>
50   * All setters must are protected, so EL can't modify this config.
51   * </p>
52   */
53  public class TobagoConfigImpl extends TobagoConfig {
54  
55    private static final Logger LOG = LoggerFactory.getLogger(TobagoConfigImpl.class);
56  
57    private List<Theme> supportedThemes;
58    private List<String> supportedThemeNames;
59    private Theme defaultTheme;
60    private String defaultThemeName;
61    private List<String> resourceDirs;
62    private Map<String, ThemeImpl> availableThemes;
63    private RenderersConfig renderersConfig;
64    private ProjectStage projectStage;
65    private boolean createSessionSecret;
66    private boolean checkSessionSecret;
67    private boolean preventFrameAttacks;
68    private ContentSecurityPolicy contentSecurityPolicy;
69    private boolean setNosniffHeader;
70    private Map<String, String> defaultValidatorInfo;
71    private Sanitizer sanitizer;
72    private boolean autoAccessKeyFromLabel;
73    private Map<String, String> mimeTypes;
74  
75    private boolean unmodifiable = false;
76  
77    protected TobagoConfigImpl() {
78      supportedThemeNames = new ArrayList<String>();
79      supportedThemes = new ArrayList<Theme>();
80      availableThemes = new HashMap<String, ThemeImpl>();
81      resourceDirs = new ArrayList<String>();
82      createSessionSecret = true;
83      checkSessionSecret = true;
84      preventFrameAttacks = true;
85      setNosniffHeader = true;
86      contentSecurityPolicy = new ContentSecurityPolicy(ContentSecurityPolicy.Mode.OFF.getValue());
87      autoAccessKeyFromLabel = true;
88      mimeTypes = new HashMap<String, String>();
89    }
90  
91    /**
92     * Lock the configuration, so it cannot be modified any more.
93     */
94    protected void lock() {
95      unmodifiable = true;
96      supportedThemes = Collections.unmodifiableList(supportedThemes);
97      for (Theme theme : supportedThemes) {
98        ((ThemeImpl) theme).lock();
99      }
100     supportedThemeNames = Collections.unmodifiableList(supportedThemeNames);
101     resourceDirs = Collections.unmodifiableList(resourceDirs);
102     availableThemes = Collections.unmodifiableMap(availableThemes);
103 
104     if (renderersConfig instanceof RenderersConfigImpl) {
105       ((RenderersConfigImpl) renderersConfig).lock();
106     }
107     contentSecurityPolicy.lock();
108 
109     mimeTypes = Collections.unmodifiableMap(mimeTypes);
110   }
111 
112   private void checkLocked() throws IllegalStateException {
113     if (unmodifiable) {
114       throw new RuntimeException("The configuration must not be changed after initialization!");
115     }
116   }
117 
118   protected void addSupportedThemeName(final String name) {
119     checkLocked();
120     supportedThemeNames.add(name);
121   }
122 
123   // TODO one init method
124   protected void resolveThemes() {
125     checkLocked();
126 
127     for (final Theme theme : availableThemes.values()) {
128       addResourceDir(theme.getResourcePath());
129     }
130 
131     if (defaultThemeName != null) {
132       defaultTheme = availableThemes.get(defaultThemeName);
133       checkThemeIsAvailable(defaultThemeName, defaultTheme);
134       if (LOG.isDebugEnabled()) {
135         LOG.debug("name = '{}'", defaultThemeName);
136         LOG.debug("defaultTheme = '{}'", defaultTheme);
137       }
138     } else {
139       int deep = 0;
140       for (final Map.Entry<String, ThemeImpl> entry : availableThemes.entrySet()) {
141         final Theme theme = entry.getValue();
142         if (theme.getFallbackList().size() > deep) {
143           defaultTheme = theme;
144           deep = theme.getFallbackList().size();
145         }
146       }
147       if (defaultTheme == null) {
148         final String error = "Did not found any theme! "
149             + "Please ensure you have a tobago-config.xml with a theme-definition in your "
150             + "theme JAR. Please add a theme JAR to your WEB-INF/lib";
151         LOG.error(error);
152         throw new RuntimeException(error);
153       } else {
154         if (LOG.isInfoEnabled()) {
155           LOG.info("Using default Theme {}", defaultTheme.getName());
156         }
157       }
158     }
159     if (!supportedThemeNames.isEmpty()) {
160       for (final String name : supportedThemeNames) {
161         final Theme theme = availableThemes.get(name);
162         checkThemeIsAvailable(name, theme);
163         supportedThemes.add(theme);
164         if (LOG.isDebugEnabled()) {
165           LOG.debug("name = '{}'", name);
166           LOG.debug("supportedThemes.last() = '{}'", supportedThemes.get(supportedThemes.size() - 1));
167         }
168       }
169     }
170   }
171 
172   private void checkThemeIsAvailable(final String name, final Theme theme) {
173     if (theme == null) {
174       final String error = "Theme not found! name: '" + name + "'. "
175           + "Please ensure you have a tobago-config.xml with a theme-definition in your "
176           + "theme JAR. Found the following themes: " + availableThemes.keySet();
177       LOG.error(error);
178       throw new RuntimeException(error);
179     }
180   }
181 
182   @Override
183   public Theme getTheme(final String name) {
184     if (name == null) {
185       LOG.debug("searching theme: null");
186       return defaultTheme;
187     }
188     if (defaultTheme.getName().equals(name)) {
189       return defaultTheme;
190     }
191     for (final Theme theme : supportedThemes) {
192       if (theme.getName().equals(name)) {
193         return theme;
194       }
195     }
196     LOG.debug("searching theme '{}' not found. Using default: {}", name, defaultTheme);
197     return defaultTheme;
198   }
199 
200   protected void setDefaultThemeName(final String defaultThemeName) {
201     checkLocked();
202     this.defaultThemeName = defaultThemeName;
203   }
204 
205   @Override
206   public List<Theme> getSupportedThemes() {
207     return supportedThemes;
208   }
209 
210   protected void addResourceDir(final String resourceDir) {
211     checkLocked();
212     if (!resourceDirs.contains(resourceDir)) {
213       if (LOG.isInfoEnabled()) {
214         LOG.info("adding resourceDir = '{}'", resourceDir);
215       }
216       resourceDirs.add(0, resourceDir);
217     }
218   }
219 
220   public List<String> getResourceDirs() {
221     return resourceDirs;
222   }
223 
224   @Override
225   public Theme getDefaultTheme() {
226     return defaultTheme;
227   }
228 
229   protected void addAvailableTheme(ThemeImpl availableTheme) {
230     checkLocked();
231     availableThemes.put(availableTheme.getName(), availableTheme);
232   }
233 
234   public Map<String, ThemeImpl> getAvailableThemes() {
235     return availableThemes;
236   }
237 
238   protected RenderersConfig getRenderersConfig() {
239     return renderersConfig;
240   }
241 
242   protected void setRenderersConfig(final RenderersConfig renderersConfig) {
243     checkLocked();
244     this.renderersConfig = renderersConfig;
245   }
246 
247   @Override
248   public ProjectStage getProjectStage() {
249     return projectStage;
250   }
251 
252   // TODO one init method
253   protected void initProjectState(final ServletContext servletContext) {
254     checkLocked();
255     String stageName = null;
256     try {
257       final Context ctx = new InitialContext();
258       final Object obj = JndiUtils.getJndiProperty(ctx, "jsf", "ProjectStage");
259       if (obj != null) {
260         if (obj instanceof String) {
261           stageName = (String) obj;
262         } else {
263           LOG.warn("JNDI lookup for key {} should return a java.lang.String value",
264               ProjectStage.PROJECT_STAGE_JNDI_NAME);
265         }
266       }
267     } catch (final NamingException e) {
268       // ignore
269     }
270 
271     if (stageName == null) {
272       stageName = servletContext.getInitParameter(ProjectStage.PROJECT_STAGE_PARAM_NAME);
273     }
274 
275     if (stageName == null) {
276       stageName = System.getProperty("org.apache.myfaces.PROJECT_STAGE");
277     }
278 
279     if (stageName != null) {
280       try {
281         projectStage = ProjectStage.valueOf(stageName);
282       } catch (final IllegalArgumentException e) {
283         LOG.error("Couldn't discover the current project stage", e);
284       }
285     }
286     if (projectStage == null) {
287       if (LOG.isInfoEnabled()) {
288         LOG.info("Couldn't discover the current project stage, using {}", ProjectStage.Production);
289       }
290       projectStage = ProjectStage.Production;
291     }
292   }
293 
294   protected synchronized void initDefaultValidatorInfo() {
295     if (defaultValidatorInfo != null) {
296       checkLocked();
297     }
298     final FacesContext facesContext = FacesContext.getCurrentInstance();
299     if (facesContext != null) {
300       try {
301         final Application application = facesContext.getApplication();
302         final Map<String, String> map = application.getDefaultValidatorInfo();
303         if (map.size() > 0) {
304           defaultValidatorInfo = Collections.unmodifiableMap(map);
305         } else {
306           defaultValidatorInfo = Collections.emptyMap();
307         }
308       } catch (final Exception e) {
309         LOG.error("Can't initialize default validators (this happens with JBoss GateIn 3.6.0).", e);
310         defaultValidatorInfo = Collections.emptyMap();
311       }
312     }
313   }
314 
315   @Override
316   public boolean isCreateSessionSecret() {
317     return createSessionSecret;
318   }
319 
320   protected void setCreateSessionSecret(final boolean createSessionSecret) {
321     checkLocked();
322     this.createSessionSecret = createSessionSecret;
323   }
324 
325   @Override
326   public boolean isCheckSessionSecret() {
327     return checkSessionSecret;
328   }
329 
330   protected void setCheckSessionSecret(final boolean checkSessionSecret) {
331     checkLocked();
332     this.checkSessionSecret = checkSessionSecret;
333   }
334 
335 
336   @Override
337   public boolean isPreventFrameAttacks() {
338     return preventFrameAttacks;
339   }
340 
341   protected void setPreventFrameAttacks(final boolean preventFrameAttacks) {
342     checkLocked();
343     this.preventFrameAttacks = preventFrameAttacks;
344   }
345 
346   @Override
347   public ContentSecurityPolicy getContentSecurityPolicy() {
348     return contentSecurityPolicy;
349   }
350 
351   @Override
352   public boolean isSetNosniffHeader() {
353     return setNosniffHeader;
354   }
355 
356   protected void setSetNosniffHeader(final boolean setNosniffHeader) {
357     checkLocked();
358     this.setNosniffHeader = setNosniffHeader;
359   }
360 
361   public Map<String, String> getDefaultValidatorInfo() {
362     // TODO: if the startup hasn't found a FacesContext and Application, this may depend on the order of the listeners.
363     if (defaultValidatorInfo == null) {
364       initDefaultValidatorInfo();
365     }
366     return defaultValidatorInfo;
367   }
368 
369   @Override
370   public Sanitizer getSanitizer() {
371     return sanitizer;
372   }
373 
374   protected void setSanitizer(Sanitizer sanitizer) {
375     checkLocked();
376     this.sanitizer = sanitizer;
377   }
378 
379   @Override
380   public boolean isAutoAccessKeyFromLabel() {
381     return autoAccessKeyFromLabel;
382   }
383 
384   public void setAutoAccessKeyFromLabel(boolean autoAccessKeyFromLabel) {
385     checkLocked();
386     this.autoAccessKeyFromLabel = autoAccessKeyFromLabel;
387   }
388 
389   @Override
390   public Map<String, String> getMimeTypes() {
391     return mimeTypes;
392   }
393 
394   /**
395    * {@inheritDoc}
396    */
397   @Override
398   @Deprecated
399   public boolean isClassicDateTimePicker() {
400     return false;
401   }
402 
403   @Override
404   public String toString() {
405     final StringBuilder builder = new StringBuilder();
406     builder.append("TobagoConfigImpl{");
407     builder.append("\nsupportedThemes=[");
408     for (final Theme supportedTheme : supportedThemes) {
409       builder.append(supportedTheme.getName());
410       builder.append(", ");
411     }
412     builder.append("], \ndefaultTheme=");
413     builder.append(defaultTheme != null ? defaultTheme.getName() : null);
414     builder.append(", \nresourceDirs=");
415     builder.append(resourceDirs);
416     builder.append(", \navailableThemes=");
417     builder.append(availableThemes.keySet());
418     builder.append(", \nprojectStage=");
419     builder.append(projectStage);
420     builder.append(", \ncreateSessionSecret=");
421     builder.append(createSessionSecret);
422     builder.append(", \ncheckSessionSecret=");
423     builder.append(checkSessionSecret);
424     builder.append(", \npreventFrameAttacks=");
425     builder.append(preventFrameAttacks);
426     builder.append(", \ncontentSecurityPolicy=");
427     builder.append(contentSecurityPolicy);
428     builder.append(", \nsetNosniffHeader=");
429     builder.append(setNosniffHeader);
430     builder.append(", \ndefaultValidatorInfo=");
431     builder.append(defaultValidatorInfo);
432     builder.append(", \nsanitizer=");
433     builder.append(sanitizer);
434     builder.append(", \nautoAccessKeyFromLabel=");
435     builder.append(autoAccessKeyFromLabel);
436     // to see only different (ignore alternative names for the same theme)
437     builder.append(", \nthemes=");
438     final Set<Theme> all = new HashSet<Theme>(availableThemes.values());
439     builder.append(all);
440     builder.append(", \nmimeTypes=");
441     builder.append(mimeTypes);
442     builder.append('}');
443     return builder.toString();
444   }
445 }