001package org.andromda.maven.plugin.andromdapp;
002
003import java.io.File;
004import java.io.FileReader;
005import java.net.URL;
006import java.util.ArrayList;
007import java.util.LinkedHashMap;
008import java.util.List;
009import java.util.Map;
010import org.apache.commons.io.FileUtils;
011import org.apache.maven.artifact.Artifact;
012import org.apache.maven.artifact.factory.ArtifactFactory;
013import org.apache.maven.model.Model;
014import org.apache.maven.model.Parent;
015import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
016import org.apache.maven.plugin.AbstractMojo;
017import org.apache.maven.plugin.MojoExecutionException;
018import org.apache.maven.plugin.MojoFailureException;
019import org.apache.maven.project.MavenProject;
020import org.codehaus.plexus.util.DirectoryScanner;
021
022/**
023 * Basically post processes a previously built ear and replaces any war
024 * artifacts with symbolic links and then symbolic links the ear to the deploy
025 * directory so that we don't have to redeploy an ear in order to make jsp changes.
026 *
027 * @author Chad Brandon
028 * @goal link
029 * @phase package
030 */
031public class SymbolicLinkExplodedEarMojo
032    extends AbstractMojo
033{
034    /**
035     * The maven project.
036     *
037     * @parameter expression="${project}"
038     * @required
039     * @readonly
040     */
041    protected MavenProject project;
042
043    /**
044     * The location in which to link the exploded ear.
045     *
046     * @parameter expression="${env.JBOSS_HOME}/server/default/deploy"
047     */
048    private String deployLocation;
049
050    /**
051     * The number of levels allowed to travel up before we get to the "root project" (i.e. this
052     * will prevent the system from attempting to get parent project that aren't really
053     * part of the direct project).
054     *
055     * @parameter expression="1"
056     */
057    private int rootProjectLimit;
058
059    /**
060     * Artifact factory, needed to download source jars for inclusion in
061     * classpath.
062     *
063     * @component role="org.apache.maven.artifact.factory.ArtifactFactory"
064     * @required
065     * @readonly
066     */
067    private ArtifactFactory artifactFactory;
068
069    /**
070     * Command to create a symbolic link on the Windows platform
071     */
072    private static final String LN_WINDOWS = "junction.exe";
073
074    /**
075     * Command to create a symbolic link on the UNIX platform
076     */
077    private static final String LN_UNIX = "ln -s";
078
079    /**
080     * <code>true</code> if the base OS is Windows.
081     */
082    private static  boolean isWindows = false;
083
084    private static final String WINDOWS = "Windows";
085
086    static
087    {
088        final String osName = System.getProperty("os.name");
089        if (osName.contains(WINDOWS))
090        {
091            isWindows = true;
092        }
093    }
094
095    /**
096     * @see org.apache.maven.plugin.Mojo#execute()
097     */
098    public void execute()
099        throws MojoExecutionException, MojoFailureException
100    {
101        try
102        {
103            final File earDirectory =
104                new File(this.project.getBuild().getDirectory() + '/' + project.getBuild().getFinalName());
105            final Map<String, Artifact> artifacts = new LinkedHashMap<String, Artifact>();
106            for (final Artifact artifact : this.getExplodedModuleArtifacts())
107            {
108                artifacts.put(
109                    artifact.getFile().getName(),
110                    artifact);
111            }
112            if (earDirectory.exists() && earDirectory.isDirectory())
113            {
114                String linkCommand;
115                if (isWindows)
116                {
117                    linkCommand = LN_WINDOWS;
118                    final File executableFile =
119                        new File(System.getProperty("java.io.tmpdir") + '/' + System.getProperty("user.name"),
120                            LN_WINDOWS);
121                    if (!executableFile.exists())
122                    {
123                        final URL resource =
124                            Thread.currentThread().getContextClassLoader().getResource(LN_WINDOWS);
125                        FileUtils.copyURLToFile(
126                            resource,
127                            executableFile);
128                    }
129                    linkCommand = executableFile.toString();
130                }
131                else
132                {
133                    linkCommand = LN_UNIX;
134                }
135                final File[] files = earDirectory.listFiles();
136                final File explodedEarDirectory = new File(earDirectory + "-exploded");
137                explodedEarDirectory.mkdirs();
138                for (int ctr = 0; ctr < files.length; ctr++)
139                {
140                    final File file = files[ctr];
141                    final String fileName = file.getName();
142                    final Artifact artifact = artifacts.get(fileName);
143                    if (artifact == null)
144                    {
145                        if (file.isFile())
146                        {
147                            FileUtils.copyFileToDirectory(
148                                file,
149                                explodedEarDirectory);
150                        }
151                        else
152                        {
153                            final File targetDirectory = new File(explodedEarDirectory,
154                                    file.getName());
155                            targetDirectory.mkdir();
156                            FileUtils.copyDirectory(
157                                file,
158                                targetDirectory);
159                        }
160                    }
161                    else
162                    {
163                        final File targetFile = new File(explodedEarDirectory, fileName);
164                        final File explodedArtifactDirectory =
165                            new File(artifact.getFile().toString().replaceAll(
166                                    "\\." + artifact.getType(),
167                                    ""));
168                        final String command;
169                        if (!isWindows)
170                        {
171                            command = linkCommand + ' ' + explodedArtifactDirectory + ' ' +
172                                targetFile.getAbsolutePath();
173                        }
174                        else
175                        {
176                            command = linkCommand + ' ' + targetFile.getAbsolutePath() + ' ' +
177                                explodedArtifactDirectory;
178                        }
179                        if (this.getLog().isDebugEnabled())
180                        {
181                            this.getLog().debug("executing command: " + command);
182                        }
183                        // - remove the archive artifact if its already present
184                        if (targetFile.exists() && targetFile.isFile())
185                        {
186                            targetFile.delete();
187                        }
188                        if (targetFile.isDirectory())
189                        {
190                            this.getLog().info("NOT linking " + explodedArtifactDirectory + " to " + targetFile + " (already linked)");
191                        }
192                        else
193                        {
194                            this.getLog().info("linking " + explodedArtifactDirectory + " to " + targetFile);
195                            Runtime.getRuntime().exec(command);
196                        }
197                    }
198                }
199
200                final File targetFile =
201                    new File(this.deployLocation,
202                        this.project.getBuild().getFinalName() + '.' + this.project.getPackaging());
203                final String command;
204                if (!isWindows)
205                {
206                    command = linkCommand + ' ' + explodedEarDirectory + ' ' + targetFile.getAbsolutePath();
207                }
208                else
209                {
210                    command = linkCommand + ' ' + targetFile.getAbsolutePath() + ' ' + explodedEarDirectory;
211                }
212                if (this.getLog().isDebugEnabled())
213                {
214                    this.getLog().debug("executing command: " + command);
215                }
216                final File applicationContextXml = new File(explodedEarDirectory, "META-INF/application.xml");
217                FileUtils.touch(applicationContextXml);
218
219                // - remove the ear file if it exists
220                if (targetFile.isFile())
221                {
222                    targetFile.delete();
223                }
224                if (targetFile.isDirectory())
225                {
226                    this.getLog().info("NOT linking " + explodedEarDirectory + " to " + targetFile + " (already linked)");
227                }
228                else
229                {
230                    this.getLog().info("linking " + explodedEarDirectory + " to " + targetFile);
231                    Runtime.getRuntime().exec(command);
232                }
233            }
234        }
235        catch (Exception exception)
236        {
237            throw new MojoExecutionException("A failure occurred while trying to link the ear", exception);
238        }
239    }
240
241    /**
242     * Stores the root project.
243     */
244    private MavenProject rootProject;
245
246    /**
247     * Retrieves the root project (i.e. the root parent project)
248     * for this project.
249     *
250     * @return the root project.
251     * @throws MojoExecutionException
252     */
253    private MavenProject getRootProject()
254        throws MojoExecutionException
255    {
256        if (this.rootProject == null)
257        {
258            MavenProject root = null;
259            int ctr = 1;
260            for (root = this.project.getParent(); root.getParent() != null && ctr < this.rootProjectLimit; root = root.getParent(), ctr++)
261            {
262            }
263            this.rootProject = root;
264        }
265        return this.rootProject;
266    }
267
268    private static final String[] INCLUDE_ALL_POMS = new String[]{"*/**/pom.xml"};
269
270    /**
271     * Retrieves all the POMs for the given project.
272     *
273     * @return all poms found.
274     * @throws MojoExecutionException
275     */
276    private List<File> getPoms()
277        throws MojoExecutionException
278    {
279        final DirectoryScanner scanner = new DirectoryScanner();
280        scanner.setBasedir(this.getRootProject().getBasedir());
281        scanner.setIncludes(INCLUDE_ALL_POMS);
282        scanner.scan();
283        final List<File> poms = new ArrayList<File>();
284        for (int ctr = 0; ctr < scanner.getIncludedFiles().length; ctr++)
285        {
286            final File file = new File(
287                    this.getRootProject().getBasedir(),
288                    scanner.getIncludedFiles()[ctr]);
289            if (file.exists())
290            {
291                poms.add(file);
292            }
293        }
294        return poms;
295    }
296
297    /**
298     * Constructs an artifact from the given <code>pom</code> file.
299     *
300     * @return all module artifacts
301     * @throws Exception
302     */
303    private List<Artifact> getExplodedModuleArtifacts()
304        throws Exception
305    {
306        final List<Artifact> artifacts = new ArrayList<Artifact>();
307        final MavenXpp3Reader reader = new MavenXpp3Reader();
308
309        for (final File pom : this.getPoms())
310        {
311            FileReader freader = new FileReader(pom);
312            final Model model = reader.read(freader);
313            String groupId = model.getGroupId();
314            for (Parent parent = model.getParent(); groupId == null && model.getParent() != null;
315                parent = model.getParent())
316            {
317                groupId = parent.getGroupId();
318            }
319            String version = model.getVersion();
320            for (Parent parent = model.getParent(); version == null && model.getParent() != null;
321                parent = model.getParent())
322            {
323                version = parent.getVersion();
324            }
325            final Artifact artifact =
326                this.artifactFactory.createArtifact(
327                    groupId,
328                    model.getArtifactId(),
329                    version,
330                    null,
331                    model.getPackaging());
332            final File pomParent = pom.getParentFile();
333            final String finalName = model.getArtifactId() + '-' + version;
334            final File explodedDirectory = new File(pomParent, "target/" + finalName);
335            final File artifactFile = new File(explodedDirectory + "." + model.getPackaging());
336            if (explodedDirectory.isDirectory() && artifactFile.exists() &&
337                !finalName.equals(this.project.getBuild().getFinalName()))
338            {
339                artifacts.add(artifact);
340                artifact.setFile(artifactFile);
341            }
342            try
343            {
344                freader.close();
345            }
346            catch (Exception ex)
347            {
348                // Ignore
349            }
350        }
351        return artifacts;
352    }
353}