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