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.config.TobagoConfig;
23  import org.apache.myfaces.tobago.context.Theme;
24  import org.apache.myfaces.tobago.context.ThemeImpl;
25  import org.apache.myfaces.tobago.exception.TobagoConfigurationException;
26  import org.apache.myfaces.tobago.sanitizer.Sanitizer;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import javax.faces.application.Application;
31  import javax.faces.application.ProjectStage;
32  import javax.faces.context.FacesContext;
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  
41  /**
42   * <p>
43   * Implementation of the Tobago configuration.
44   * </p>
45   * <p>
46   * All setters must are protected, so EL can't modify this config.
47   * </p>
48   */
49  public class TobagoConfigImpl extends TobagoConfig {
50  
51    private static final Logger LOG = LoggerFactory.getLogger(TobagoConfigImpl.class);
52  
53    private List<Theme> supportedThemes;
54    private List<String> supportedThemeNames;
55    private Theme defaultTheme;
56    private String defaultThemeName;
57    private Map<String, ThemeImpl> availableThemes;
58    private boolean createSessionSecret;
59    private boolean checkSessionSecret;
60    private boolean preventFrameAttacks;
61    private ContentSecurityPolicy contentSecurityPolicy;
62    private SecurityAnnotation securityAnnotation;
63    private boolean setNosniffHeader;
64    private Map<String, String> defaultValidatorInfo;
65    private Sanitizer sanitizer;
66    private boolean decodeLineFeed;
67    private Map<String, String> mimeTypes;
68  
69    private boolean unmodifiable = false;
70  
71    protected TobagoConfigImpl() {
72      supportedThemeNames = new ArrayList<>();
73      supportedThemes = new ArrayList<>();
74      availableThemes = new HashMap<>();
75      createSessionSecret = true;
76      checkSessionSecret = true;
77      preventFrameAttacks = true;
78      setNosniffHeader = true;
79      securityAnnotation = SecurityAnnotation.disable;
80      decodeLineFeed = true;
81      contentSecurityPolicy = new ContentSecurityPolicy(ContentSecurityPolicy.Mode.OFF.getValue());
82      mimeTypes = new HashMap<>();
83    }
84  
85    /**
86     * Lock the configuration, so it cannot be modified any more.
87     */
88    protected void lock() {
89      unmodifiable = true;
90      supportedThemes = Collections.unmodifiableList(supportedThemes);
91      for (final Theme theme : supportedThemes) {
92        ((ThemeImpl) theme).lock();
93      }
94      supportedThemeNames = Collections.unmodifiableList(supportedThemeNames);
95      availableThemes = Collections.unmodifiableMap(availableThemes);
96  
97      contentSecurityPolicy.lock();
98  
99      mimeTypes = Collections.unmodifiableMap(mimeTypes);
100   }
101 
102   private void checkLocked() throws IllegalStateException {
103     if (unmodifiable) {
104       throw new TobagoConfigurationException("The configuration must not be changed after initialization!");
105     }
106   }
107 
108   protected void addSupportedThemeName(final String name) {
109     checkLocked();
110     supportedThemeNames.add(name);
111   }
112 
113   // TODO one init method
114   protected void resolveThemes() {
115     checkLocked();
116 
117     if (defaultThemeName != null) {
118       defaultTheme = availableThemes.get(defaultThemeName);
119       checkThemeIsAvailable(defaultThemeName, defaultTheme);
120       if (LOG.isDebugEnabled()) {
121         LOG.debug("name = '{}'", defaultThemeName);
122         LOG.debug("defaultTheme = '{}'", defaultTheme);
123       }
124     } else {
125       int deep = 0;
126       for (final Map.Entry<String, ThemeImpl> entry : availableThemes.entrySet()) {
127         final Theme theme = entry.getValue();
128         if (theme.getFallbackList().size() > deep) {
129           defaultTheme = theme;
130           deep = theme.getFallbackList().size();
131         }
132       }
133       if (defaultTheme == null) {
134         final String error = "Did not found any theme! "
135             + "Please ensure you have a tobago-config.xml with a theme-definition in your "
136             + "theme JAR. Please add a theme JAR to your WEB-INF/lib";
137         LOG.error(error);
138         throw new TobagoConfigurationException(error);
139       } else {
140         if (LOG.isInfoEnabled()) {
141           LOG.info("Using default Theme {}", defaultTheme.getName());
142         }
143       }
144     }
145     if (!supportedThemeNames.isEmpty()) {
146       for (final String name : supportedThemeNames) {
147         final Theme theme = availableThemes.get(name);
148         checkThemeIsAvailable(name, theme);
149         supportedThemes.add(theme);
150         if (LOG.isDebugEnabled()) {
151           LOG.debug("name = '{}'", name);
152           LOG.debug("supportedThemes.last() = '{}'", supportedThemes.get(supportedThemes.size() - 1));
153         }
154       }
155     }
156   }
157 
158   private void checkThemeIsAvailable(final String name, final Theme theme) {
159     if (theme == null) {
160       final String error = "Theme not found! name: '" + name + "'. "
161           + "Please ensure you have a tobago-config.xml with a theme-definition in your "
162           + "theme JAR. Found the following themes: " + availableThemes.keySet();
163       LOG.error(error);
164       throw new TobagoConfigurationException(error);
165     }
166   }
167 
168   @Override
169   public Theme getTheme(final String name) {
170     if (name == null) {
171       LOG.debug("searching theme: null");
172       return defaultTheme;
173     }
174     if (defaultTheme != null && defaultTheme.getName().equals(name)) {
175       return defaultTheme;
176     }
177     for (final Theme theme : supportedThemes) {
178       if (theme.getName().equals(name)) {
179         return theme;
180       }
181     }
182     LOG.debug("searching theme '{}' not found. Using default: {}", name, defaultTheme);
183     return defaultTheme;
184   }
185 
186   protected void setDefaultThemeName(final String defaultThemeName) {
187     checkLocked();
188     this.defaultThemeName = defaultThemeName;
189   }
190 
191   @Override
192   public List<Theme> getSupportedThemes() {
193     return supportedThemes;
194   }
195 
196   @Override
197   public Theme getDefaultTheme() {
198     return defaultTheme;
199   }
200 
201   protected void addAvailableTheme(final ThemeImpl availableTheme) {
202     checkLocked();
203     availableThemes.put(availableTheme.getName(), availableTheme);
204   }
205 
206   public Map<String, ThemeImpl> getAvailableThemes() {
207     return availableThemes;
208   }
209 
210   /**
211    * @deprecated since 4.0.0, use FacesContext.isProjectStage
212    * @return the ProjectStage
213    */
214   @Override
215   @Deprecated
216   public ProjectStage getProjectStage() {
217     return FacesContext.getCurrentInstance().getApplication().getProjectStage();
218   }
219 
220   protected synchronized void initDefaultValidatorInfo() {
221     if (defaultValidatorInfo != null) {
222       checkLocked();
223     }
224     final FacesContext facesContext = FacesContext.getCurrentInstance();
225     if (facesContext != null) {
226       try {
227         final Application application = facesContext.getApplication();
228         final Map<String, String> map = application.getDefaultValidatorInfo();
229         if (map.size() > 0) {
230           defaultValidatorInfo = Collections.unmodifiableMap(map);
231         } else {
232           defaultValidatorInfo = Collections.emptyMap();
233         }
234       } catch (final Exception e) {
235         LOG.error("Can't initialize default validators (this happens with JBoss GateIn 3.6.0).", e);
236         defaultValidatorInfo = Collections.emptyMap();
237       }
238     }
239   }
240 
241   @Override
242   public boolean isCreateSessionSecret() {
243     return createSessionSecret;
244   }
245 
246   protected void setCreateSessionSecret(final boolean createSessionSecret) {
247     checkLocked();
248     this.createSessionSecret = createSessionSecret;
249   }
250 
251   @Override
252   public boolean isCheckSessionSecret() {
253     return checkSessionSecret;
254   }
255 
256   protected void setCheckSessionSecret(final boolean checkSessionSecret) {
257     checkLocked();
258     this.checkSessionSecret = checkSessionSecret;
259   }
260 
261 
262   @Override
263   public boolean isPreventFrameAttacks() {
264     return preventFrameAttacks;
265   }
266 
267   protected void setPreventFrameAttacks(final boolean preventFrameAttacks) {
268     checkLocked();
269     this.preventFrameAttacks = preventFrameAttacks;
270   }
271 
272   @Override
273   public ContentSecurityPolicy getContentSecurityPolicy() {
274     return contentSecurityPolicy;
275   }
276 
277   @Override
278   public boolean isSetNosniffHeader() {
279     return setNosniffHeader;
280   }
281 
282   protected void setSetNosniffHeader(final boolean setNosniffHeader) {
283     checkLocked();
284     this.setNosniffHeader = setNosniffHeader;
285   }
286 
287   @Override
288   public SecurityAnnotation getSecurityAnnotation() {
289     return securityAnnotation;
290   }
291 
292   public void setSecurityAnnotation(final SecurityAnnotation securityAnnotation) {
293     checkLocked();
294     this.securityAnnotation = securityAnnotation;
295   }
296 
297   public Map<String, String> getDefaultValidatorInfo() {
298     // TODO: if the startup hasn't found a FacesContext and Application, this may depend on the order of the listeners.
299     if (defaultValidatorInfo == null) {
300       initDefaultValidatorInfo();
301     }
302     return defaultValidatorInfo;
303   }
304 
305   @Override
306   public Sanitizer getSanitizer() {
307     return sanitizer;
308   }
309 
310   protected void setSanitizer(final Sanitizer sanitizer) {
311     checkLocked();
312     this.sanitizer = sanitizer;
313   }
314 
315   @Override
316   public boolean isDecodeLineFeed() {
317     return decodeLineFeed;
318   }
319 
320   public void setDecodeLineFeed(final boolean decodeLineFeed) {
321     checkLocked();
322     this.decodeLineFeed = decodeLineFeed;
323   }
324 
325   @Override
326   public Map<String, String> getMimeTypes() {
327     return mimeTypes;
328   }
329 
330   /**
331    * {@inheritDoc}
332    * @deprecated since Tobago 3.0.0
333    */
334   @Override
335   @Deprecated
336   public boolean isClassicDateTimePicker() {
337     return false;
338   }
339 
340   @Override
341   public String toString() {
342     final StringBuilder builder = new StringBuilder();
343     builder.append("TobagoConfigImpl{");
344     builder.append("\nsupportedThemes=[");
345     for (final Theme supportedTheme : supportedThemes) {
346       builder.append(supportedTheme.getName());
347       builder.append(", ");
348     }
349     builder.append("], \ndefaultTheme=");
350     builder.append(defaultTheme != null ? defaultTheme.getName() : null);
351     builder.append(", \navailableThemes=");
352     builder.append(availableThemes.keySet());
353     builder.append(", \ncreateSessionSecret=");
354     builder.append(createSessionSecret);
355     builder.append(", \ncheckSessionSecret=");
356     builder.append(checkSessionSecret);
357     builder.append(", \npreventFrameAttacks=");
358     builder.append(preventFrameAttacks);
359     builder.append(", \ncontentSecurityPolicy=");
360     builder.append(contentSecurityPolicy);
361     builder.append(", \nsecurityAnnotation=");
362     builder.append(securityAnnotation);
363     builder.append(", \nsetNosniffHeader=");
364     builder.append(setNosniffHeader);
365     builder.append(", \ndefaultValidatorInfo=");
366     builder.append(defaultValidatorInfo);
367     builder.append(", \nsanitizer=");
368     builder.append(sanitizer);
369     // to see only different (ignore alternative names for the same theme)
370     builder.append(", \nthemes=");
371     final Set<Theme> all = new HashSet<>(availableThemes.values());
372     builder.append(all);
373     builder.append(", \nmimeTypes=");
374     builder.append(mimeTypes);
375     builder.append('}');
376     return builder.toString();
377   }
378 }