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.context.ThemeImpl;
23  import org.apache.myfaces.tobago.exception.TobagoConfigurationException;
24  import org.apache.myfaces.tobago.sanitizer.IgnoringSanitizer;
25  import org.apache.myfaces.tobago.sanitizer.JsoupSanitizer;
26  import org.apache.myfaces.tobago.sanitizer.Sanitizer;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import java.util.ArrayList;
31  import java.util.Comparator;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Properties;
35  
36  public class TobagoConfigSorter implements Comparator<TobagoConfigFragment> {
37  
38    private static final Logger LOG = LoggerFactory.getLogger(TobagoConfigSorter.class);
39  
40    private List<TobagoConfigFragment> list;
41    private List<Pair> pairs;
42  
43    public TobagoConfigSorter(final List<TobagoConfigFragment> list) {
44      this.list = list;
45    }
46  
47    public void sort() {
48  
49      createRelevantPairs();
50  
51      makeTransitive();
52  
53      ensureIrreflexive();
54  
55      ensureAntiSymmetric();
56  
57      sort0();
58  
59      if (LOG.isInfoEnabled()) {
60        LOG.info("Order of the Tobago config files:");
61        for (final TobagoConfigFragment fragment : list) {
62          String name = fragment.getName();
63          if (name == null) {
64            name = "<unnamed>";
65          } else {
66            name = "'" + name + "'";
67          }
68          LOG.info("name=" + name + " url='" + fragment.getUrl() + "'");
69        }
70      }
71    }
72  
73    public TobagoConfigImpl merge() {
74  
75      final TobagoConfigImpl result = new TobagoConfigImpl();
76  
77      // default sanitizer
78      String sanitizerClass = JsoupSanitizer.class.getName();
79      Properties sanitizerProperties = new Properties();
80      sanitizerProperties.setProperty("whitelist", "relaxed");
81  
82      for (final TobagoConfigFragment fragment : list) {
83        // default theme
84        final String defaultTheme = fragment.getDefaultThemeName();
85        if (defaultTheme != null) {
86          result.setDefaultThemeName(defaultTheme);
87        }
88  
89        // supported themes
90        for (final String supported : fragment.getSupportedThemeNames()) {
91          result.addSupportedThemeName(supported);
92        }
93  
94        // session secret
95        if (fragment.getCreateSessionSecret() != null) {
96          result.setCreateSessionSecret(fragment.getCreateSessionSecret());
97        }
98        if (fragment.getCheckSessionSecret() != null) {
99          result.setCheckSessionSecret(fragment.getCheckSessionSecret());
100       }
101 
102       if (fragment.getPreventFrameAttacks() != null) {
103         result.setPreventFrameAttacks(fragment.getPreventFrameAttacks());
104       }
105 
106       if (fragment.getContentSecurityPolicy() != null) {
107         result.getContentSecurityPolicy().merge(fragment.getContentSecurityPolicy());
108       }
109 
110       if (fragment.getSecurityAnnotation() != null) {
111         result.setSecurityAnnotation(fragment.getSecurityAnnotation());
112       }
113 
114       if (fragment.getSetNosniffHeader() != null) {
115         result.setSetNosniffHeader(fragment.getSetNosniffHeader());
116       }
117 
118       if (fragment.getSanitizerClass() != null) {
119         sanitizerClass = fragment.getSanitizerClass();
120         sanitizerProperties = fragment.getSanitizerProperties();
121       }
122 
123       if (fragment.getDecodeLineFeed() != null) {
124         result.setDecodeLineFeed(fragment.getDecodeLineFeed());
125       }
126 
127       // theme definition
128       for (final ThemeImpl theme : fragment.getThemeDefinitions()) {
129         result.addAvailableTheme(theme);
130       }
131 
132       // url
133       // todo???
134 
135       final Map<String, String> mimeTypes = result.getMimeTypes();
136       for (final Map.Entry<String, String> entry : fragment.getMimeTypes().entrySet()) {
137         mimeTypes.put(entry.getKey(), entry.getValue());
138       }
139 
140     }
141 
142     resolveThemes(result, result.getAvailableThemes());
143 
144     if (sanitizerClass != null) {
145       try {
146         final Class<? extends Sanitizer> aClass = Class.forName(sanitizerClass).asSubclass(Sanitizer.class);
147         final Sanitizer sanitizer = aClass.newInstance();
148         sanitizer.setProperties(sanitizerProperties);
149         result.setSanitizer(sanitizer);
150       } catch (final Exception e) {
151         LOG.error("Can't create sanitizer: '" + sanitizerClass + "'", e);
152         result.setSanitizer(new IgnoringSanitizer());
153       }
154     }
155 
156     return result;
157   }
158 
159   protected void makeTransitive() {
160     // make the half order transitive: a < b && b < c => a < c
161     boolean growing = true;
162     while (growing) {
163       growing = false;
164       for (int i = 0; i < pairs.size(); i++) {
165         for (int j = 0; j < pairs.size(); j++) {
166           if (pairs.get(i).getHigher() == pairs.get(j).getLower()
167               && !isInRelation(pairs.get(i).getLower(), pairs.get(j).getHigher())) {
168             pairs.add(new Pair(pairs.get(i).getLower(), pairs.get(j).getHigher()));
169             growing = true;
170           }
171         }
172       }
173     }
174   }
175 
176   protected void ensureIrreflexive() {
177     for (final Pair a : pairs) {
178         if (a.getLower() == a.getHigher()) {
179           final StringBuilder buffer = new StringBuilder();
180           buffer.append("Ordering problem. There are conflicting order rules. Not irreflexive. '");
181           buffer.append(a.getLower());
182           buffer.append("' < '");
183           buffer.append(a.getHigher());
184           buffer.append("'!\nThe reason may be a cycle.\n");
185           buffer.append("Complete list of rules: \n");
186           for (final Pair pair : pairs) {
187             buffer.append("'");
188             buffer.append(pair.getLower());
189             buffer.append("' < '");
190             buffer.append(pair.getHigher());
191             buffer.append("'\n");
192 
193           }
194           throw new TobagoConfigurationException(buffer.toString());
195         }
196       }
197   }
198 
199   protected void ensureAntiSymmetric() {
200     for (final Pair a : pairs) {
201       for (final Pair b : pairs) {
202         if (a.getLower() == b.getHigher() && a.getHigher() == b.getLower()) {
203           final StringBuilder buffer = new StringBuilder();
204           buffer.append("Ordering problem. There are conflicting order rules. Not antisymmetric. '");
205           buffer.append(a.getLower());
206           buffer.append("' < '");
207           buffer.append(a.getHigher());
208           buffer.append("'" + "'");
209           buffer.append(a.getLower());
210           buffer.append("' > '");
211           buffer.append(a.getHigher());
212           buffer.append("'!\nThe reason may be a cycle.\n");
213           buffer.append("Complete list of rules: \n");
214           for (final Pair pair : pairs) {
215             buffer.append("'");
216             buffer.append(pair.getLower());
217             buffer.append("' < '");
218             buffer.append(pair.getHigher());
219             buffer.append("'\n");
220 
221           }
222           throw new TobagoConfigurationException(buffer.toString());
223         }
224       }
225     }
226   }
227 
228   @Override
229   public int compare(final TobagoConfigFragment a, final TobagoConfigFragment b) {
230     if (isInRelation(a, b)) {
231       return -1;
232     }
233     if (isInRelation(b, a)) {
234       return 1;
235     }
236     return 0;
237   }
238 
239   protected void createRelevantPairs() {
240 
241     pairs = new ArrayList<>();
242 
243     // collecting all relations, which are relevant for us. We don't need "before" and "after" of unknown names.
244     for (final TobagoConfigFragment tobagoConfig : list) {
245       for (final String befores : tobagoConfig.getBefore()) {
246         final TobagoConfigFragment before = findByName(befores);
247         if (before != null) {
248           pairs.add(new Pair(tobagoConfig, before));
249         }
250       }
251       for (final String afters : tobagoConfig.getAfter()) {
252         final TobagoConfigFragment after = findByName(afters);
253         if (after != null) {
254           pairs.add(new Pair(after, tobagoConfig));
255         }
256       }
257     }
258   }
259 
260   protected void sort0() {
261     list.sort(this);
262   }
263 
264   private boolean isInRelation(final TobagoConfigFragment lower, final TobagoConfigFragment higher) {
265     for (final Pair p : pairs) {
266       if (p.getLower() == lower && p.getHigher() == higher) {
267         return true;
268       }
269     }
270     return false;
271   }
272 
273   private TobagoConfigFragment findByName(final String name) {
274     for (final TobagoConfigFragment tobagoConfig : list) {
275       if (name.equals(tobagoConfig.getName())) {
276         return tobagoConfig;
277       }
278     }
279     return null;
280   }
281 
282   private void resolveThemes(final TobagoConfigImpl tobagoConfig, final Map<String, ThemeImpl> map) {
283     for (final ThemeImpl theme : map.values()) {
284       final String fallbackName = theme.getFallbackName();
285       final ThemeImpl fallback = map.get(fallbackName);
286       theme.setFallback(fallback);
287     }
288     for (final ThemeImpl theme : map.values()) {
289       theme.resolveFallbacks();
290     }
291     for (final ThemeImpl theme : map.values()) {
292       theme.resolveResources();
293     }
294     for (final ThemeImpl theme : map.values()) {
295       theme.init();
296     }
297   }
298 
299   protected List<Pair> getPairs() {
300     return pairs;
301   }
302 
303   private static class Pair {
304 
305     private final TobagoConfigFragment lower;
306     private final TobagoConfigFragment higher;
307 
308     private Pair(final TobagoConfigFragment lower, final TobagoConfigFragment higher) {
309       this.lower = lower;
310       this.higher = higher;
311     }
312 
313     public TobagoConfigFragment getLower() {
314       return lower;
315     }
316 
317     public TobagoConfigFragment getHigher() {
318       return higher;
319     }
320 
321     @Override
322     public String toString() {
323       return lower + "<" + higher;
324     }
325   }
326 
327 }