package org.codehaus.mojo.unix.util; /* * The MIT License * * Copyright 2009 The Codehaus. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is furnished to do * so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import fj.*; import static fj.Function.*; import fj.data.*; import static fj.data.List.*; import static fj.data.Option.*; import fj.pre.*; import org.codehaus.mojo.unix.java.*; import static org.codehaus.mojo.unix.java.FileF.*; import static org.codehaus.mojo.unix.util.FileModulator.*; import org.codehaus.plexus.util.*; import java.io.*; import static java.lang.Math.*; import java.util.concurrent.*; /** * <h2>Single Format</h2> * <ul> * <li>Files use their "native" names.</li> * <li>Used for <code>packaging=FORMAT</code></li> * </ul> * <pre> * scripts/ * |-- preinstall * |-- postinstall <- Common install actions for all packages * `-- postinstall-a <- Specific install actions for the "a" package * </pre> * <p/> * <h2>Multiple Formats</h2> * <ul> * <li>Files do not use their "native" names, has to be one of pre-install, post-install, pre-remove, post-remove.</li> * <li>Use for attached executions. Note that the format always has to be present because of how the attached mojos * work. They work independently and there is no way of telling if more that one attached mojo will run or not.</li> * </ul> * <pre> * scripts/ * |-- post-install Common for all packages, all formats * |-- post-install-a Common for package "a" in all formats * |-- post-install-a-deb * |-- post-install-a-pkg * |-- post-install-a-rpm * |-- post-install-rpm Common for all packages in "rpm" format * |-- post-install-b-deb * |-- post-install-b-pkg * `-- post-install-b-rpm * </pre> * * @author <a href="mailto:trygvis@codehaus.org">Trygve Laugstøl</a> * @version $Id$ */ public final class ScriptUtil { private final ScriptFile preInstall; private final ScriptFile postInstall; private final ScriptFile preRemove; private final ScriptFile postRemove; private final List<String> customScripts; public enum Strategy { SINGLE( ScriptFile.specificNameF ), MULTIPLE( ScriptFile.commonNameF ); private final F<ScriptFile, String> accessor; Strategy( F<ScriptFile, String> accessor ) { this.accessor = accessor; } } private final static class ScriptFile { String commonName; String specificName; private ScriptFile( String commonName, String specificName ) { this.commonName = commonName; this.specificName = specificName; } public File toFile( File toDir ) { return new File( toDir, specificName ); } public static F<ScriptFile, String> commonNameF = new F<ScriptFile, String>() { public String f( ScriptFile scriptFile ) { return scriptFile.commonName; } }; public static F<ScriptFile, String> specificNameF = new F<ScriptFile, String>() { public String f( ScriptFile scriptFile ) { return scriptFile.specificName; } }; } public ScriptUtil( String preInstall, String postInstall, String preRemove, String postRemove ) { Validate.validateNotNull( preInstall, postInstall, preRemove, postRemove ); this.preInstall = new ScriptFile( "pre-install", preInstall ); this.postInstall = new ScriptFile( "post-install", postInstall ); this.preRemove = new ScriptFile( "pre-remove", preRemove ); this.postRemove = new ScriptFile( "post-remove", postRemove ); customScripts = nil(); } private ScriptUtil( ScriptFile preInstall, ScriptFile postInstall, ScriptFile preRemove, ScriptFile postRemove, List<String> customScripts ) { Validate.validateNotNull( preInstall, postInstall, preRemove, postRemove, customScripts ); this.preInstall = preInstall; this.postInstall = postInstall; this.preRemove = preRemove; this.postRemove = postRemove; this.customScripts = customScripts; } public ScriptUtil customScript( String specificName ) { return new ScriptUtil( preInstall, postInstall, preRemove, postRemove, customScripts.cons( specificName ) ); } public final static class Execution { public final Option<Callable<File>> preInstall; public final Option<Callable<File>> postInstall; public final Option<Callable<File>> preRemove; public final Option<Callable<File>> postRemove; public final List<Callable<File>> customScripts; public Execution( Option<Callable<File>> preInstall, Option<Callable<File>> postInstall, Option<Callable<File>> preRemove, Option<Callable<File>> postRemove, List<Callable<File>> customScripts ) { this.preInstall = preInstall; this.postInstall = postInstall; this.preRemove = preRemove; this.postRemove = postRemove; this.customScripts = customScripts; } public Result execute() throws Exception { List<File> scripts = nil(); for ( Callable<File> customScript : customScripts ) { scripts = scripts.cons( customScript.call() ); } return new Result( preInstall.isSome() ? some( preInstall.some().call() ) : Option.<File>none(), postInstall.isSome() ? some( postInstall.some().call() ) : Option.<File>none(), preRemove.isSome() ? some( preRemove.some().call() ) : Option.<File>none(), postRemove.isSome() ? some( postRemove.some().call() ) : Option.<File>none(), scripts ); } } public final static class Result { public final Option<File> preInstall; public final Option<File> postInstall; public final Option<File> preRemove; public final Option<File> postRemove; public final List<File> customScripts; public Result( Option<File> preInstall, Option<File> postInstall, Option<File> preRemove, Option<File> postRemove, List<File> customScripts ) { this.preInstall = preInstall; this.postInstall = postInstall; this.preRemove = preRemove; this.postRemove = postRemove; this.customScripts = customScripts; } } public Execution createExecution( String id, String format, File scripts, File toDir, Strategy strategy ) { F<String, List<String>> expand = curry( modulatePath, id, format ); F<ScriptFile, List<String>> f = compose( expand, strategy.accessor ); F<String, File> newScriptsFile = curry( FileF.newFile, scripts ); List<File> preInstallFiles = f.f( preInstall ).map( newScriptsFile ).filter( canRead ); List<File> postInstallFiles = f.f( postInstall ) .map( newScriptsFile ).filter( canRead ); List<File> preRemoveFiles = f.f( preRemove ).map( newScriptsFile ).filter( canRead ); List<File> postRemoveFiles = f.f( postRemove ).map( newScriptsFile ).filter( canRead ); List<Callable<File>> customFiles = nil(); for ( String customScript : customScripts ) { F<ScriptFile, List<String>> toFilesCustom = compose( expand, ScriptFile.specificNameF ); List<File> list = toFilesCustom.f( new ScriptFile( null, customScript ) ). map( newScriptsFile ). filter( canRead ); if ( list.isNotEmpty() ) { customFiles = customFiles.cons( curry( copyFiles, new File( toDir, customScript ) ).f( list ) ); } } return new Execution( iif( List.<File>isNotEmpty_(), preInstallFiles ).map( curry( copyFiles, preInstall.toFile( toDir ) ) ), iif( List.<File>isNotEmpty_(), postInstallFiles ).map( curry( copyFiles, postInstall.toFile( toDir ) ) ), iif( List.<File>isNotEmpty_(), preRemoveFiles ).map( curry( copyFiles, preRemove.toFile( toDir ) ) ), iif( List.<File>isNotEmpty_(), postRemoveFiles ).map( curry( copyFiles, postRemove.toFile( toDir ) ) ), customFiles ); } F2<File, File, Callable<File>> customToCallable = new F2<File, File, Callable<File>>() { public Callable<File> f( final File toDir, final File file ) { return new Callable<File>() { public File call() throws Exception { return copyFiles( new File( toDir, file.getName() ), single( file ) ); } }; } }; private static File copyFiles( File to, List<File> files ) throws IOException { Show.listShow( Show.<File>anyShow() ).println( files ); FileOutputStream fos = null; FileInputStream fis = null; try { long lastModified = 0; if ( !to.getParentFile().isDirectory() ) { FileUtils.forceMkdir( to.getParentFile() ); } fos = new FileOutputStream( to ); for ( File file : files ) { fis = new FileInputStream( file ); IOUtil.copy( fis, fos ); fis.close(); lastModified = max( lastModified, file.lastModified() ); } fos.close(); // TODO: Check that the files differ to prevent writing new files. if ( !to.setLastModified( lastModified ) ) { throw new IOException( "Unable to set last modified on '" + to.getAbsolutePath() + "'." ); } return to; } finally { IOUtil.close( fos ); IOUtil.close( fis ); } } private static F2<File, List<File>, Callable<File>> copyFiles = new F2<File, List<File>, Callable<File>>() { public Callable<File> f( final File toFile, final List<File> files ) { return new Callable<File>() { public File call() throws Exception { return copyFiles( toFile, files ); } }; } }; }