1 //
2 // ========================================================================
3 // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.webapp;
20
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Map;
27
28 import org.eclipse.jetty.util.resource.Resource;
29
30
31 /**
32 * Ordering
33 *
34 * Ordering options for jars in WEB-INF lib.
35 */
36 public interface Ordering
37 {
38
39 public List<Resource> order(List<Resource> fragments);
40 public boolean isAbsolute ();
41 public boolean hasOther();
42
43
44 /**
45 * AbsoluteOrdering
46 *
47 * An <absolute-order> element in web.xml
48 */
49 public static class AbsoluteOrdering implements Ordering
50 {
51 public static final String OTHER = "@@-OTHER-@@";
52 protected List<String> _order = new ArrayList<String>();
53 protected boolean _hasOther = false;
54 protected MetaData _metaData;
55
56 public AbsoluteOrdering (MetaData metaData)
57 {
58 _metaData = metaData;
59 }
60
61 /**
62 * Order the list of jars in WEB-INF/lib according to the ordering declarations in the descriptors
63 * @see org.eclipse.jetty.webapp.Ordering#order(java.util.List)
64 */
65 @Override
66 public List<Resource> order(List<Resource> jars)
67 {
68 List<Resource> orderedList = new ArrayList<Resource>();
69 List<Resource> tmp = new ArrayList<Resource>(jars);
70
71 //1. put everything into the list of named others, and take the named ones out of there,
72 //assuming we will want to use the <other> clause
73 Map<String,FragmentDescriptor> others = new HashMap<String,FragmentDescriptor>(_metaData.getNamedFragments());
74
75 //2. for each name, take out of the list of others, add to tail of list
76 int index = -1;
77 for (String item:_order)
78 {
79 if (!item.equals(OTHER))
80 {
81 FragmentDescriptor f = others.remove(item);
82 if (f != null)
83 {
84 Resource jar = _metaData.getJarForFragment(item);
85 orderedList.add(jar); //take from others and put into final list in order, ignoring duplicate names
86 //remove resource from list for resource matching name of descriptor
87 tmp.remove(jar);
88 }
89 }
90 else
91 index = orderedList.size(); //remember the index at which we want to add in all the others
92 }
93
94 //3. if <other> was specified, insert rest of the fragments
95 if (_hasOther)
96 {
97 orderedList.addAll((index < 0? 0: index), tmp);
98 }
99
100 return orderedList;
101 }
102
103 @Override
104 public boolean isAbsolute()
105 {
106 return true;
107 }
108
109 public void add (String name)
110 {
111 _order.add(name);
112 }
113
114 public void addOthers ()
115 {
116 if (_hasOther)
117 throw new IllegalStateException ("Duplicate <other> element in absolute ordering");
118
119 _hasOther = true;
120 _order.add(OTHER);
121 }
122
123 @Override
124 public boolean hasOther ()
125 {
126 return _hasOther;
127 }
128 }
129 /**
130 * RelativeOrdering
131 *
132 * A set of <order> elements in web-fragment.xmls.
133 */
134 public static class RelativeOrdering implements Ordering
135 {
136 protected MetaData _metaData;
137 protected LinkedList<Resource> _beforeOthers = new LinkedList<Resource>();
138 protected LinkedList<Resource> _afterOthers = new LinkedList<Resource>();
139 protected LinkedList<Resource> _noOthers = new LinkedList<Resource>();
140
141 public RelativeOrdering (MetaData metaData)
142 {
143 _metaData = metaData;
144 }
145 /**
146 * Order the list of jars according to the ordering declared
147 * in the various web-fragment.xml files.
148 * @see org.eclipse.jetty.webapp.Ordering#order(java.util.List)
149 */
150 @Override
151 public List<Resource> order(List<Resource> jars)
152 {
153 //for each jar, put it into the ordering according to the fragment ordering
154 for (Resource jar:jars)
155 {
156 //check if the jar has a fragment descriptor
157 FragmentDescriptor descriptor = _metaData.getFragment(jar);
158 if (descriptor != null)
159 {
160 switch (descriptor.getOtherType())
161 {
162 case None:
163 {
164 ((RelativeOrdering)_metaData.getOrdering()).addNoOthers(jar);
165 break;
166 }
167 case Before:
168 {
169 ((RelativeOrdering)_metaData.getOrdering()).addBeforeOthers(jar);
170 break;
171 }
172 case After:
173 {
174 ((RelativeOrdering)_metaData.getOrdering()).addAfterOthers(jar);
175 break;
176 }
177 }
178 }
179 else
180 {
181 //jar fragment has no descriptor, but there is a relative ordering in place, so it must be part of the others
182 ((RelativeOrdering)_metaData.getOrdering()).addNoOthers(jar);
183 }
184 }
185
186 //now apply the ordering
187 List<Resource> orderedList = new ArrayList<Resource>();
188 int maxIterations = 2;
189 boolean done = false;
190 do
191 {
192 //1. order the before-others according to any explicit before/after relationships
193 boolean changesBefore = orderList(_beforeOthers);
194
195 //2. order the after-others according to any explicit before/after relationships
196 boolean changesAfter = orderList(_afterOthers);
197
198 //3. order the no-others according to their explicit before/after relationships
199 boolean changesNone = orderList(_noOthers);
200
201 //we're finished on a clean pass through with no ordering changes
202 done = (!changesBefore && !changesAfter && !changesNone);
203 }
204 while (!done && (--maxIterations >0));
205
206 //4. merge before-others + no-others +after-others
207 if (!done)
208 throw new IllegalStateException("Circular references for fragments");
209
210 for (Resource r: _beforeOthers)
211 orderedList.add(r);
212 for (Resource r: _noOthers)
213 orderedList.add(r);
214 for(Resource r: _afterOthers)
215 orderedList.add(r);
216
217 return orderedList;
218 }
219
220 @Override
221 public boolean isAbsolute ()
222 {
223 return false;
224 }
225
226 @Override
227 public boolean hasOther ()
228 {
229 return !_beforeOthers.isEmpty() || !_afterOthers.isEmpty();
230 }
231
232 public void addBeforeOthers (Resource r)
233 {
234 _beforeOthers.addLast(r);
235 }
236
237 public void addAfterOthers (Resource r)
238 {
239 _afterOthers.addLast(r);
240 }
241
242 public void addNoOthers (Resource r)
243 {
244 _noOthers.addLast(r);
245 }
246
247 protected boolean orderList (LinkedList<Resource> list)
248 {
249 //Take a copy of the list so we can iterate over it and at the same time do random insertions
250 boolean changes = false;
251 List<Resource> iterable = new ArrayList<Resource>(list);
252 Iterator<Resource> itor = iterable.iterator();
253
254 while (itor.hasNext())
255 {
256 Resource r = itor.next();
257 FragmentDescriptor f = _metaData.getFragment(r);
258 if (f == null)
259 {
260 //no fragment for this resource so cannot have any ordering directives
261 continue;
262 }
263
264 //Handle any explicit <before> relationships for the fragment we're considering
265 List<String> befores = f.getBefores();
266 if (befores != null && !befores.isEmpty())
267 {
268 for (String b: befores)
269 {
270 //Fragment we're considering must be before b
271 //Check that we are already before it, if not, move us so that we are.
272 //If the name does not exist in our list, then get it out of the no-other list
273 if (!isBefore(list, f.getName(), b))
274 {
275 //b is not already before name, move it so that it is
276 int idx1 = getIndexOf(list, f.getName());
277 int idx2 = getIndexOf(list, b);
278
279 //if b is not in the same list
280 if (idx2 < 0)
281 {
282 changes = true;
283 // must be in the noOthers list or it would have been an error
284 Resource bResource = _metaData.getJarForFragment(b);
285 if (bResource != null)
286 {
287 //If its in the no-others list, insert into this list so that we are before it
288 if (_noOthers.remove(bResource))
289 {
290 insert(list, idx1+1, b);
291
292 }
293 }
294 }
295 else
296 {
297 //b is in the same list but b is before name, so swap it around
298 list.remove(idx1);
299 insert(list, idx2, f.getName());
300 changes = true;
301 }
302 }
303 }
304 }
305
306 //Handle any explicit <after> relationships
307 List<String> afters = f.getAfters();
308 if (afters != null && !afters.isEmpty())
309 {
310 for (String a: afters)
311 {
312 //Check that fragment we're considering is after a, moving it if possible if its not
313 if (!isAfter(list, f.getName(), a))
314 {
315 //name is not after a, move it
316 int idx1 = getIndexOf(list, f.getName());
317 int idx2 = getIndexOf(list, a);
318
319 //if a is not in the same list as name
320 if (idx2 < 0)
321 {
322 changes = true;
323 //take it out of the noOthers list and put it in the right place in this list
324 Resource aResource = _metaData.getJarForFragment(a);
325 if (aResource != null)
326 {
327 if (_noOthers.remove(aResource))
328 {
329 insert(list,idx1, aResource);
330 }
331 }
332 }
333 else
334 {
335 //a is in the same list as name, but in the wrong place, so move it
336 list.remove(idx2);
337 insert(list,idx1, a);
338 changes = true;
339 }
340 }
341 //Name we're considering must be after this name
342 //Check we're already after it, if not, move us so that we are.
343 //If the name does not exist in our list, then get it out of the no-other list
344 }
345 }
346 }
347
348 return changes;
349 }
350
351 /**
352 * Is fragment with name a before fragment with name b?
353 * @param list
354 * @param fragNameA
355 * @param fragNameB
356 * @return true if fragment name A is before fragment name B
357 */
358 protected boolean isBefore (List<Resource> list, String fragNameA, String fragNameB)
359 {
360 //check if a and b are already in the same list, and b is already
361 //before a
362 int idxa = getIndexOf(list, fragNameA);
363 int idxb = getIndexOf(list, fragNameB);
364
365
366 if (idxb >=0 && idxb < idxa)
367 {
368 //a and b are in the same list but a is not before b
369 return false;
370 }
371
372 if (idxb < 0)
373 {
374 //a and b are not in the same list, but it is still possible that a is before
375 //b, depending on which list we're examining
376 if (list == _beforeOthers)
377 {
378 //The list we're looking at is the beforeOthers.If b is in the _afterOthers or the _noOthers, then by
379 //definition a is before it
380 return true;
381 }
382 else if (list == _afterOthers)
383 {
384 //The list we're looking at is the afterOthers, then a will be the tail of
385 //the final list. If b is in the beforeOthers list, then b will be before a and an error.
386 if (_beforeOthers.contains(fragNameB))
387 throw new IllegalStateException("Incorrect relationship: "+fragNameA+" before "+fragNameB);
388 else
389 return false; //b could be moved to the list
390 }
391 }
392
393 //a and b are in the same list and a is already before b
394 return true;
395 }
396
397
398 /**
399 * Is fragment name "a" after fragment name "b"?
400 * @param list
401 * @param fragNameA
402 * @param fragNameB
403 * @return true if fragment name A is after fragment name B
404 */
405 protected boolean isAfter(List<Resource> list, String fragNameA, String fragNameB)
406 {
407 int idxa = getIndexOf(list, fragNameA);
408 int idxb = getIndexOf(list, fragNameB);
409
410 if (idxb >=0 && idxa < idxb)
411 {
412 //a and b are both in the same list, but a is before b
413 return false;
414 }
415
416 if (idxb < 0)
417 {
418 //a and b are in different lists. a could still be after b depending on which list it is in.
419
420 if (list == _afterOthers)
421 {
422 //The list we're looking at is the afterOthers. If b is in the beforeOthers or noOthers then
423 //by definition a is after b because a is in the afterOthers list.
424 return true;
425 }
426 else if (list == _beforeOthers)
427 {
428 //The list we're looking at is beforeOthers, and contains a and will be before
429 //everything else in the final ist. If b is in the afterOthers list, then a cannot be before b.
430 if (_afterOthers.contains(fragNameB))
431 throw new IllegalStateException("Incorrect relationship: "+fragNameB+" after "+fragNameA);
432 else
433 return false; //b could be moved from noOthers list
434 }
435 }
436
437 return true; //a and b in the same list, a is after b
438 }
439
440 /**
441 * Insert the resource matching the fragName into the list of resources
442 * at the location indicated by index.
443 *
444 * @param list
445 * @param index
446 * @param fragName
447 */
448 protected void insert(List<Resource> list, int index, String fragName)
449 {
450 Resource jar = _metaData.getJarForFragment(fragName);
451 if (jar == null)
452 throw new IllegalStateException("No jar for insertion");
453
454 insert(list, index, jar);
455 }
456
457 protected void insert(List<Resource> list, int index, Resource resource)
458 {
459 if (list == null)
460 throw new IllegalStateException("List is null for insertion");
461
462 //add it at the end
463 if (index > list.size())
464 list.add(resource);
465 else
466 list.add(index, resource);
467 }
468
469 protected void remove (List<Resource> resources, Resource r)
470 {
471 if (resources == null)
472 return;
473 resources.remove(r);
474 }
475
476 protected int getIndexOf(List<Resource> resources, String fragmentName)
477 {
478 FragmentDescriptor fd = _metaData.getFragment(fragmentName);
479 if (fd == null)
480 return -1;
481
482
483 Resource r = _metaData.getJarForFragment(fragmentName);
484 if (r == null)
485 return -1;
486
487 return resources.indexOf(r);
488 }
489 }
490
491 }