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