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 Map<String, String> mimeTypes;
73  
74    private boolean unmodifiable = false;
75  
76    protected TobagoConfigImpl() {
77      supportedThemeNames = new ArrayList<String>();
78      supportedThemes = new ArrayList<Theme>();
79      availableThemes = new HashMap<String, ThemeImpl>();
80      resourceDirs = new ArrayList<String>();
81      createSessionSecret = true;
82      checkSessionSecret = true;
83      preventFrameAttacks = true;
84      setNosniffHeader = true;
85      contentSecurityPolicy = new ContentSecurityPolicy(ContentSecurityPolicy.Mode.OFF.getValue());
86      mimeTypes = new HashMap<String, String>();
87    }
88  
89    /**
90     * Lock the configuration, so it cannot be modified any more.
91     */
92    protected void lock() {
93      unmodifiable = true;
94      supportedThemes = Collections.unmodifiableList(supportedThemes);
95      for (Theme theme : supportedThemes) {
96        ((ThemeImpl) theme).lock();
97      }
98      supportedThemeNames = Collections.unmodifiableList(supportedThemeNames);
99      resourceDirs = Collections.unmodifiableList(resourceDirs);
100     availableThemes = Collections.unmodifiableMap(availableThemes);
101 
102     if (renderersConfig instanceof RenderersConfigImpl) {
103       ((RenderersConfigImpl) renderersConfig).lock();
104     }
105     contentSecurityPolicy.lock();
106 
107     mimeTypes = Collections.unmodifiableMap(mimeTypes);
108   }
109 
110   private void checkLocked() throws IllegalStateException {
111     if (unmodifiable) {
112       throw new RuntimeException("The configuration must not be changed after initialization!");
113     }
114   }
115 
116   protected void addSupportedThemeName(final String name) {
117     checkLocked();
118     supportedThemeNames.add(name);
119   }
120 
121   // TODO one init method
122   protected void resolveThemes() {
123     checkLocked();
124 
125     for (final Theme theme : availableThemes.values()) {
126       addResourceDir(theme.getResourcePath());
127     }
128 
129     if (defaultThemeName != null) {
130       defaultTheme = availableThemes.get(defaultThemeName);
131       checkThemeIsAvailable(defaultThemeName, defaultTheme);
132       if (LOG.isDebugEnabled()) {
133         LOG.debug("name = '{}'", defaultThemeName);
134         LOG.debug("defaultTheme = '{}'", defaultTheme);
135       }
136     } else {
137       int deep = 0;
138       for (final Map.Entry<String, ThemeImpl> entry : availableThemes.entrySet()) {
139         final Theme theme = entry.getValue();
140         if (theme.getFallbackList().size() > deep) {
141           defaultTheme = theme;
142           deep = theme.getFallbackList().size();
143         }
144       }
145       if (defaultTheme == null) {
146         final String error = "Did not found any theme! "
147             + "Please ensure you have a tobago-config.xml with a theme-definition in your "
148             + "theme JAR. Please add a theme JAR to your WEB-INF/lib";
149         LOG.error(error);
150         throw new RuntimeException(error);
151       } else {
152         if (LOG.isInfoEnabled()) {
153           LOG.info("Using default Theme {}", defaultTheme.getName());
154         }
155       }
156     }
157     if (!supportedThemeNames.isEmpty()) {
158       for (final String name : supportedThemeNames) {
159         final Theme theme = availableThemes.get(name);
160         checkThemeIsAvailable(name, theme);
161         supportedThemes.add(theme);
162         if (LOG.isDebugEnabled()) {
163           LOG.debug("name = '{}'", name);
164           LOG.debug("supportedThemes.last() = '{}'", supportedThemes.get(supportedThemes.size() - 1));
165         }
166       }
167     }
168   }
169 
170   private void checkThemeIsAvailable(final String name, final Theme theme) {
171     if (theme == null) {
172       final String error = "Theme not found! name: '" + name + "'. "
173           + "Please ensure you have a tobago-config.xml with a theme-definition in your "
174           + "theme JAR. Found the following themes: " + availableThemes.keySet();
175       LOG.error(error);
176       throw new RuntimeException(error);
177     }
178   }
179 
180   @Override
181   public Theme getTheme(final String name) {
182     if (name == null) {
183       LOG.debug("searching theme: null");
184       return defaultTheme;
185     }
186     if (defaultTheme.getName().equals(name)) {
187       return defaultTheme;
188     }
189     for (final Theme theme : supportedThemes) {
190       if (theme.getName().equals(name)) {
191         return theme;
192       }
193     }
194     LOG.debug("searching theme '{}' not found. Using default: {}", name, defaultTheme);
195     return defaultTheme;
196   }
197 
198   protected void setDefaultThemeName(final String defaultThemeName) {
199     checkLocked();
200     this.defaultThemeName = defaultThemeName;
201   }
202 
203   @Override
204   public List<Theme> getSupportedThemes() {
205     return supportedThemes;
206   }
207 
208   protected void addResourceDir(final String resourceDir) {
209     checkLocked();
210     if (!resourceDirs.contains(resourceDir)) {
211       if (LOG.isInfoEnabled()) {
212         LOG.info("adding resourceDir = '{}'", resourceDir);
213       }
214       resourceDirs.add(0, resourceDir);
215     }
216   }
217 
218   public List<String> getResourceDirs() {
219     return resourceDirs;
220   }
221 
222   @Override
223   public Theme getDefaultTheme() {
224     return defaultTheme;
225   }
226 
227   protected void addAvailableTheme(ThemeImpl availableTheme) {
228     checkLocked();
229     availableThemes.put(availableTheme.getName(), availableTheme);
230   }
231 
232   public Map<String, ThemeImpl> getAvailableThemes() {
233     return availableThemes;
234   }
235 
236   protected RenderersConfig getRenderersConfig() {
237     return renderersConfig;
238   }
239 
240   protected void setRenderersConfig(final RenderersConfig renderersConfig) {
241     checkLocked();
242     this.renderersConfig = renderersConfig;
243   }
244 
245   @Override
246   public ProjectStage getProjectStage() {
247     return projectStage;
248   }
249 
250   // TODO one init method
251   protected void initProjectState(final ServletContext servletContext) {
252     checkLocked();
253     String stageName = null;
254     try {
255       final Context ctx = new InitialContext();
256       final Object obj = JndiUtils.getJndiProperty(ctx, "jsf", "ProjectStage");
257       if (obj != null) {
258         if (obj instanceof String) {
259           stageName = (String) obj;
260         } else {
261           LOG.warn("JNDI lookup for key {} should return a java.lang.String value",
262               ProjectStage.PROJECT_STAGE_JNDI_NAME);
263         }
264       }
265     } catch (final NamingException e) {
266       // ignore
267     }
268 
269     if (stageName == null) {
270       stageName = servletContext.getInitParameter(ProjectStage.PROJECT_STAGE_PARAM_NAME);
271     }
272 
273     if (stageName == null) {
274       stageName = System.getProperty("org.apache.myfaces.PROJECT_STAGE");
275     }
276 
277     if (stageName != null) {
278       try {
279         projectStage = ProjectStage.valueOf(stageName);
280       } catch (final IllegalArgumentException e) {
281         LOG.error("Couldn't discover the current project stage", e);
282       }
283     }
284     if (projectStage == null) {
285       if (LOG.isInfoEnabled()) {
286         LOG.info("Couldn't discover the current project stage, using {}", ProjectStage.Production);
287       }
288       projectStage = ProjectStage.Production;
289     }
290   }
291 
292   protected synchronized void initDefaultValidatorInfo() {
293     if (defaultValidatorInfo != null) {
294       checkLocked();
295     }
296     final FacesContext facesContext = FacesContext.getCurrentInstance();
297     if (facesContext != null) {
298       try {
299         final Application application = facesContext.getApplication();
300         final Map<String, String> map = application.getDefaultValidatorInfo();
301         if (map.size() > 0) {
302           defaultValidatorInfo = Collections.unmodifiableMap(map);
303         } else {
304           defaultValidatorInfo = Collections.emptyMap();
305         }
306       } catch (final Exception e) {
307         LOG.error("Can't initialize default validators (this happens with JBoss GateIn 3.6.0).", e);
308         defaultValidatorInfo = Collections.emptyMap();
309       }
310     }
311   }
312 
313   @Override
314   public boolean isCreateSessionSecret() {
315     return createSessionSecret;
316   }
317 
318   protected void setCreateSessionSecret(final boolean createSessionSecret) {
319     checkLocked();
320     this.createSessionSecret = createSessionSecret;
321   }
322 
323   @Override
324   public boolean isCheckSessionSecret() {
325     return checkSessionSecret;
326   }
327 
328   protected void setCheckSessionSecret(final boolean checkSessionSecret) {
329     checkLocked();
330     this.checkSessionSecret = checkSessionSecret;
331   }
332 
333 
334   @Override
335   public boolean isPreventFrameAttacks() {
336     return preventFrameAttacks;
337   }
338 
339   protected void setPreventFrameAttacks(final boolean preventFrameAttacks) {
340     checkLocked();
341     this.preventFrameAttacks = preventFrameAttacks;
342   }
343 
344   @Override
345   public ContentSecurityPolicy getContentSecurityPolicy() {
346     return contentSecurityPolicy;
347   }
348 
349   @Override
350   public boolean isSetNosniffHeader() {
351     return setNosniffHeader;
352   }
353 
354   protected void setSetNosniffHeader(final boolean setNosniffHeader) {
355     checkLocked();
356     this.setNosniffHeader = setNosniffHeader;
357   }
358 
359   public Map<String, String> getDefaultValidatorInfo() {
360     // TODO: if the startup hasn't found a FacesContext and Application, this may depend on the order of the listeners.
361     if (defaultValidatorInfo == null) {
362       initDefaultValidatorInfo();
363     }
364     return defaultValidatorInfo;
365   }
366 
367   @Override
368   public Sanitizer getSanitizer() {
369     return sanitizer;
370   }
371 
372   protected void setSanitizer(Sanitizer sanitizer) {
373     checkLocked();
374     this.sanitizer = sanitizer;
375   }
376 
377   @Override
378   public Map<String, String> getMimeTypes() {
379     return mimeTypes;
380   }
381 
382   /**
383    * {@inheritDoc}
384    */
385   @Override
386   @Deprecated
387   public boolean isClassicDateTimePicker() {
388     return false;
389   }
390 
391   @Override
392   public String toString() {
393     final StringBuilder builder = new StringBuilder();
394     builder.append("TobagoConfigImpl{");
395     builder.append("\nsupportedThemes=[");
396     for (final Theme supportedTheme : supportedThemes) {
397       builder.append(supportedTheme.getName());
398       builder.append(", ");
399     }
400     builder.append("], \ndefaultTheme=");
401     builder.append(defaultTheme != null ? defaultTheme.getName() : null);
402     builder.append(", \nresourceDirs=");
403     builder.append(resourceDirs);
404     builder.append(", \navailableThemes=");
405     builder.append(availableThemes.keySet());
406     builder.append(", \nprojectStage=");
407     builder.append(projectStage);
408     builder.append(", \ncreateSessionSecret=");
409     builder.append(createSessionSecret);
410     builder.append(", \ncheckSessionSecret=");
411     builder.append(checkSessionSecret);
412     builder.append(", \npreventFrameAttacks=");
413     builder.append(preventFrameAttacks);
414     builder.append(", \ncontentSecurityPolicy=");
415     builder.append(contentSecurityPolicy);
416     builder.append(", \nsetNosniffHeader=");
417     builder.append(setNosniffHeader);
418     builder.append(", \ndefaultValidatorInfo=");
419     builder.append(defaultValidatorInfo);
420     builder.append(", \nsanitizer=");
421     builder.append(sanitizer);
422     // to see only different (ignore alternative names for the same theme)
423     builder.append(", \nthemes=");
424     final Set<Theme> all = new HashSet<Theme>(availableThemes.values());
425     builder.append(all);
426     builder.append(", \nmimeTypes=");
427     builder.append(mimeTypes);
428     builder.append('}');
429     return builder.toString();
430   }
431 }