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}