001    /*
002     * Copyright 2005 Stephen J. McConnell
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
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.tools.tasks;
020    
021    import java.io.File;
022    import java.io.OutputStream;
023    import java.io.FileOutputStream;
024    import java.io.OutputStreamWriter;
025    import java.io.Writer;
026    import java.io.IOException;
027    import java.net.URI;
028    import java.net.URL;
029    
030    import net.dpml.lang.Version;
031    
032    import net.dpml.library.Module;
033    import net.dpml.library.Resource;
034    import net.dpml.library.Type;
035    
036    import net.dpml.transit.Artifact;
037    import net.dpml.transit.artifact.ArtifactNotFoundException;
038    import net.dpml.transit.link.ArtifactLinkManager;
039    
040    import org.apache.tools.ant.BuildException;
041    import org.apache.tools.ant.Project;
042    import org.apache.tools.ant.types.FileSet;
043    import org.apache.tools.ant.taskdefs.Copy;
044    import org.apache.tools.ant.taskdefs.Checksum;
045    
046    /**
047     * Execute the install phase.
048     *
049     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
050     * @version 1.2.0
051     */
052    public class InstallTask extends GenericTask
053    {    
054       /**
055        * Execute the project.
056        * @exception BuildException if a build errror occurs
057        */
058        public void execute() throws BuildException
059        {
060            installDeliverables();
061        }
062    
063        private void installDeliverables()
064        {
065            Resource resource = getResource();
066            String resourceVersion = resource.getVersion();
067            boolean snapshot = "SNAPSHOT".equals( resourceVersion );
068            boolean bootstrap = "BOOTSTRAP".equals( resourceVersion );
069            boolean validation = resource.getBooleanProperty( "project.validation.enabled", false );
070            boolean validate = !snapshot && !bootstrap && validation;
071            Type[] types = resource.getTypes();
072            if( types.length == 0 )
073            {
074                return;
075            }
076            
077            final File deliverables = getContext().getTargetDeliverablesDirectory();
078            for( int i=0; i < types.length; i++ )
079            {
080                Type type = types[i];
081                checkType( resource, type, validate );
082            }
083    
084            if( deliverables.exists() )
085            {
086                log( "Installing deliverables from [" + deliverables + "]", Project.MSG_VERBOSE );
087                final File cache = (File) getProject().getReference( "dpml.cache" );
088                log( "To cache dir [" + cache + "]", Project.MSG_VERBOSE );
089                try
090                {
091                    final FileSet fileset = new FileSet();
092                    fileset.setProject( getProject() );
093                    fileset.setDir( deliverables );
094                    fileset.createInclude().setName( "**/*" );
095                    Module parent = resource.getParent();
096                    if( null == parent )
097                    {
098                        copy( cache, fileset, true );
099                    }
100                    else
101                    {
102                        final String group = parent.getResourcePath();
103                        final File destination = new File( cache, group );
104                        copy( destination, fileset, true );
105                    }
106                }
107                catch( Throwable e )
108                {
109                    final String error = 
110                      "Unexpected error while constructing ant fileset."
111                      + "\nDeliverables dir: " + deliverables;
112                    throw new BuildException( error, e );
113                }
114            }
115        }
116        
117        private void checkType( Resource resource, Type type, boolean validate )
118        {
119            //
120            // Check that the project has actually built the resource
121            // type that it declares
122            //
123    
124            String id = type.getID();
125            String filename = getContext().getLayoutFilename( id );
126            final File deliverables = getContext().getTargetDeliverablesDirectory();
127            File group = new File( deliverables, id + "s" );
128            File target = new File( group, filename );
129            if( !target.exists() && !id.equalsIgnoreCase( "null" ) )
130            {
131                final String error = 
132                  "Project [" 
133                  + resource 
134                  + "] declares that it produces the resource type ["
135                  + id
136                  + "] however no artifacts of that type are present in the target deliverables directory.";
137                throw new BuildException( error, getLocation() );
138            }
139    
140            //
141            // If the type declares an alias then construct a link 
142            // and add the link to the deliverables directory as part of 
143            // install process.
144            //
145    
146            Version version = type.getVersion();
147            if( null != version )
148            {
149                try
150                {
151                    Artifact artifact = resource.getArtifact( id );
152                    String uri = artifact.toURI().toASCIIString();
153                    String link = null;
154                    if( Version.NULL_VERSION.equals( version ) )
155                    {
156                        link = resource.getName() + "." + id + ".link";
157                    }
158                    else
159                    {
160                        link = resource.getName()
161                        + "-"
162                        + version.getMajor()
163                        + "." 
164                        + version.getMinor()
165                        + "."
166                        + id + ".link";
167                    }
168                    File out = new File( group, link );
169                    boolean flag = true;
170                    if( out.exists() )
171                    {
172                        ArtifactLinkManager manager = new ArtifactLinkManager();
173                        URI enclosed = manager.getTargetURI( out.toURI() );
174                        if( artifact.toURI().equals( enclosed ) )
175                        {
176                            flag = false;
177                        }
178                    }
179                    
180                    if( flag )
181                    {
182                        final String message = 
183                          link.toString()
184                          + "\n  target: " 
185                          +  uri.toString();
186                        log( message, Project.MSG_VERBOSE );
187                        out.createNewFile();
188                        final OutputStream output = new FileOutputStream( out );
189                        final Writer writer = new OutputStreamWriter( output );
190                        writer.write( uri );
191                        writer.close();
192                        output.close();
193                    }
194                }
195                catch( Exception e )
196                {
197                    final String error = 
198                      "Internal error while attempting to create a link for the resource type ["
199                      + id 
200                      + "] in project ["
201                      + resource
202                      + "].";
203                    throw new BuildException( error, e, getLocation() );
204                }
205            }
206    
207            if( validate )
208            {
209                validateType( resource, type, target );
210            }
211        }
212        
213        private void validateType( Resource resource, Type type, File target )
214        {
215            String id = type.getID();
216            try
217            {
218                Artifact artifact = resource.getArtifact( id );
219                URL url = artifact.toURL();
220                File file = (File) url.getContent( new Class[]{File.class} );
221                if( file.exists() )
222                {
223                    log( "validating " + target.getName() );
224                    compare( file, target, id );
225                }
226            }
227            catch( ArtifactNotFoundException anfe )
228            {
229                // continue as there is nothing to compare with
230            }
231            catch( IOException ioe )
232            {
233                final String error =
234                  "IO error while attempting to cross-check resource type: " + id
235                  + "\n" + ioe.toString();
236                throw new BuildException( error, ioe, getLocation() );
237            }
238        }
239        
240        private void compare( File old, File target, String id )
241        {
242            String oldValue = getChecksum( old );
243            String newValue = getChecksum( target );
244            if( !oldValue.equals( newValue ) )
245            {
246                String path = getContext().getLayoutFilename( id );
247                final String error =
248                  "A versioned resource created in this build has a different MD5 signature "
249                  + "compared to an existing resource of the same name in the cache directory. "
250                  + "If the cached resource is a published resource a possibility exists that "
251                  + "this build artifact will be introducing a modification to an existing published "
252                  + "contract. If the resource has not been published then you can rebuild without "
253                  + "deliverable validation.  Otherwise, consider assigning an alternative "
254                  + "(non-conflicting) version identifier."
255                  + "\n"
256                  + "\n\tProduced Type: " + path
257                  + "\n\tCached Resource: " + getCanonicalPath( old )
258                  + "\n";
259                throw new BuildException( error, getLocation() );
260            }
261        }
262        
263        private String getCanonicalPath( File file )
264        {
265            try
266            {
267                return file.getCanonicalPath();
268            }
269            catch( IOException e )
270            {
271                final String error = 
272                  "Internal error while attempting to resolve a canonical path for the file: " + file;
273                throw new BuildException( error, e, getLocation() );
274            }
275        }
276        
277        private String getChecksum( File file )
278        {
279            final String key = "checksum.property." + file.toString();
280            final Checksum checksum = (Checksum) getProject().createTask( "checksum" );
281            checksum.setTaskName( getTaskName() );
282            checksum.setFile( file );
283            checksum.setProperty( key );
284            checksum.init();
285            checksum.execute();
286            return getProject().getProperty( key );
287        }
288        
289       /**
290        * Utility operation to copy a fileset to a destination directory.
291        * @param destination the destination directory
292        * @param fileset the fileset to copy
293        * @param preserve the preserve timestamp flag
294        */
295        public void copy( final File destination, final FileSet fileset, boolean preserve )
296        {
297            mkDir( destination );
298            final Copy copy = (Copy) getProject().createTask( "copy" );
299            copy.setTaskName( getTaskName() );
300            copy.setPreserveLastModified( preserve );
301            copy.setTodir( destination );
302            copy.addFileset( fileset );
303            copy.setOverwrite( true ); // required for filtered deliverables
304            copy.init();
305            copy.execute();
306        }
307    }