001/*
002 * Copyright 2011-2012 Stephen Connolly.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jszip.maven;
018
019import org.apache.maven.ProjectDependenciesResolver;
020import org.apache.maven.artifact.Artifact;
021import org.apache.maven.artifact.ArtifactUtils;
022import org.apache.maven.artifact.DependencyResolutionRequiredException;
023import org.apache.maven.artifact.repository.ArtifactRepository;
024import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
025import org.apache.maven.artifact.resolver.ArtifactResolutionException;
026import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
027import org.apache.maven.execution.MavenSession;
028import org.apache.maven.model.Plugin;
029import org.apache.maven.model.PluginExecution;
030import org.apache.maven.model.building.ModelBuildingRequest;
031import org.apache.maven.plugin.MavenPluginManager;
032import org.apache.maven.plugin.Mojo;
033import org.apache.maven.plugin.MojoExecution;
034import org.apache.maven.plugin.MojoExecutionException;
035import org.apache.maven.plugin.MojoFailureException;
036import org.apache.maven.plugin.PluginConfigurationException;
037import org.apache.maven.plugin.PluginContainerException;
038import org.apache.maven.plugin.descriptor.MojoDescriptor;
039import org.apache.maven.plugin.descriptor.PluginDescriptor;
040import org.apache.maven.plugins.annotations.Component;
041import org.apache.maven.plugins.annotations.LifecyclePhase;
042import org.apache.maven.plugins.annotations.Parameter;
043import org.apache.maven.plugins.annotations.ResolutionScope;
044import org.apache.maven.project.DefaultProjectBuildingRequest;
045import org.apache.maven.project.DuplicateProjectException;
046import org.apache.maven.project.MavenProject;
047import org.apache.maven.project.MavenProjectHelper;
048import org.apache.maven.project.ProjectBuilder;
049import org.apache.maven.project.ProjectBuildingException;
050import org.apache.maven.project.ProjectBuildingRequest;
051import org.apache.maven.project.ProjectSorter;
052import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
053import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
054import org.apache.maven.shared.artifact.filter.collection.ProjectTransitivityFilter;
055import org.apache.maven.shared.artifact.filter.collection.ScopeFilter;
056import org.apache.maven.shared.artifact.filter.collection.TypeFilter;
057import org.apache.maven.shared.filtering.MavenFilteringException;
058import org.apache.maven.shared.filtering.MavenResourcesExecution;
059import org.apache.maven.shared.filtering.MavenResourcesFiltering;
060import org.apache.maven.shared.invoker.DefaultInvocationRequest;
061import org.apache.maven.shared.invoker.DefaultInvoker;
062import org.apache.maven.shared.invoker.InvocationRequest;
063import org.apache.maven.shared.invoker.Invoker;
064import org.apache.maven.shared.invoker.InvokerLogger;
065import org.apache.maven.shared.invoker.MavenInvocationException;
066import org.codehaus.plexus.util.FileUtils;
067import org.codehaus.plexus.util.IOUtil;
068import org.codehaus.plexus.util.StringUtils;
069import org.codehaus.plexus.util.dag.CycleDetectedException;
070import org.eclipse.jetty.server.Connector;
071import org.eclipse.jetty.server.Handler;
072import org.eclipse.jetty.server.Server;
073import org.eclipse.jetty.server.handler.ContextHandlerCollection;
074import org.eclipse.jetty.server.handler.DefaultHandler;
075import org.eclipse.jetty.server.handler.HandlerCollection;
076import org.eclipse.jetty.server.nio.SelectChannelConnector;
077import org.eclipse.jetty.util.resource.Resource;
078import org.eclipse.jetty.util.resource.ResourceCollection;
079import org.eclipse.jetty.webapp.WebAppClassLoader;
080import org.eclipse.jetty.webapp.WebAppContext;
081import org.jszip.css.CssEngine;
082import org.jszip.jetty.CssEngineResource;
083import org.jszip.jetty.JettyWebAppContext;
084import org.jszip.jetty.SystemProperties;
085import org.jszip.jetty.SystemProperty;
086import org.jszip.jetty.VirtualDirectoryResource;
087import org.jszip.less.LessEngine;
088import org.jszip.pseudo.io.PseudoDirectoryScanner;
089import org.jszip.pseudo.io.PseudoFile;
090import org.jszip.pseudo.io.PseudoFileOutputStream;
091import org.jszip.pseudo.io.PseudoFileSystem;
092import org.jszip.sass.SassEngine;
093
094import java.io.File;
095import java.io.IOException;
096import java.net.MalformedURLException;
097import java.util.ArrayList;
098import java.util.Arrays;
099import java.util.Collections;
100import java.util.HashSet;
101import java.util.Iterator;
102import java.util.List;
103import java.util.Set;
104import java.util.Stack;
105import java.util.concurrent.TimeUnit;
106
107/**
108 * Starts a Jetty servlet container with resources resolved from the reactor projects to enable live editing of those
109 * resources and pom and classpath scanning to restart the servlet container when the classpath is modified. Note that
110 * if the poms are modified in such a way that the reactor build plan is modified, we have no choice but to stop the
111 * servlet container and require the maven session to be restarted, but best effort is made to ensure that restart
112 * is only when required.
113 */
114@org.apache.maven.plugins.annotations.Mojo(name = "run",
115        defaultPhase = LifecyclePhase.TEST_COMPILE,
116        requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
117public class RunMojo extends AbstractJSZipMojo {
118    /**
119     * The artifact path mappings for unpacking.
120     */
121    @Parameter(property = "mappings")
122    private Mapping[] mappings;
123
124    /**
125     * If true, the <testOutputDirectory>
126     * and the dependencies of <scope>test<scope>
127     * will be put first on the runtime classpath.
128     */
129    @Parameter(alias = "useTextClasspath", defaultValue = "false")
130    private boolean useTestScope;
131
132
133    /**
134     * The default location of the web.xml file. Will be used
135     * if <webApp><descriptor> is not set.
136     */
137    @Parameter(property = "maven.war.webxml", readonly = true)
138    private String webXml;
139
140
141    /**
142     * The directory containing generated classes.
143     */
144    @Parameter(property = "project.build.outputDirectory", required = true)
145    private File classesDirectory;
146
147
148    /**
149     * The directory containing generated test classes.
150     */
151    @Parameter(property = "project.build.testOutputDirectory", required = true)
152    private File testClassesDirectory;
153
154    /**
155     * Root directory for all html/jsp etc files
156     */
157    @Parameter(defaultValue = "${basedir}/src/main/webapp", required = true)
158    private File warSourceDirectory = null;
159
160
161    /**
162     * List of connectors to use. If none are configured
163     * then the default is a single SelectChannelConnector at port 8080. You can
164     * override this default port number by using the system property jetty.port
165     * on the command line, eg:  mvn -Djetty.port=9999 jszip:run. Consider using instead
166     * the <jettyXml> element to specify external jetty xml config file.
167     */
168    @Parameter
169    protected Connector[] connectors;
170
171    /**
172     * The module that the goal should apply to. Specify either groupId:artifactId or just plain artifactId.
173     */
174    @Parameter(property = "jszip.run.module")
175    private String runModule;
176
177    /**
178     * List of the packaging types will be considered for executing this goal. Normally you do not
179     * need to configure this parameter unless you have a custom war packaging type. Defaults to <code>war</code>
180     */
181    @Parameter
182    private String[] runPackages;
183
184    /**
185     * System properties to set before execution.
186     * Note that these properties will NOT override System properties
187     * that have been set on the command line or by the JVM. They WILL
188     * override System properties that have been set via systemPropertiesFile.
189     * Optional.
190     */
191    @Parameter
192    private SystemProperties systemProperties;
193
194    /**
195     * The project builder
196     */
197    @Component
198    private ProjectBuilder projectBuilder;
199
200    /**
201     * The reactor project
202     */
203    @Parameter(property = "reactorProjects", required = true, readonly = true)
204    protected List<MavenProject> reactorProjects;
205
206    /**
207     * Location of the local repository.
208     */
209    @Parameter(property = "localRepository", required = true, readonly = true)
210    private ArtifactRepository localRepository;
211
212    /**
213     * The current build session instance. This is used for plugin manager API calls.
214     */
215    @Parameter(property = "session", required = true, readonly = true)
216    private MavenSession session;
217
218    /**
219     * The forked project.
220     */
221    @Parameter(property = "executedProject", required = true, readonly = true)
222    private MavenProject executedProject;
223
224    /**
225     * Directory containing the less processor.
226     */
227    @Parameter(defaultValue = "src/build/js/less-rhino.js")
228    private File customLessScript;
229
230    /**
231     * Skip compilation.
232     */
233    @Parameter(property = "jszip.less.skip", defaultValue = "false")
234    private boolean lessSkip;
235
236    /**
237     * Force compilation even if the source LESS file is older than the destination CSS file.
238     */
239    @Parameter(property = "jszip.less.forceIfOlder", defaultValue = "false")
240    private boolean lessForceIfOlder;
241
242    /**
243     * Compress CSS.
244     */
245    @Parameter(property = "jszip.less.compress", defaultValue = "true")
246    private boolean lessCompress;
247
248    /**
249     * Indicates whether the build will continue even if there are compilation errors.
250     */
251    @Parameter(property = "jszip.less.failOnError", defaultValue = "true")
252    private boolean lessFailOnError;
253
254    /**
255     * Indicates whether to show extracts of the code where errors occur.
256     */
257    @Parameter(property = "jszip.less.showErrorExtracts", defaultValue = "false")
258    private boolean showErrorExtracts;
259
260    /**
261     * A list of &lt;include&gt; elements specifying the less files (by pattern) that should be included in
262     * processing.
263     */
264    @Parameter
265    private List<String> lessIncludes;
266
267    /**
268     * A list of &lt;exclude&gt; elements specifying the less files (by pattern) that should be excluded from
269     * processing.
270     */
271    @Parameter
272    private List<String> lessExcludes;
273
274    /**
275     * Skip compilation.
276     */
277    @Parameter(property = "jszip.sass.skip", defaultValue = "false")
278    private boolean sassSkip;
279
280    /**
281     * Force compilation even if the source Sass file is older than the destination CSS file.
282     */
283    @Parameter(property = "jszip.sass.forceIfOlder", defaultValue = "false")
284    private boolean sassForceIfOlder;
285
286    /**
287     * Indicates whether the build will continue even if there are compilation errors.
288     */
289    @Parameter(property = "jszip.sass.failOnError", defaultValue = "true")
290    private boolean sassFailOnError;
291
292    /**
293     * A list of &lt;include&gt; elements specifying the sass files (by pattern) that should be included in
294     * processing.
295     */
296    @Parameter
297    private List<String> sassIncludes;
298
299    /**
300     * A list of &lt;exclude&gt; elements specifying the sass files (by pattern) that should be excluded from
301     * processing.
302     */
303    @Parameter
304    private List<String> sassExcludes;
305
306    /**
307     * The character encoding scheme to be applied when reading SASS files.
308     */
309    @Parameter( defaultValue = "${project.build.sourceEncoding}" )
310    private String encoding;
311
312    /**
313     * Used to resolve transitive dependencies.
314     */
315    @Component
316    private ProjectDependenciesResolver projectDependenciesResolver;
317
318    /**
319     * Maven ProjectHelper.
320     */
321    @Component
322    private MavenProjectHelper projectHelper;
323
324    /**
325     * The Maven plugin Manager
326     */
327    @Component
328    private MavenPluginManager mavenPluginManager;
329
330    /**
331     * This plugin's descriptor
332     */
333    @Parameter(property = "plugin")
334    private PluginDescriptor pluginDescriptor;
335
336    /**
337     * Our resource filterer
338     */
339    @Component(role = org.apache.maven.shared.filtering.MavenResourcesFiltering.class, hint = "default")
340    protected MavenResourcesFiltering mavenResourcesFiltering;
341
342    private final String scope = "test";
343    private final long classpathCheckInterval = TimeUnit.SECONDS.toMillis(10);
344
345    public void execute()
346            throws MojoExecutionException, MojoFailureException {
347        if (runPackages == null || runPackages.length == 0) {
348            runPackages = new String[]{"war"};
349        }
350
351        injectMissingArtifacts(project, executedProject);
352
353        if (!Arrays.asList(runPackages).contains(project.getPackaging())) {
354            getLog().info("Skipping JSZip run: module " + ArtifactUtils.versionlessKey(project.getGroupId(),
355                    project.getArtifactId()) + " as not specified in runPackages");
356            return;
357        }
358        if (StringUtils.isNotBlank(runModule)
359                && !project.getArtifactId().equals(runModule)
360                && !ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId()).equals(runModule)) {
361            getLog().info("Skipping JSZip run: module " + ArtifactUtils.versionlessKey(project.getGroupId(),
362                    project.getArtifactId()) + " as requested runModule is " + runModule);
363            return;
364        }
365        getLog().info("Starting JSZip run: module " + ArtifactUtils.versionlessKey(project.getGroupId(),
366                project.getArtifactId()));
367        MavenProject project = this.project;
368        long lastResourceChange = System.currentTimeMillis();
369        long lastClassChange = System.currentTimeMillis();
370        long lastPomChange = getPomsLastModified();
371
372        Server server = new Server();
373        if (connectors == null || connectors.length == 0) {
374            SelectChannelConnector selectChannelConnector = new SelectChannelConnector();
375            selectChannelConnector.setPort(8080);
376            connectors = new Connector[]{
377                    selectChannelConnector
378            };
379        }
380        server.setConnectors(connectors);
381        ContextHandlerCollection contexts = new ContextHandlerCollection();
382        HandlerCollection handlerCollection = new HandlerCollection(true);
383        DefaultHandler defaultHandler = new DefaultHandler();
384        handlerCollection.setHandlers(new Handler[]{contexts, defaultHandler});
385        server.setHandler(handlerCollection);
386        try {
387            server.start();
388        } catch (Exception e) {
389            throw new MojoExecutionException(e.getMessage(), e);
390        }
391        List<MavenProject> reactorProjects = this.reactorProjects;
392        WebAppContext webAppContext;
393        Resource webXml;
394        List<Resource> resources;
395        try {
396            resources = new ArrayList<Resource>();
397            addCssEngineResources(project, reactorProjects, mappings, resources);
398            for (Artifact a : getOverlayArtifacts(project, scope)) {
399                addOverlayResources(reactorProjects, resources, a);
400            }
401            if (warSourceDirectory == null) {
402                warSourceDirectory = new File(project.getBasedir(), "src/main/webapp");
403            }
404            if (warSourceDirectory.isDirectory()) {
405                resources.add(Resource.newResource(warSourceDirectory));
406            }
407            Collections.reverse(resources);
408            getLog().debug("Overlays:");
409            int index = 0;
410            for (Resource r : resources) {
411                getLog().debug("  [" + index++ + "] = " + r);
412            }
413            final ResourceCollection resourceCollection =
414                    new ResourceCollection(resources.toArray(new Resource[resources.size()]));
415
416            webAppContext = new JettyWebAppContext();
417            webAppContext.setWar(warSourceDirectory.getAbsolutePath());
418            webAppContext.setBaseResource(resourceCollection);
419
420            WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext);
421            for (String s : getClasspathElements(project, scope)) {
422                classLoader.addClassPath(s);
423            }
424            webAppContext.setClassLoader(classLoader);
425
426            contexts.setHandlers(new Handler[]{webAppContext});
427            contexts.start();
428            webAppContext.start();
429            Resource webInf = webAppContext.getWebInf();
430            webXml = webInf != null ? webInf.getResource("web.xml") : null;
431        } catch (MojoExecutionException e) {
432            throw e;
433        } catch (MojoFailureException e) {
434            throw e;
435        } catch (ArtifactFilterException e) {
436            throw new MojoExecutionException(e.getMessage(), e);
437        } catch (MalformedURLException e) {
438            throw new MojoExecutionException(e.getMessage(), e);
439        } catch (IOException e) {
440            throw new MojoExecutionException(e.getMessage(), e);
441        } catch (Exception e) {
442            throw new MojoExecutionException(e.getMessage(), e);
443        }
444
445        long webXmlLastModified = webXml == null ? 0L : webXml.lastModified();
446        try {
447
448            getLog().info("Context started. Will restart if changes to poms detected.");
449            long nextClasspathCheck = System.currentTimeMillis() + classpathCheckInterval;
450            while (true) {
451                long nextCheck = System.currentTimeMillis() + 500;
452                long pomsLastModified = getPomsLastModified();
453                boolean pomsChanged = lastPomChange < pomsLastModified;
454                boolean overlaysChanged = false;
455                boolean classPathChanged = webXmlLastModified < (webXml == null ? 0L : webXml.lastModified());
456                if (nextClasspathCheck < System.currentTimeMillis()) {
457                    long classChange = classpathLastModified(project);
458                    if (classChange > lastClassChange) {
459                        classPathChanged = true;
460                        lastClassChange = classChange;
461                    }
462                    nextClasspathCheck = System.currentTimeMillis() + classpathCheckInterval;
463                }
464                if (!classPathChanged && !overlaysChanged && !pomsChanged) {
465
466                    try {
467                        lastResourceChange = processResourceSourceChanges(reactorProjects, project, lastResourceChange);
468                    } catch (ArtifactFilterException e) {
469                        getLog().debug("Couldn't process resource changes", e);
470                    }
471                    try {
472                        Thread.sleep(Math.max(100L, nextCheck - System.currentTimeMillis()));
473                    } catch (InterruptedException e) {
474                        getLog().debug("Interrupted", e);
475                    }
476                    continue;
477                }
478                if (pomsChanged) {
479                    getLog().info("Change in poms detected, re-parsing to evaluate impact...");
480                    // we will now process this change,
481                    // so from now on don't re-process
482                    // even if we have issues processing
483                    lastPomChange = pomsLastModified;
484                    List<MavenProject> newReactorProjects;
485                    try {
486                        newReactorProjects = buildReactorProjects();
487                    } catch (ProjectBuildingException e) {
488                        getLog().info("Re-parse aborted due to malformed pom.xml file(s)", e);
489                        continue;
490                    } catch (CycleDetectedException e) {
491                        getLog().info("Re-parse aborted due to dependency cycle in project model", e);
492                        continue;
493                    } catch (DuplicateProjectException e) {
494                        getLog().info("Re-parse aborted due to duplicate projects in project model", e);
495                        continue;
496                    } catch (Exception e) {
497                        getLog().info("Re-parse aborted due a problem that prevented sorting the project model", e);
498                        continue;
499                    }
500                    if (!buildPlanEqual(newReactorProjects, this.reactorProjects)) {
501                        throw new BuildPlanModifiedException("A pom.xml change has impacted the build plan.");
502                    }
503                    MavenProject newProject = findProject(newReactorProjects, this.project);
504                    if (newProject == null) {
505                        throw new BuildPlanModifiedException(
506                                "A pom.xml change appears to have removed " + this.project.getId()
507                                        + " from the build plan.");
508                    }
509
510                    newProject.setArtifacts(resolve(newProject, "runtime"));
511
512                    getLog().debug("Comparing effective classpath of new and old models");
513                    try {
514                        classPathChanged = classPathChanged || classpathsEqual(project, newProject, scope);
515                    } catch (DependencyResolutionRequiredException e) {
516                        getLog().info("Re-parse aborted due to dependency resolution problems", e);
517                        continue;
518                    }
519                    if (classPathChanged) {
520                        getLog().info("Effective classpath of " + project.getId() + " has changed.");
521                    } else {
522                        getLog().debug("Effective classpath is unchanged.");
523                    }
524
525                    getLog().debug("Comparing effective overlays of new and old models");
526                    try {
527                        overlaysChanged = overlaysEqual(project, newProject);
528                    } catch (OverConstrainedVersionException e) {
529                        getLog().info("Re-parse aborted due to dependency resolution problems", e);
530                        continue;
531                    } catch (ArtifactFilterException e) {
532                        getLog().info("Re-parse aborted due to overlay resolution problems", e);
533                        continue;
534                    }
535                    if (overlaysChanged) {
536                        getLog().info("Overlay modules of " + project.getId() + " have changed.");
537                    } else {
538                        getLog().debug("Overlay modules are unchanged.");
539                    }
540
541                    getLog().debug("Comparing overlays paths of new and old models");
542                    try {
543                        List<Resource> newResources = new ArrayList<Resource>();
544                        // TODO newMappings
545                        addCssEngineResources(newProject, newReactorProjects, mappings, resources);
546                        for (Artifact a : getOverlayArtifacts(project, scope)) {
547                            addOverlayResources(newReactorProjects, newResources, a);
548                        }
549                        if (warSourceDirectory.isDirectory()) {
550                            newResources.add(Resource.newResource(warSourceDirectory));
551                        }
552                        Collections.reverse(newResources);
553                        getLog().debug("New overlays:");
554                        int index = 0;
555                        for (Resource r : newResources) {
556                            getLog().debug("  [" + index++ + "] = " + r);
557                        }
558                        boolean overlayPathsChanged = !resources.equals(newResources);
559                        if (overlayPathsChanged) {
560                            getLog().info("Overlay module paths of " + project.getId() + " have changed.");
561                        } else {
562                            getLog().debug("Overlay module paths are unchanged.");
563                        }
564                        overlaysChanged = overlaysChanged || overlayPathsChanged;
565                    } catch (ArtifactFilterException e) {
566                        getLog().info("Re-parse aborted due to overlay evaluation problems", e);
567                        continue;
568                    } catch (PluginConfigurationException e) {
569                        getLog().info("Re-parse aborted due to overlay evaluation problems", e);
570                        continue;
571                    } catch (PluginContainerException e) {
572                        getLog().info("Re-parse aborted due to overlay evaluation problems", e);
573                        continue;
574                    } catch (IOException e) {
575                        getLog().info("Re-parse aborted due to overlay evaluation problems", e);
576                        continue;
577                    }
578
579                    project = newProject;
580                    reactorProjects = newReactorProjects;
581                }
582
583                if (!overlaysChanged && !classPathChanged) {
584                    continue;
585                }
586                getLog().info("Restarting context to take account of changes...");
587                try {
588                    webAppContext.stop();
589                } catch (Exception e) {
590                    throw new MojoExecutionException(e.getMessage(), e);
591                }
592
593                if (classPathChanged) {
594                    getLog().info("Updating classpath...");
595                    try {
596                        WebAppClassLoader classLoader = new WebAppClassLoader(webAppContext);
597                        for (String s : getClasspathElements(project, scope)) {
598                            classLoader.addClassPath(s);
599                        }
600                        webAppContext.setClassLoader(classLoader);
601                    } catch (Exception e) {
602                        throw new MojoExecutionException(e.getMessage(), e);
603                    }
604                }
605
606                if (overlaysChanged || classPathChanged) {
607                    getLog().info("Updating overlays...");
608                    try {
609                        resources = new ArrayList<Resource>();
610                        addCssEngineResources(project, reactorProjects, mappings, resources);
611                        for (Artifact a : getOverlayArtifacts(project, scope)) {
612                            addOverlayResources(reactorProjects, resources, a);
613                        }
614                        if (warSourceDirectory.isDirectory()) {
615                            resources.add(Resource.newResource(warSourceDirectory));
616                        }
617                        Collections.reverse(resources);
618                        getLog().debug("Overlays:");
619                        int index = 0;
620                        for (Resource r : resources) {
621                            getLog().debug("  [" + index++ + "] = " + r);
622                        }
623                        final ResourceCollection resourceCollection =
624                                new ResourceCollection(resources.toArray(new Resource[resources.size()]));
625                        webAppContext.setBaseResource(resourceCollection);
626                    } catch (Exception e) {
627                        throw new MojoExecutionException(e.getMessage(), e);
628                    }
629                }
630                try {
631                    webAppContext.start();
632                } catch (Exception e) {
633                    throw new MojoExecutionException(e.getMessage(), e);
634                }
635                webXmlLastModified = webXml == null ? 0L : webXml.lastModified();
636                getLog().info("Context restarted.");
637            }
638
639        } finally {
640            try {
641                server.stop();
642            } catch (Exception e) {
643                throw new MojoExecutionException(e.getMessage(), e);
644            }
645        }
646    }
647
648    private void addOverlayResources(List<MavenProject> reactorProjects, List<Resource> _resources, Artifact a)
649            throws PluginConfigurationException, PluginContainerException, IOException, MojoExecutionException {
650        List<Resource> resources = new ArrayList<Resource>();
651        MavenProject fromReactor = findProject(reactorProjects, a);
652        if (fromReactor != null) {
653            MavenSession session = this.session.clone();
654            session.setCurrentProject(fromReactor);
655            Plugin plugin = findThisPluginInProject(fromReactor);
656
657            // we cheat here and use our version of the plugin... but this is less of a cheat than the only
658            // other way which is via reflection.
659            MojoDescriptor jszipDescriptor = findMojoDescriptor(pluginDescriptor, JSZipMojo.class);
660
661            for (PluginExecution pluginExecution : plugin.getExecutions()) {
662                if (!pluginExecution.getGoals().contains(jszipDescriptor.getGoal())) {
663                    continue;
664                }
665                MojoExecution mojoExecution =
666                        createMojoExecution(plugin, pluginExecution, jszipDescriptor);
667                JSZipMojo mojo = (JSZipMojo) mavenPluginManager
668                        .getConfiguredMojo(Mojo.class, session, mojoExecution);
669                try {
670                    File contentDirectory = mojo.getContentDirectory();
671                    if (contentDirectory.isDirectory()) {
672                        getLog().debug(
673                                "Adding resource directory " + contentDirectory);
674                        resources.add(Resource.newResource(contentDirectory));
675                    }
676                    // TODO filtering support
677                    //
678                    // The good news:
679                    //  * resources:resources gets the list of resources from /project/build/resources *only*
680                    // The bad news:
681                    //  * looks like maven-invoker is the only way to safely invoke it again
682                    //
683                    // probable solution
684                    //
685                    // 1. get the list of all resource directories, add on the scan for changes
686                    // 2. if a change to a non-filtered file, just copy it over
687                    // 3. if a change to a filtered file or a change to effective pom, use maven-invoker to run the
688                    //    lifecycle up to 'compile' or 'process-resources' <-- preferred
689                    //
690                    File resourcesDirectory = mojo.getResourcesDirectory();
691                    if (resourcesDirectory.isDirectory()) {
692                        getLog().debug(
693                                "Adding resource directory " + resourcesDirectory);
694                        resources.add(Resource.newResource(resourcesDirectory));
695                    }
696                } finally {
697                    mavenPluginManager.releaseMojo(mojo, mojoExecution);
698                }
699            }
700        } else {
701            resources.add(Resource.newResource("jar:" + a.getFile().toURI().toURL() + "!/"));
702        }
703
704        // TODO support live reloading of mappings
705        String path = "";
706        if (mappings != null) {
707            for (Mapping mapping : mappings) {
708                if (mapping.isMatch(a)) {
709                    path = StringUtils.clean(mapping.getPath());
710                    break;
711                }
712            }
713        }
714
715        if (StringUtils.isBlank(path)) {
716            _resources.addAll(resources);
717        } else {
718            ResourceCollection child = new ResourceCollection(resources.toArray(new Resource[resources.size()]));
719            _resources.add(new VirtualDirectoryResource(child, path));
720        }
721    }
722
723    private void addCssEngineResources(MavenProject project, List<MavenProject> reactorProjects, Mapping[] mappings, List<Resource> _resources)
724            throws MojoExecutionException, IOException {
725        List<PseudoFileSystem.Layer> layers = new ArrayList<PseudoFileSystem.Layer>();
726        layers.add(new PseudoFileSystem.FileLayer("/virtual", warSourceDirectory));
727        FilterArtifacts filter = new FilterArtifacts();
728
729        filter.addFilter(new ProjectTransitivityFilter(project.getDependencyArtifacts(), false));
730
731        filter.addFilter(new ScopeFilter("runtime", ""));
732
733        filter.addFilter(new TypeFilter(JSZIP_TYPE, ""));
734
735        // start with all artifacts.
736        Set<Artifact> artifacts = project.getArtifacts();
737
738        // perform filtering
739        try {
740            artifacts = filter.filter(artifacts);
741        } catch (ArtifactFilterException e) {
742            throw new MojoExecutionException(e.getMessage(), e);
743        }
744
745        for (Artifact artifact : artifacts) {
746            String path = Mapping.getArtifactPath(mappings, artifact);
747            getLog().info("Adding " + ArtifactUtils.key(artifact) + " to virtual filesystem");
748            File file = artifact.getFile();
749            if (file.isDirectory()) {
750                MavenProject fromReactor = findProject(reactorProjects, artifact);
751                if (fromReactor != null) {
752                    MavenSession session = this.session.clone();
753                    session.setCurrentProject(fromReactor);
754                    Plugin plugin = findThisPluginInProject(fromReactor);
755                    try {
756                        // we cheat here and use our version of the plugin... but this is less of a cheat than the only
757                        // other way which is via reflection.
758                        MojoDescriptor jszipDescriptor = findMojoDescriptor(this.pluginDescriptor, JSZipMojo.class);
759
760                        for (PluginExecution pluginExecution : plugin.getExecutions()) {
761                            if (!pluginExecution.getGoals().contains(jszipDescriptor.getGoal())) {
762                                continue;
763                            }
764                            MojoExecution mojoExecution =
765                                    createMojoExecution(plugin, pluginExecution, jszipDescriptor);
766                            JSZipMojo mojo = (JSZipMojo) mavenPluginManager
767                                    .getConfiguredMojo(org.apache.maven.plugin.Mojo.class, session, mojoExecution);
768                            try {
769                                File contentDirectory = mojo.getContentDirectory();
770                                if (contentDirectory.isDirectory()) {
771                                    getLog().debug("Merging directory " + contentDirectory + " into " + path);
772                                    layers.add(new PseudoFileSystem.FileLayer(path, contentDirectory));
773                                }
774                                File resourcesDirectory = mojo.getResourcesDirectory();
775                                if (resourcesDirectory.isDirectory()) {
776                                    getLog().debug("Merging directory " + contentDirectory + " into " + path);
777                                    layers.add(new PseudoFileSystem.FileLayer(path, resourcesDirectory));
778                                }
779                            } finally {
780                                mavenPluginManager.releaseMojo(mojo, mojoExecution);
781                            }
782                        }
783                    } catch (PluginConfigurationException e) {
784                        throw new MojoExecutionException(e.getMessage(), e);
785                    } catch (PluginContainerException e) {
786                        throw new MojoExecutionException(e.getMessage(), e);
787                    }
788                } else {
789                    throw new MojoExecutionException("Cannot find jzsip artifact: " + artifact.getId());
790                }
791            } else {
792                try {
793                    getLog().debug("Merging .zip file " + file + " into " + path);
794                    layers.add(new PseudoFileSystem.ZipLayer(path, file));
795                } catch (IOException e) {
796                    throw new MojoExecutionException(e.getMessage(), e);
797                }
798            }
799        }
800
801        final PseudoFileSystem fs = new PseudoFileSystem(layers);
802
803        CssEngine engine = new LessEngine(fs, encoding == null ? "utf-8" : encoding, getLog(), lessCompress, customLessScript, showErrorExtracts);
804
805        // look for files to compile
806
807        PseudoDirectoryScanner scanner = new PseudoDirectoryScanner();
808
809        scanner.setFileSystem(fs);
810
811        scanner.setBasedir(fs.getPseudoFile("/virtual"));
812
813        if (lessIncludes != null && !lessIncludes.isEmpty()) {
814            scanner.setIncludes(processIncludesExcludes(lessIncludes));
815        } else {
816            scanner.setIncludes(new String[]{"**/*.less"});
817        }
818
819        if (lessExcludes != null && !lessExcludes.isEmpty()) {
820            scanner.setExcludes(processIncludesExcludes(lessExcludes));
821        } else {
822            scanner.setExcludes(new String[0]);
823        }
824
825        scanner.scan();
826
827        for (String fileName : new ArrayList<String>(Arrays.asList(scanner.getIncludedFiles()))) {
828            final CssEngineResource child = new CssEngineResource(fs, engine, "/virtual/" + fileName);
829            final String path = FileUtils.dirname(fileName);
830            if (StringUtils.isBlank(path)) {
831                _resources.add(new VirtualDirectoryResource(new VirtualDirectoryResource(child, child.getName()), ""));
832            } else {
833                _resources.add(new VirtualDirectoryResource(new VirtualDirectoryResource(child, child.getName()), path));
834            }
835        }
836
837        engine = new SassEngine(fs, encoding == null ? "utf-8" : encoding);
838
839        if (sassIncludes != null && !sassIncludes.isEmpty()) {
840            scanner.setIncludes(processIncludesExcludes(sassIncludes));
841        } else {
842            scanner.setIncludes(new String[]{"**/*.sass","**/*.scss"});
843        }
844
845        if (sassExcludes != null && !sassExcludes.isEmpty()) {
846            scanner.setExcludes(processIncludesExcludes(sassExcludes));
847        } else {
848            scanner.setExcludes(new String[]{"**/_*.sass","**/_*.scss"});
849        }
850
851        scanner.scan();
852
853        for (String fileName : new ArrayList<String>(Arrays.asList(scanner.getIncludedFiles()))) {
854            final CssEngineResource child = new CssEngineResource(fs, engine, "/virtual/" + fileName);
855            final String path = FileUtils.dirname(fileName);
856            if (StringUtils.isBlank(path)) {
857                _resources.add(new VirtualDirectoryResource(new VirtualDirectoryResource(child, child.getName()), ""));
858            } else {
859                _resources.add(new VirtualDirectoryResource(new VirtualDirectoryResource(child, child.getName()), path));
860            }
861        }
862
863    }
864
865    private void injectMissingArtifacts(MavenProject destination, MavenProject source) {
866        if (destination.getArtifact().getFile() == null && source.getArtifact().getFile() != null) {
867            getLog().info("Pushing primary artifact from forked execution into current execution");
868            destination.getArtifact().setFile(source.getArtifact().getFile());
869        }
870        for (Artifact executedArtifact : source.getAttachedArtifacts()) {
871            String executedArtifactId =
872                    (executedArtifact.getClassifier() == null ? "." : "-" + executedArtifact.getClassifier() + ".")
873                            + executedArtifact.getType();
874            if (StringUtils.equals(executedArtifact.getGroupId(), destination.getGroupId())
875                    && StringUtils.equals(executedArtifact.getArtifactId(), destination.getArtifactId())
876                    && StringUtils.equals(executedArtifact.getVersion(), destination.getVersion())) {
877                boolean found = false;
878                for (Artifact artifact : destination.getAttachedArtifacts()) {
879                    if (StringUtils.equals(artifact.getGroupId(), destination.getGroupId())
880                            && StringUtils.equals(artifact.getArtifactId(), destination.getArtifactId())
881                            && StringUtils.equals(artifact.getVersion(), destination.getVersion())
882                            && StringUtils.equals(artifact.getClassifier(), executedArtifact.getClassifier())
883                            && StringUtils.equals(artifact.getType(), executedArtifact.getType())) {
884                        if (artifact.getFile() == null) {
885                            getLog().info("Pushing " + executedArtifactId
886                                    + " artifact from forked execution into current execution");
887                            artifact.setFile(executedArtifact.getFile());
888                        }
889                        found = true;
890                    }
891                }
892                if (!found) {
893                    getLog().info("Attaching " +
894                            executedArtifactId
895                            + " artifact from forked execution into current execution");
896                    projectHelper
897                            .attachArtifact(destination, executedArtifact.getType(), executedArtifact.getClassifier(),
898                                    executedArtifact.getFile());
899                }
900            }
901        }
902    }
903
904    private long processResourceSourceChanges(List<MavenProject> reactorProjects, MavenProject project,
905                                              long lastModified)
906            throws ArtifactFilterException {
907        long newLastModified = lastModified;
908        getLog().debug("Last modified for resource sources = " + lastModified);
909
910        Set<File> checked = new HashSet<File>();
911        for (Artifact a : getOverlayArtifacts(project, scope)) {
912            MavenProject p = findProject(reactorProjects, a);
913            if (p == null || p.getBuild() == null || p.getBuild().getResources() == null) {
914                continue;
915            }
916            boolean changed = false;
917            boolean changedFiltered = false;
918            for (org.apache.maven.model.Resource r : p.getBuild().getResources()) {
919                File dir = new File(r.getDirectory());
920                getLog().debug("Checking last modified for " + dir);
921                if (checked.contains(dir)) {
922                    continue;
923                }
924                checked.add(dir);
925                long dirLastModified = recursiveLastModified(dir);
926                if (lastModified < dirLastModified) {
927                    changed = true;
928                    if (r.isFiltering()) {
929                        changedFiltered = true;
930                    }
931                }
932            }
933            if (changedFiltered) {
934                getLog().info("Detected change in resources of " + ArtifactUtils.versionlessKey(a) + "...");
935                getLog().debug("Resource filtering is used by project, invoking Maven to handle update");
936                // need to let Maven handle it as its the only (although slower) safe way to do it right with filters
937                InvocationRequest request = new DefaultInvocationRequest();
938                request.setPomFile(p.getFile());
939                request.setInteractive(false);
940                request.setRecursive(false);
941                request.setGoals(Collections.singletonList("process-resources"));
942
943                Invoker invoker = new DefaultInvoker();
944                invoker.setLogger(new MavenProxyLogger());
945                try {
946                    invoker.execute(request);
947                    newLastModified = System.currentTimeMillis();
948                    getLog().info("Change in resources of " + ArtifactUtils.versionlessKey(a) + " processed");
949                } catch (MavenInvocationException e) {
950                    getLog().info(e);
951                }
952            } else if (changed) {
953                getLog().info("Detected change in resources of " + ArtifactUtils.versionlessKey(a) + "...");
954                getLog().debug("Resource filtering is not used by project, handling update ourselves");
955                // can do it fast ourselves
956                MavenResourcesExecution mavenResourcesExecution =
957                        new MavenResourcesExecution(p.getResources(), new File(p.getBuild().getOutputDirectory()), p,
958                                p.getProperties().getProperty("project.build.sourceEncoding"), Collections.emptyList(),
959                                Collections.<String>emptyList(), session);
960                try {
961                    mavenResourcesFiltering.filterResources(mavenResourcesExecution);
962                    newLastModified = System.currentTimeMillis();
963                    getLog().info("Change in resources of " + ArtifactUtils.versionlessKey(a) + " processed");
964                } catch (MavenFilteringException e) {
965                    getLog().info(e);
966                }
967            }
968        }
969        return newLastModified;
970    }
971
972    private List<MavenProject> buildReactorProjects() throws Exception {
973
974        List<MavenProject> projects = new ArrayList<MavenProject>();
975        for (MavenProject p : reactorProjects) {
976            ProjectBuildingRequest request = new DefaultProjectBuildingRequest();
977
978            request.setProcessPlugins(true);
979            request.setProfiles(request.getProfiles());
980            request.setActiveProfileIds(session.getRequest().getActiveProfiles());
981            request.setInactiveProfileIds(session.getRequest().getInactiveProfiles());
982            request.setRemoteRepositories(session.getRequest().getRemoteRepositories());
983            request.setSystemProperties(session.getSystemProperties());
984            request.setUserProperties(session.getUserProperties());
985            request.setRemoteRepositories(session.getRequest().getRemoteRepositories());
986            request.setPluginArtifactRepositories(session.getRequest().getPluginArtifactRepositories());
987            request.setRepositorySession(session.getRepositorySession());
988            request.setLocalRepository(localRepository);
989            request.setBuildStartTime(session.getRequest().getStartTime());
990            request.setResolveDependencies(true);
991            request.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_STRICT);
992            projects.add(projectBuilder.build(p.getFile(), request).getProject());
993        }
994        return new ProjectSorter(projects).getSortedProjects();
995    }
996
997    private long classpathLastModified(MavenProject project) {
998        long result = Long.MIN_VALUE;
999        try {
1000            for (String element : getClasspathElements(project, scope)) {
1001                File elementFile = new File(element);
1002                result = Math.max(recursiveLastModified(elementFile), result);
1003            }
1004        } catch (DependencyResolutionRequiredException e) {
1005            // ignore
1006        }
1007        return result;
1008
1009    }
1010
1011    private long recursiveLastModified(File fileOrDirectory) {
1012        long result = Long.MIN_VALUE;
1013        if (fileOrDirectory.exists()) {
1014            result = Math.max(fileOrDirectory.lastModified(), result);
1015            if (fileOrDirectory.isDirectory()) {
1016                Stack<Iterator<File>> stack = new Stack<Iterator<File>>();
1017                stack.push(contentsAsList(fileOrDirectory).iterator());
1018                while (!stack.empty()) {
1019                    Iterator<File> i = stack.pop();
1020                    while (i.hasNext()) {
1021                        File file = i.next();
1022                        result = Math.max(file.lastModified(), result);
1023                        if (file.isDirectory()) {
1024                            stack.push(i);
1025                            i = contentsAsList(file).iterator();
1026                        }
1027                    }
1028                }
1029            }
1030        }
1031        return result;
1032    }
1033
1034    private static List<File> contentsAsList(File directory) {
1035        File[] files = directory.listFiles();
1036        return files == null ? Collections.<File>emptyList() : Arrays.asList(files);
1037    }
1038
1039    private boolean classpathsEqual(MavenProject oldProject, MavenProject newProject, String scope)
1040            throws DependencyResolutionRequiredException {
1041        int seq = 0;
1042        List<String> newCP = getClasspathElements(newProject, scope);
1043        List<String> oldCP = getClasspathElements(oldProject, scope);
1044        boolean classPathChanged = newCP.size() != oldCP.size();
1045        for (Iterator<String> i = newCP.iterator(), j = oldCP.iterator(); i.hasNext() || j.hasNext(); ) {
1046            String left = i.hasNext() ? i.next() : "(empty)";
1047            String right = j.hasNext() ? j.next() : "(empty)";
1048            if (!StringUtils.equals(left, right)) {
1049                getLog().debug("classpath[" + seq + "]");
1050                getLog().debug("  old = " + left);
1051                getLog().debug("  new = " + right);
1052                classPathChanged = true;
1053            }
1054            seq++;
1055        }
1056        return classPathChanged;
1057    }
1058
1059    private MavenProject findProject(List<MavenProject> newReactorProjects, MavenProject oldProject) {
1060        final String targetId = oldProject.getId();
1061        for (MavenProject newProject : newReactorProjects) {
1062            if (targetId.equals(newProject.getId())) {
1063                return newProject;
1064            }
1065        }
1066        return null;
1067    }
1068
1069    private boolean buildPlanEqual(List<MavenProject> newPlan, List<MavenProject> oldPlan) {
1070        if (newPlan.size() != oldPlan.size()) {
1071            return false;
1072        }
1073        int seq = 0;
1074        for (Iterator<MavenProject> i = newPlan.iterator(), j = oldPlan.iterator(); i.hasNext() && j.hasNext(); ) {
1075            MavenProject left = i.next();
1076            MavenProject right = j.next();
1077            getLog().debug(
1078                    "[" + (seq++) + "] = " + left.equals(right) + (left == right ? " same" : " diff") + " : "
1079                            + left.getName() + "[" + left.getDependencies().size() + "], " + right.getName()
1080                            + "["
1081                            + right.getDependencies().size() + "]");
1082            if (!left.equals(right)) {
1083                return false;
1084            }
1085            if (left.getDependencies().size() != right.getDependencies().size()) {
1086                getLog().info("Dependency tree of " + left.getId() + " has been modified");
1087            }
1088        }
1089        return true;
1090    }
1091
1092    private boolean overlaysEqual(MavenProject oldProject, MavenProject newProject)
1093            throws ArtifactFilterException, OverConstrainedVersionException {
1094        boolean overlaysChanged;
1095        Set<Artifact> newOA = getOverlayArtifacts(newProject, scope);
1096        Set<Artifact> oldOA = getOverlayArtifacts(oldProject, scope);
1097        overlaysChanged = newOA.size() != oldOA.size();
1098        for (Artifact n : newOA) {
1099            boolean found = false;
1100            for (Artifact o : oldOA) {
1101                if (StringUtils.equals(n.getArtifactId(), o.getArtifactId()) && StringUtils
1102                        .equals(n.getGroupId(), o.getGroupId())) {
1103                    if (o.getSelectedVersion().equals(n.getSelectedVersion())) {
1104                        found = true;
1105                        break;
1106                    }
1107                }
1108            }
1109            if (!found) {
1110                getLog().debug("added overlay artifact: " + n);
1111                overlaysChanged = true;
1112            }
1113        }
1114        for (Artifact o : oldOA) {
1115            boolean found = false;
1116            for (Artifact n : newOA) {
1117                if (StringUtils.equals(n.getArtifactId(), o.getArtifactId()) && StringUtils
1118                        .equals(n.getGroupId(), o.getGroupId())) {
1119                    if (o.getSelectedVersion().equals(n.getSelectedVersion())) {
1120                        found = true;
1121                        break;
1122                    }
1123                }
1124            }
1125            if (!found) {
1126                getLog().debug("removed overlay artifact: " + o);
1127                overlaysChanged = true;
1128            }
1129        }
1130        if (overlaysChanged) {
1131            getLog().info("Effective overlays of " + oldProject.getId() + " have changed.");
1132        } else {
1133            getLog().debug("Effective overlays are unchanged.");
1134        }
1135        return overlaysChanged;
1136    }
1137
1138    @SuppressWarnings("unchecked")
1139    private Set<Artifact> getOverlayArtifacts(MavenProject project, String scope) throws ArtifactFilterException {
1140        FilterArtifacts filter = new FilterArtifacts();
1141
1142        filter.addFilter(new ProjectTransitivityFilter(project.getDependencyArtifacts(), false));
1143
1144        filter.addFilter(new ScopeFilter(scope, ""));
1145
1146        filter.addFilter(new TypeFilter(JSZIP_TYPE, ""));
1147
1148        return filter.filter(project.getArtifacts());
1149    }
1150
1151    private long getPomsLastModified() {
1152        long result = Long.MIN_VALUE;
1153        for (MavenProject p : reactorProjects) {
1154            result = Math.max(p.getFile().lastModified(), result);
1155        }
1156        return result;
1157    }
1158
1159    @SuppressWarnings("unchecked")
1160    private List<String> getClasspathElements(MavenProject project, String scope)
1161            throws DependencyResolutionRequiredException {
1162        if ("test".equals(scope)) {
1163            return project.getTestClasspathElements();
1164        }
1165        if ("compile".equals(scope)) {
1166            return project.getCompileClasspathElements();
1167        }
1168        if ("runtime".equals(scope)) {
1169            return project.getRuntimeClasspathElements();
1170        }
1171        return Collections.emptyList();
1172    }
1173
1174    @SuppressWarnings("unchecked")
1175    private Set<Artifact> resolve(MavenProject newProject, String scope)
1176            throws MojoExecutionException {
1177        try {
1178            return projectDependenciesResolver.resolve(newProject, Collections.singletonList(scope), session);
1179        } catch (ArtifactNotFoundException e) {
1180            throw new MojoExecutionException(e.getMessage(), e);
1181        } catch (ArtifactResolutionException e) {
1182            throw new MojoExecutionException(e.getMessage(), e);
1183        }
1184    }
1185
1186    public void setSystemProperties(SystemProperties systemProperties) {
1187        if (this.systemProperties == null) {
1188            this.systemProperties = systemProperties;
1189        } else {
1190            Iterator itor = systemProperties.getSystemProperties().iterator();
1191            while (itor.hasNext()) {
1192                SystemProperty prop = (SystemProperty) itor.next();
1193                this.systemProperties.setSystemProperty(prop);
1194            }
1195        }
1196    }
1197
1198    public SystemProperties getSystemProperties() {
1199        return this.systemProperties;
1200    }
1201
1202
1203    private class MavenProxyLogger implements InvokerLogger {
1204
1205        public void debug(String content) {
1206            getLog().debug(content);
1207        }
1208
1209        public void info(Throwable error) {
1210            getLog().info(error);
1211        }
1212
1213        public void info(String content, Throwable error) {
1214            getLog().info(content, error);
1215        }
1216
1217        public void info(String content) {
1218            getLog().info(content);
1219        }
1220
1221        public void warn(Throwable error) {
1222            getLog().warn(error);
1223        }
1224
1225        public void error(String content, Throwable error) {
1226            getLog().error(content, error);
1227        }
1228
1229        public void debug(String content, Throwable error) {
1230            getLog().debug(content, error);
1231        }
1232
1233        public void debug(Throwable error) {
1234            getLog().debug(error);
1235        }
1236
1237        public void warn(String content) {
1238            getLog().warn(content);
1239        }
1240
1241        public void error(Throwable error) {
1242            getLog().error(error);
1243        }
1244
1245        public void error(String content) {
1246            getLog().error(content);
1247        }
1248
1249        public void warn(String content, Throwable error) {
1250            getLog().warn(content, error);
1251        }
1252
1253        public void fatalError(String s) {
1254            getLog().error(s);
1255        }
1256
1257        public boolean isDebugEnabled() {
1258            return getLog().isDebugEnabled();
1259        }
1260
1261        public boolean isInfoEnabled() {
1262            return getLog().isInfoEnabled();
1263        }
1264
1265        public boolean isWarnEnabled() {
1266            return getLog().isWarnEnabled();
1267        }
1268
1269        public boolean isErrorEnabled() {
1270            return getLog().isErrorEnabled();
1271        }
1272
1273        public void fatalError(String s, Throwable throwable) {
1274            getLog().error(s, throwable);
1275        }
1276
1277        public boolean isFatalErrorEnabled() {
1278            return getLog().isErrorEnabled();
1279        }
1280
1281        public void setThreshold(int i) {
1282        }
1283
1284        public int getThreshold() {
1285            return 0;
1286        }
1287    }
1288}