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.sanitizer.IgnoringSanitizer;
24  import org.apache.myfaces.tobago.sanitizer.JsoupSanitizer;
25  import org.apache.myfaces.tobago.sanitizer.Sanitizer;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  
29  import java.util.ArrayList;
30  import java.util.Collections;
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        // resource dirs
95        for (final String dir : fragment.getResourceDirs()) {
96          result.addResourceDir(dir);
97        }
98  
99        // renderers config
100       if (fragment.getRenderersConfig() != null) {
101         if (result.getRenderersConfig() instanceof RenderersConfigImpl) {
102           ((RenderersConfigImpl) result.getRenderersConfig()).merge(fragment.getRenderersConfig(), false);
103         } else if (result.getRenderersConfig() == null) {
104           result.setRenderersConfig(fragment.getRenderersConfig());
105         }
106       }
107 
108       // session secret
109       if (fragment.getCreateSessionSecret() != null) {
110         result.setCreateSessionSecret(fragment.getCreateSessionSecret());
111       }
112       if (fragment.getCheckSessionSecret() != null) {
113         result.setCheckSessionSecret(fragment.getCheckSessionSecret());
114       }
115 
116       if (fragment.getPreventFrameAttacks() != null) {
117         result.setPreventFrameAttacks(fragment.getPreventFrameAttacks());
118       }
119 
120       if (fragment.getContentSecurityPolicy() != null) {
121         result.getContentSecurityPolicy().merge(fragment.getContentSecurityPolicy());
122       }
123 
124       if (fragment.getSetNosniffHeader() != null) {
125         result.setSetNosniffHeader(fragment.getSetNosniffHeader());
126       }
127 
128       if (fragment.getSanitizerClass() != null) {
129         sanitizerClass = fragment.getSanitizerClass();
130         sanitizerProperties = fragment.getSanitizerProperties();
131       }
132 
133       if (fragment.getAutoAccessKeyFromLabel() != null) {
134         result.setAutoAccessKeyFromLabel(fragment.getAutoAccessKeyFromLabel());
135       }
136 
137       // theme definition
138       result.toString();
139       for (ThemeImpl theme : fragment.getThemeDefinitions()) {
140         result.addAvailableTheme(theme);
141       }
142 
143       // url
144       // todo???
145 
146       final Map<String, String> mimeTypes = result.getMimeTypes();
147       for (final Map.Entry<String, String> entry : fragment.getMimeTypes().entrySet()) {
148         mimeTypes.put(entry.getKey(), entry.getValue());
149       }
150 
151     }
152 
153     resolveThemes(result, result.getAvailableThemes());
154 
155     if (sanitizerClass != null) {
156       try {
157         final Class<? extends Sanitizer> aClass = Class.forName(sanitizerClass).asSubclass(Sanitizer.class);
158         final Sanitizer sanitizer = aClass.newInstance();
159         sanitizer.setProperties(sanitizerProperties);
160         result.setSanitizer(sanitizer);
161       } catch (Throwable e) {
162         LOG.error("Can't create sanitizer: '" + sanitizerClass + "'", e);
163         result.setSanitizer(new IgnoringSanitizer());
164       }
165     }
166 
167     return result;
168   }
169 
170   protected void makeTransitive() {
171     // make the half order transitive: a < b && b < c => a < c
172     boolean growing = true;
173     while (growing) {
174       growing = false;
175       for (int i = 0; i < pairs.size(); i++) {
176         for (int j = 0; j < pairs.size(); j++) {
177           if (pairs.get(i).getHigher() == pairs.get(j).getLower()
178               && !isInRelation(pairs.get(i).getLower(), pairs.get(j).getHigher())) {
179             pairs.add(new Pair(pairs.get(i).getLower(), pairs.get(j).getHigher()));
180             growing = true;
181           }
182         }
183       }
184     }
185   }
186 
187   protected void ensureIrreflexive() {
188     for (final Pair a : pairs) {
189         if (a.getLower() == a.getHigher()) {
190           final StringBuilder buffer = new StringBuilder();
191           buffer.append("Ordering problem. There are conflicting order rules. Not irreflexive. '");
192           buffer.append(a.getLower());
193           buffer.append("' < '");
194           buffer.append(a.getHigher());
195           buffer.append("'!\nThe reason may be a cycle.\n");
196           buffer.append("Complete list of rules: \n");
197           for (final Pair pair : pairs) {
198             buffer.append("'");
199             buffer.append(pair.getLower());
200             buffer.append("' < '");
201             buffer.append(pair.getHigher());
202             buffer.append("'\n");
203 
204           }
205           throw new RuntimeException(buffer.toString());
206         }
207       }
208   }
209 
210   protected void ensureAntiSymmetric() {
211     for (final Pair a : pairs) {
212       for (final Pair b : pairs) {
213         if (a.getLower() == b.getHigher() && a.getHigher() == b.getLower()) {
214           final StringBuilder buffer = new StringBuilder();
215           buffer.append("Ordering problem. There are conflicting order rules. Not antisymmetric. '");
216           buffer.append(a.getLower());
217           buffer.append("' < '");
218           buffer.append(a.getHigher());
219           buffer.append("'" + "'");
220           buffer.append(a.getLower());
221           buffer.append("' > '");
222           buffer.append(a.getHigher());
223           buffer.append("'!\nThe reason may be a cycle.\n");
224           buffer.append("Complete list of rules: \n");
225           for (final Pair pair : pairs) {
226             buffer.append("'");
227             buffer.append(pair.getLower());
228             buffer.append("' < '");
229             buffer.append(pair.getHigher());
230             buffer.append("'\n");
231 
232           }
233           throw new RuntimeException(buffer.toString());
234         }
235       }
236     }
237   }
238 
239   @Override
240   public int compare(final TobagoConfigFragment a, final TobagoConfigFragment b) {
241     if (isInRelation(a, b)) {
242       return -1;
243     }
244     if (isInRelation(b, a)) {
245       return 1;
246     }
247     return 0;
248   }
249 
250   protected void createRelevantPairs() {
251 
252     pairs = new ArrayList<Pair>();
253 
254     // collecting all relations, which are relevant for us. We don't need "before" and "after" of unknown names.
255     for (final TobagoConfigFragment tobagoConfig : list) {
256       for (final String befores : tobagoConfig.getBefore()) {
257         final TobagoConfigFragment before = findByName(befores);
258         if (before != null) {
259           pairs.add(new Pair(tobagoConfig, before));
260         }
261       }
262       for (final String afters : tobagoConfig.getAfter()) {
263         final TobagoConfigFragment after = findByName(afters);
264         if (after != null) {
265           pairs.add(new Pair(after, tobagoConfig));
266         }
267       }
268     }
269   }
270 
271   protected void sort0() {
272     Collections.sort(list, this);
273   }
274 
275   private boolean isInRelation(final TobagoConfigFragment lower, final TobagoConfigFragment higher) {
276     for (final Pair p : pairs) {
277       if (p.getLower() == lower && p.getHigher() == higher) {
278         return true;
279       }
280     }
281     return false;
282   }
283 
284   private TobagoConfigFragment findByName(final String name) {
285     for (final TobagoConfigFragment tobagoConfig : list) {
286       if (name.equals(tobagoConfig.getName())) {
287         return tobagoConfig;
288       }
289     }
290     return null;
291   }
292 
293   private void resolveThemes(TobagoConfigImpl tobagoConfig, Map<String, ThemeImpl> map) {
294     for (final ThemeImpl theme : map.values()) {
295       final String fallbackName = theme.getFallbackName();
296       final ThemeImpl fallback = map.get(fallbackName);
297       theme.setFallback(fallback);
298     }
299     for (final ThemeImpl theme : map.values()) {
300       theme.resolveFallbacks();
301     }
302     for (final ThemeImpl theme : map.values()) {
303       theme.resolveRendererConfig(tobagoConfig.getRenderersConfig());
304     }
305     for (final ThemeImpl theme : map.values()) {
306       theme.resolveResources();
307     }
308     for (final ThemeImpl theme : map.values()) {
309       theme.init();
310     }
311   }
312 
313   protected List<Pair> getPairs() {
314     return pairs;
315   }
316 
317   private static class Pair {
318 
319     private final TobagoConfigFragment lower;
320     private final TobagoConfigFragment higher;
321 
322     private Pair(final TobagoConfigFragment lower, final TobagoConfigFragment higher) {
323       this.lower = lower;
324       this.higher = higher;
325     }
326 
327     public TobagoConfigFragment getLower() {
328       return lower;
329     }
330 
331     public TobagoConfigFragment getHigher() {
332       return higher;
333     }
334 
335     @Override
336     public String toString() {
337       return lower + "<" + higher;
338     }
339   }
340 
341 }