1//
2//  ========================================================================
3//  Copyright (c) 1995-2014 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
19package org.eclipse.jetty.webapp;
20
21import java.util.ArrayList;
22import java.util.HashMap;
23import java.util.Iterator;
24import java.util.LinkedList;
25import java.util.List;
26import java.util.Map;
27
28import org.eclipse.jetty.util.resource.Resource;
29
30
31/**
32 * Ordering
33 *
34 * Ordering options for jars in WEB-INF lib.
35 */
36public 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 &lt;absolute-order&gt; 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 &lt;order&gt; 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}
492