View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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.maven.plugin;
20  
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.InputStream;
25  import java.net.MalformedURLException;
26  import java.net.URL;
27  import java.net.URLClassLoader;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Enumeration;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Properties;
34  import java.util.Set;
35  
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.plugin.AbstractMojo;
38  import org.apache.maven.plugin.MojoExecutionException;
39  import org.apache.maven.plugin.MojoFailureException;
40  import org.apache.maven.project.MavenProject;
41  import org.codehaus.plexus.util.FileUtils;
42  import org.eclipse.jetty.security.LoginService;
43  import org.eclipse.jetty.server.Connector;
44  import org.eclipse.jetty.server.RequestLog;
45  import org.eclipse.jetty.server.ShutdownMonitor;
46  import org.eclipse.jetty.server.handler.ContextHandler;
47  import org.eclipse.jetty.server.handler.ContextHandlerCollection;
48  import org.eclipse.jetty.server.handler.HandlerCollection;
49  import org.eclipse.jetty.util.Scanner;
50  import org.eclipse.jetty.util.resource.Resource;
51  import org.eclipse.jetty.xml.XmlConfiguration;
52  
53  
54  
55  /**
56   * AbstractJettyMojo
57   *
58   * Common base class for most jetty mojos.
59   * 
60   * 
61   */
62  public abstract class AbstractJettyMojo extends AbstractMojo
63  {
64      /**
65       * 
66       */
67      public String PORT_SYSPROPERTY = "jetty.port";
68      
69      
70      /**
71       * Whether or not to include dependencies on the plugin's classpath with <scope>provided</scope>
72       * Use WITH CAUTION as you may wind up with duplicate jars/classes.
73       * 
74       * @since jetty-7.5.2
75       * @parameter  default-value="false"
76       */
77      protected boolean useProvidedScope;
78      
79      
80      /**
81       * List of goals that are NOT to be used
82       * 
83       * @since jetty-7.5.2
84       * @parameter
85       */
86      protected String[] excludedGoals;
87      
88  
89    
90      
91      /**
92       * List of other contexts to set up. Consider using instead
93       * the <jettyXml> element to specify external jetty xml config file. 
94       * Optional.
95       * 
96       * 
97       * @parameter
98       */
99      protected ContextHandler[] contextHandlers;
100     
101     
102     /**
103      * List of security realms to set up. Consider using instead
104      * the <jettyXml> element to specify external jetty xml config file. 
105      * Optional.
106      * 
107      * 
108      * @parameter
109      */
110     protected LoginService[] loginServices;
111     
112 
113     /**
114      * A RequestLog implementation to use for the webapp at runtime.
115      * Consider using instead the <jettyXml> element to specify external jetty xml config file. 
116      * Optional.
117      * 
118      *
119      * @parameter
120      */
121     protected RequestLog requestLog;
122     
123     
124     /**
125      * An instance of org.eclipse.jetty.webapp.WebAppContext that represents the webapp.
126      * Use any of its setters to configure the webapp. This is the preferred and most
127      * flexible method of configuration, rather than using the (deprecated) individual
128      * parameters like "tmpDirectory", "contextPath" etc.
129      * 
130      * @parameter alias="webAppConfig"
131      */
132     protected JettyWebAppContext webApp;
133 
134 
135     /**
136      * The interval in seconds to scan the webapp for changes 
137      * and restart the context if necessary. Ignored if reload
138      * is enabled. Disabled by default.
139      * 
140      * @parameter expression="${jetty.scanIntervalSeconds}" default-value="0"
141      * @required
142      */
143     protected int scanIntervalSeconds;
144     
145     
146     /**
147      * reload can be set to either 'automatic' or 'manual'
148      *
149      * if 'manual' then the context can be reloaded by a linefeed in the console
150      * if 'automatic' then traditional reloading on changed files is enabled.
151      * 
152      * @parameter expression="${jetty.reload}" default-value="automatic"
153      */
154     protected String reload;
155 
156     
157     /**
158      * File containing system properties to be set before execution
159      * 
160      * Note that these properties will NOT override System properties
161      * that have been set on the command line, by the JVM, or directly 
162      * in the POM via systemProperties. Optional.
163      * 
164      * @parameter expression="${jetty.systemPropertiesFile}"
165      */
166     protected File systemPropertiesFile;
167 
168     
169     /**
170      * System properties to set before execution. 
171      * Note that these properties will NOT override System properties 
172      * that have been set on the command line or by the JVM. They WILL 
173      * override System properties that have been set via systemPropertiesFile.
174      * Optional.
175      * @parameter
176      */
177     protected SystemProperties systemProperties;
178     
179     
180     /**
181      * Comma separated list of a jetty xml configuration files whose contents 
182      * will be applied before any plugin configuration. Optional.
183      * 
184      * 
185      * @parameter alias="jettyConfig"
186      */
187     protected String jettyXml;
188     
189     
190     /**
191      * Port to listen to stop jetty on executing -DSTOP.PORT=<stopPort> 
192      * -DSTOP.KEY=<stopKey> -jar start.jar --stop
193      * 
194      * @parameter
195      */
196     protected int stopPort;
197     
198     
199     /**
200      * Key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey> 
201      * -DSTOP.PORT=<stopPort> -jar start.jar --stop
202      * 
203      * @parameter
204      */
205     protected String stopKey;
206 
207     /**
208      * Use the dump() facility of jetty to print out the server configuration to logging
209      * 
210      * @parameter expression"${dumponStart}" default-value="false"
211      */
212     protected boolean dumpOnStart;
213     
214     /**
215      * <p>
216      * Determines whether or not the server blocks when started. The default
217      * behavior (daemon = false) will cause the server to pause other processes
218      * while it continues to handle web requests. This is useful when starting the
219      * server with the intent to work with it interactively.
220      * </p><p>
221      * Often, it is desirable to let the server start and continue running subsequent
222      * processes in an automated build environment. This can be facilitated by setting
223      * daemon to true.
224      * </p>
225      * 
226      * @parameter expression="${jetty.daemon}" default-value="false"
227      */
228     protected boolean daemon;
229     
230     
231     /**  
232      * Skip this mojo execution.
233      * 
234      * @parameter expression="${jetty.skip}" default-value="false"
235      */
236     protected boolean skip;
237 
238     
239     /**
240      * Location of a context xml configuration file whose contents
241      * will be applied to the webapp AFTER anything in &lt;webApp&gt;.Optional.
242      * 
243      * 
244      * @parameter alias="webAppXml"
245      */
246     protected String contextXml;
247 
248 
249     /**
250      * The maven project.
251      *
252      * @parameter expression="${project}"
253      * @readonly
254      */
255     protected MavenProject project;
256 
257     
258     /**
259      * The artifacts for the project.
260      * 
261      * @parameter expression="${project.artifacts}"
262      * @readonly
263      */
264     protected Set projectArtifacts;
265     
266     
267     /** 
268      * @parameter expression="${mojoExecution}" 
269      * @readonly
270      */
271     protected org.apache.maven.plugin.MojoExecution execution;
272     
273 
274     /**
275      * The artifacts for the plugin itself.
276      * 
277      * @parameter expression="${plugin.artifacts}"
278      * @readonly
279      */
280     protected List pluginArtifacts;
281     
282     
283 
284     /**
285      * A ServerConnector to use.
286      * 
287      * @parameter
288      */
289     protected MavenServerConnector httpConnector;
290     
291     
292     /**
293      * A wrapper for the Server object
294      */
295     protected JettyServer server = new JettyServer();
296     
297     
298     /**
299      * A scanner to check for changes to the webapp
300      */
301     protected Scanner scanner;
302     
303     
304     /**
305      *  List of files and directories to scan
306      */
307     protected ArrayList<File> scanList;
308     
309     
310     /**
311      * List of Listeners for the scanner
312      */
313     protected ArrayList<Scanner.BulkListener> scannerListeners;
314     
315     
316     /**
317      * A scanner to check ENTER hits on the console
318      */
319     protected Thread consoleScanner;
320     
321     
322     
323     
324     
325     
326     public abstract void restartWebApp(boolean reconfigureScanner) throws Exception;
327 
328     
329     public abstract void checkPomConfiguration() throws MojoExecutionException;    
330     
331     
332     public abstract void configureScanner () throws MojoExecutionException;
333     
334 
335     
336 
337 
338     /** 
339      * @see org.apache.maven.plugin.Mojo#execute()
340      */
341     public void execute() throws MojoExecutionException, MojoFailureException
342     {
343         getLog().info("Configuring Jetty for project: " + this.project.getName());
344         if (skip)
345         {
346             getLog().info("Skipping Jetty start: jetty.skip==true");
347             return;
348         }
349 
350         if (isExcluded(execution.getMojoDescriptor().getGoal()))
351         {
352             getLog().info("The goal \""+execution.getMojoDescriptor().getFullGoalName()+
353                           "\" has been made unavailable for this web application by an <excludedGoal> configuration.");
354             return;
355         }
356         
357         configurePluginClasspath();
358         PluginLog.setLog(getLog());
359         checkPomConfiguration();
360         startJetty();
361     }
362     
363     
364     
365     
366     /**
367      * @throws MojoExecutionException
368      */
369     public void configurePluginClasspath() throws MojoExecutionException
370     {  
371         //if we are configured to include the provided dependencies on the plugin's classpath
372         //(which mimics being on jetty's classpath vs being on the webapp's classpath), we first
373         //try and filter out ones that will clash with jars that are plugin dependencies, then
374         //create a new classloader that we setup in the parent chain.
375         if (useProvidedScope)
376         {
377             try
378             {
379                 List<URL> provided = new ArrayList<URL>();
380                 URL[] urls = null;
381                
382                 for ( Iterator<Artifact> iter = projectArtifacts.iterator(); iter.hasNext(); )
383                 {                   
384                     Artifact artifact = iter.next();
385                     if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !isPluginArtifact(artifact))
386                     {
387                         provided.add(artifact.getFile().toURI().toURL());
388                         if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
389                     }
390                 }
391 
392                 if (!provided.isEmpty())
393                 {
394                     urls = new URL[provided.size()];
395                     provided.toArray(urls);
396                     URLClassLoader loader  = new URLClassLoader(urls, getClass().getClassLoader());
397                     Thread.currentThread().setContextClassLoader(loader);
398                     getLog().info("Plugin classpath augmented with <scope>provided</scope> dependencies: "+Arrays.toString(urls));
399                 }
400             }
401             catch (MalformedURLException e)
402             {
403                 throw new MojoExecutionException("Invalid url", e);
404             }
405         }
406     }
407     
408     
409     
410     
411     /**
412      * @param artifact
413      * @return
414      */
415     public boolean isPluginArtifact(Artifact artifact)
416     {
417         if (pluginArtifacts == null || pluginArtifacts.isEmpty())
418             return false;
419         
420         boolean isPluginArtifact = false;
421         for (Iterator<Artifact> iter = pluginArtifacts.iterator(); iter.hasNext() && !isPluginArtifact; )
422         {
423             Artifact pluginArtifact = iter.next();
424             if (getLog().isDebugEnabled()) { getLog().debug("Checking "+pluginArtifact);}
425             if (pluginArtifact.getGroupId().equals(artifact.getGroupId()) && pluginArtifact.getArtifactId().equals(artifact.getArtifactId()))
426                 isPluginArtifact = true;
427         }
428         
429         return isPluginArtifact;
430     }
431 
432     
433     
434     
435     /**
436      * @throws Exception
437      */
438     public void finishConfigurationBeforeStart() throws Exception
439     {
440         HandlerCollection contexts = (HandlerCollection)server.getChildHandlerByClass(ContextHandlerCollection.class);
441         if (contexts==null)
442             contexts = (HandlerCollection)server.getChildHandlerByClass(HandlerCollection.class);
443         
444         for (int i=0; (this.contextHandlers != null) && (i < this.contextHandlers.length); i++)
445         {
446             contexts.addHandler(this.contextHandlers[i]);
447         }
448     }
449 
450    
451    
452     
453     /**
454      * @throws Exception
455      */
456     public void applyJettyXml() throws Exception
457     {
458         if (getJettyXmlFiles() == null)
459             return;
460 
461         XmlConfiguration last = null;
462         for ( File xmlFile : getJettyXmlFiles() )
463         {
464             getLog().info( "Configuring Jetty from xml configuration file = " + xmlFile.getCanonicalPath() );        
465             XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(xmlFile));
466             
467             //chain ids from one config file to another
468             if (last == null)
469                 xmlConfiguration.getIdMap().put("Server", this.server); 
470             else
471                 xmlConfiguration.getIdMap().putAll(last.getIdMap());
472             
473             //Set the system properties each time in case the config file set a new one
474             Enumeration<?> ensysprop = System.getProperties().propertyNames();
475             while (ensysprop.hasMoreElements())
476             {
477                 String name = (String)ensysprop.nextElement();
478                 xmlConfiguration.getProperties().put(name,System.getProperty(name));
479             }
480             last = xmlConfiguration;
481             xmlConfiguration.configure(); 
482         }
483     }
484 
485 
486 
487     
488     /**
489      * @throws MojoExecutionException
490      */
491     public void startJetty () throws MojoExecutionException
492     {
493         try
494         {
495             getLog().debug("Starting Jetty Server ...");
496             
497             if(stopPort>0 && stopKey!=null)
498             {
499                 ShutdownMonitor monitor = ShutdownMonitor.getInstance();
500                 monitor.setPort(stopPort);
501                 monitor.setKey(stopKey);
502                 monitor.setExitVm(!daemon);
503             }
504             
505             printSystemProperties();
506             
507             //apply any config from a jetty.xml file first which is able to
508             //be overwritten by config in the pom.xml
509             applyJettyXml ();      
510 
511             // if a <httpConnector> was specified in the pom, use it
512             if (httpConnector != null)
513             {
514                 // check that its port was set
515                 if (httpConnector.getPort() <= 0)
516                 {
517                     //use any jetty.port settings provided
518                     String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR); 
519                     httpConnector.setPort(Integer.parseInt(tmp.trim()));
520                 }  
521                 if (httpConnector.getServer() == null)
522                     httpConnector.setServer(this.server);
523                 this.server.addConnector(httpConnector);
524             }
525 
526             // if the user hasn't configured the connectors in a jetty.xml file so use a default one
527             Connector[] connectors = this.server.getConnectors();
528             if (connectors == null|| connectors.length == 0)
529             {
530                 //if <httpConnector> not configured in the pom, create one
531                 if (httpConnector == null)
532                 {
533                     httpConnector = new MavenServerConnector();               
534                     //use any jetty.port settings provided
535                     String tmp = System.getProperty(PORT_SYSPROPERTY, MavenServerConnector.DEFAULT_PORT_STR);
536                     httpConnector.setPort(Integer.parseInt(tmp.trim()));
537                 }
538                 if (httpConnector.getServer() == null)
539                     httpConnector.setServer(this.server);
540                 this.server.setConnectors(new Connector[] {httpConnector});
541             }
542 
543             //set up a RequestLog if one is provided
544             if (this.requestLog != null)
545                 this.server.setRequestLog(this.requestLog);
546 
547             //set up the webapp and any context provided
548             this.server.configureHandlers();
549             configureWebApplication();
550             this.server.addWebApplication(webApp);
551 
552             // set up security realms
553             for (int i = 0; (this.loginServices != null) && i < this.loginServices.length; i++)
554             {
555                 getLog().debug(this.loginServices[i].getClass().getName() + ": "+ this.loginServices[i].toString());
556                 this.server.addBean(this.loginServices[i]);
557             }
558 
559             //do any other configuration required by the
560             //particular Jetty version
561             finishConfigurationBeforeStart();
562 
563             // start Jetty
564             this.server.start();
565 
566             getLog().info("Started Jetty Server");
567            
568             
569             if ( dumpOnStart )
570             {
571                 getLog().info(this.server.dump());
572             }
573             
574             // start the scanner thread (if necessary) on the main webapp
575             configureScanner ();
576             startScanner();
577             
578             // start the new line scanner thread if necessary
579             startConsoleScanner();
580 
581             // keep the thread going if not in daemon mode
582             if (!daemon )
583             {
584                 server.join();
585             }
586         }
587         catch (Exception e)
588         {
589             throw new MojoExecutionException("Failure", e);
590         }
591         finally
592         {
593             if (!daemon )
594             {
595                 getLog().info("Jetty server exiting.");
596             }            
597         }        
598     }
599     
600     
601 
602     
603     /**
604      * Subclasses should invoke this to setup basic info
605      * on the webapp
606      * 
607      * @throws MojoExecutionException
608      */
609     public void configureWebApplication () throws Exception
610     {
611         //As of jetty-7, you must use a <webApp> element
612         if (webApp == null)
613             webApp = new JettyWebAppContext();
614         
615         //Apply any context xml file to set up the webapp
616         //CAUTION: if you've defined a <webApp> element then the
617         //context xml file can OVERRIDE those settings
618         if (contextXml != null)
619         {
620             File file = FileUtils.getFile(contextXml);
621             XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(file));
622             getLog().info("Applying context xml file "+contextXml);
623             xmlConfiguration.configure(webApp);   
624         }
625         
626         //If no contextPath was specified, go with default of project artifactid
627         String cp = webApp.getContextPath();
628         if (cp == null || "".equals(cp))
629         {
630             cp = "/"+project.getArtifactId();
631             webApp.setContextPath(cp);
632         }        
633 
634         //If no tmp directory was specified, and we have one, use it
635         if (webApp.getTempDirectory() == null)
636         {
637             File target = new File(project.getBuild().getDirectory());
638             File tmp = new File(target,"tmp");
639             if (!tmp.exists())
640                 tmp.mkdirs();            
641             webApp.setTempDirectory(tmp);
642         }
643       
644         getLog().info("Context path = " + webApp.getContextPath());
645         getLog().info("Tmp directory = "+ (webApp.getTempDirectory()== null? " determined at runtime": webApp.getTempDirectory()));
646         getLog().info("Web defaults = "+(webApp.getDefaultsDescriptor()==null?" jetty default":webApp.getDefaultsDescriptor()));
647         getLog().info("Web overrides = "+(webApp.getOverrideDescriptor()==null?" none":webApp.getOverrideDescriptor()));
648     }
649 
650 
651 
652     
653     /**
654      * Run a scanner thread on the given list of files and directories, calling
655      * stop/start on the given list of LifeCycle objects if any of the watched
656      * files change.
657      *
658      */
659     private void startScanner() throws Exception
660     {
661         // check if scanning is enabled
662         if (scanIntervalSeconds <= 0) return;
663 
664         // check if reload is manual. It disables file scanning
665         if ( "manual".equalsIgnoreCase( reload ) )
666         {
667             // issue a warning if both scanIntervalSeconds and reload
668             // are enabled
669             getLog().warn("scanIntervalSeconds is set to " + scanIntervalSeconds + " but will be IGNORED due to manual reloading");
670             return;
671         }
672 
673         scanner = new Scanner();
674         scanner.setReportExistingFilesOnStartup(false);
675         scanner.setScanInterval(scanIntervalSeconds);
676         scanner.setScanDirs(scanList);
677         scanner.setRecursive(true);
678         Iterator itor = (this.scannerListeners==null?null:this.scannerListeners.iterator());
679         while (itor!=null && itor.hasNext())
680             scanner.addListener((Scanner.Listener)itor.next());
681         getLog().info("Starting scanner at interval of " + scanIntervalSeconds + " seconds.");
682         scanner.start();
683     }
684     
685     
686     
687     
688     /**
689      * Run a thread that monitors the console input to detect ENTER hits.
690      */
691     protected void startConsoleScanner() throws Exception
692     {
693         if ( "manual".equalsIgnoreCase( reload ) )
694         {
695             getLog().info("Console reloading is ENABLED. Hit ENTER on the console to restart the context.");
696             consoleScanner = new ConsoleScanner(this);
697             consoleScanner.start();
698         }       
699     }
700 
701     
702     
703     
704     /**
705      * 
706      */
707     private void printSystemProperties ()
708     {
709         // print out which system properties were set up
710         if (getLog().isDebugEnabled())
711         {
712             if (systemProperties != null)
713             {
714                 Iterator itor = systemProperties.getSystemProperties().iterator();
715                 while (itor.hasNext())
716                 {
717                     SystemProperty prop = (SystemProperty)itor.next();
718                     getLog().debug("Property "+prop.getName()+"="+prop.getValue()+" was "+ (prop.isSet() ? "set" : "skipped"));
719                 }
720             }
721         }
722     }
723 
724     
725     
726     
727     /**
728      * Try and find a jetty-web.xml file, using some
729      * historical naming conventions if necessary.
730      * @param webInfDir
731      * @return the jetty web xml file
732      */
733     public File findJettyWebXmlFile (File webInfDir)
734     {
735         if (webInfDir == null)
736             return null;
737         if (!webInfDir.exists())
738             return null;
739 
740         File f = new File (webInfDir, "jetty-web.xml");
741         if (f.exists())
742             return f;
743 
744         //try some historical alternatives
745         f = new File (webInfDir, "web-jetty.xml");
746         if (f.exists())
747             return f;
748         
749         return null;
750     }
751 
752 
753    
754     
755     /**
756      * @param file
757      * @throws Exception
758      */
759     public void setSystemPropertiesFile(File file) throws Exception
760     {
761         this.systemPropertiesFile = file;
762         Properties properties = new Properties();
763         try (InputStream propFile = new FileInputStream(systemPropertiesFile))
764         {
765             properties.load(propFile);
766         }
767         if (this.systemProperties == null )
768             this.systemProperties = new SystemProperties();
769         
770         for (Enumeration<?> keys = properties.keys(); keys.hasMoreElements();  )
771         {
772             String key = (String)keys.nextElement();
773             if ( ! systemProperties.containsSystemProperty(key) )
774             {
775                 SystemProperty prop = new SystemProperty();
776                 prop.setKey(key);
777                 prop.setValue(properties.getProperty(key));
778                 
779                 this.systemProperties.setSystemProperty(prop);
780             }
781         } 
782     }
783     
784     
785     
786     
787     /**
788      * @param systemProperties
789      */
790     public void setSystemProperties(SystemProperties systemProperties)
791     {
792         if (this.systemProperties == null)
793             this.systemProperties = systemProperties;
794         else
795         {
796             for (SystemProperty prop: systemProperties.getSystemProperties())
797             {
798                 this.systemProperties.setSystemProperty(prop);
799             }   
800         }
801     }
802     
803 
804     
805 
806     
807     
808     
809     /**
810      * @return
811      */
812     public List<File> getJettyXmlFiles()
813     {
814         if ( this.jettyXml == null )
815         {
816             return null;
817         }
818         
819         List<File> jettyXmlFiles = new ArrayList<File>();
820         
821         if ( this.jettyXml.indexOf(',') == -1 )
822         {
823             jettyXmlFiles.add( new File( this.jettyXml ) );
824         }
825         else
826         {
827             String[] files = this.jettyXml.split(",");
828             
829             for ( String file : files )
830             {
831                 jettyXmlFiles.add( new File(file) );
832             }
833         }
834         
835         return jettyXmlFiles;
836     }
837 
838     
839     
840     /**
841      * @param goal
842      * @return
843      */
844     public boolean isExcluded (String goal)
845     {
846         if (excludedGoals == null || goal == null)
847             return false;
848         
849         goal = goal.trim();
850         if ("".equals(goal))
851             return false;
852         
853         boolean excluded = false;
854         for (int i=0; i<excludedGoals.length && !excluded; i++)
855         {
856             if (excludedGoals[i].equalsIgnoreCase(goal))
857                 excluded = true;
858         }
859         
860         return excluded;
861     }
862 }