001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License.
018     */
019    
020    package org.apache.geronimo.mavenplugins.car;
021    
022    import org.apache.geronimo.kernel.config.InvalidConfigException;
023    import org.apache.geronimo.kernel.config.NoSuchConfigException;
024    import org.apache.geronimo.kernel.config.ConfigurationData;
025    import org.apache.geronimo.kernel.repository.Artifact;
026    import org.apache.geronimo.kernel.repository.ArtifactManager;
027    import org.apache.geronimo.kernel.repository.ArtifactResolver;
028    import org.apache.geronimo.kernel.repository.DefaultArtifactManager;
029    import org.apache.geronimo.kernel.repository.Environment;
030    import org.apache.geronimo.kernel.repository.FileWriteMonitor;
031    import org.apache.geronimo.kernel.repository.Dependency;
032    import org.apache.geronimo.kernel.repository.WritableListableRepository;
033    import org.apache.geronimo.kernel.repository.Repository;
034    import org.apache.geronimo.system.repository.Maven2Repository;
035    import org.apache.geronimo.system.configuration.RepositoryConfigurationStore;
036    import org.apache.geronimo.system.resolver.ExplicitDefaultArtifactResolver;
037    
038    import org.codehaus.mojo.pluginsupport.dependency.DependencyTree;
039    
040    import org.apache.maven.artifact.repository.ArtifactRepository;
041    
042    import java.io.File;
043    import java.io.FileInputStream;
044    import java.io.IOException;
045    import java.io.InputStream;
046    import java.io.BufferedInputStream;
047    import java.io.BufferedReader;
048    import java.io.FileReader;
049    import java.io.InputStreamReader;
050    
051    import java.util.Iterator;
052    import java.util.HashSet;
053    import java.util.LinkedHashSet;
054    import java.util.Set;
055    import java.util.Collections;
056    import java.util.HashMap;
057    import java.util.zip.ZipEntry;
058    import java.util.jar.JarFile;
059    
060    import org.codehaus.plexus.util.FileUtils;
061    
062    /**
063     * Installs Geronimo module CAR files into a target repository to support assembly.
064     *
065     * @version $Rev: 524717 $ $Date: 2007-04-01 23:39:19 -0400 (Sun, 01 Apr 2007) $
066     * @goal install-modules
067     */
068    public class InstallModulesMojo
069        extends AbstractCarMojo
070    {
071        /**
072         * The location of the target repository.
073         *
074         * @parameter expression="${project.build.directory}/repository"
075         * @required
076         */
077        private File targetRepositoryDirectory = null;
078    
079        /**
080         * Configuration to be installed specified as groupId/artifactId/version/type
081         * if none specified, plugin will install all dependencies of type "car"
082         *
083         * @parameter
084         * @optional
085         */
086        private String artifact = null;
087    
088        /**
089         * Location of the source repository for the dependencies
090         *
091         * @parameter expression="${localRepository}"
092         * @required
093         */
094        private ArtifactRepository sourceRepository = null;
095    
096        /**
097         * The location where the properties mapping will be generated.
098         *
099         * @parameter expression="${project.build.directory}/explicit-versions.properties"
100         * @required
101         */
102        private File explicitResolutionProperties = null;
103    
104        /**
105         * The Geronimo repository artifact resolver.
106         * <p/>
107         * <p/>
108         * Using a custom name here to prevent problems that happen when Plexus
109         * injects the Maven resolver into the base-class.
110         * </p>
111         */
112        private ArtifactResolver geronimoArtifactResolver;
113    
114        private WritableListableRepository targetRepo;
115    
116        private RepositoryConfigurationStore targetStore;
117    
118        private WritableListableRepository sourceRepo;
119    
120        private RepositoryConfigurationStore sourceStore;
121    
122        /**
123         * Set of artifacts which have already been installed, so we can skip any processing.
124         */
125        private Set installedArtifacts = new HashSet();
126    
127        protected void doExecute() throws Exception {
128            DependencyTree dependencies = dependencyHelper.getDependencies(project);
129            generateExplicitVersionProperties(explicitResolutionProperties, dependencies);
130    
131            //
132            // TODO: Check if we need to use the Maven2RepositoryAdapter here or not...
133            //
134    
135            Maven2RepositoryAdapter.ArtifactLookup lookup = new ArtifactLookupImpl(new HashMap());
136            sourceRepo = new Maven2RepositoryAdapter(dependencies, lookup);
137            // sourceRepo = new Maven2RepositoryAdapter(new File(sourceRepository.getBasedir()));
138            sourceStore = new RepositoryConfigurationStore(sourceRepo);
139    
140            FileUtils.forceMkdir(targetRepositoryDirectory);
141    
142            targetRepo = new Maven2Repository(targetRepositoryDirectory);
143            targetStore = new RepositoryConfigurationStore(targetRepo);
144    
145            ArtifactManager artifactManager = new DefaultArtifactManager();
146            geronimoArtifactResolver = new ExplicitDefaultArtifactResolver(
147                    explicitResolutionProperties.getPath(),
148                    artifactManager,
149                    Collections.singleton(sourceRepo),
150                    null);
151    
152            if (artifact != null) {
153                install(Artifact.create(artifact));
154            } else {
155                Iterator iter = getDependencies().iterator();
156                while (iter.hasNext()) {
157    
158                    Artifact artifact = mavenToGeronimoArtifact((org.apache.maven.artifact.Artifact) iter.next());
159                    if (isModuleArtifact(artifact)) {
160                        install(artifact);
161                    }
162                }
163            }
164        }
165    
166        /**
167         * Retrieves all artifact dependencies.
168         *
169         * @return A HashSet of artifacts
170         */
171        protected Set getDependencies() {
172            Set dependenciesSet = new HashSet();
173    
174            org.apache.maven.artifact.Artifact artifact = project.getArtifact();
175            if (artifact != null && artifact.getFile() != null) {
176                dependenciesSet.add(artifact);
177            }
178    
179            Set projectArtifacts = project.getArtifacts();
180            if (projectArtifacts != null) {
181                dependenciesSet.addAll(projectArtifacts);
182            }
183    
184            return dependenciesSet;
185        }
186    
187        /**
188         * Install the given artifact into the target Geronimo repository.
189         *
190         * @param artifact The artifact to be installed; must not be null
191         * @throws Exception Failed to install artifact
192         */
193        private void install(final Artifact artifact) throws Exception {
194            assert artifact != null;
195    
196            if (installedArtifacts.contains(artifact)) {
197                log.debug("Skipping artifact; already installed: " + artifact);
198            } else {
199                // The artifact must exist in the source repository
200                if (!sourceRepo.contains(artifact)) {
201                    throw new Exception("Missing artifact in source repository: " + artifact);
202                }
203    
204                if (isModuleArtifact(artifact)) {
205                    installModule(artifact);
206                } else {
207                    installDependency(artifact);
208                }
209            }
210        }
211    
212        /**
213         * Install a Geornimo module artifact.
214         *
215         * @param artifact The Geronimo module artifact to be installed; must not be null, must be a module
216         * @throws Exception Failed to insall Geronimo module artifact
217         */
218        private void installModule(final Artifact artifact) throws Exception {
219            assert artifact != null;
220            assert isModuleArtifact(artifact);
221    
222            boolean install = true;
223    
224            // The source store must contain the module artifact
225            if (!sourceStore.containsConfiguration(artifact)) {
226                throw new Exception("Missing module artifact in source repository: " + artifact);
227            }
228    
229            // If the target store already contains the module, check if we need to reinstall it
230            if (targetStore.containsConfiguration(artifact)) {
231                if (hasModuleChanged(artifact)) {
232                    log.debug("Old module exists in target store; uninstalling: " + artifact);
233                    targetStore.uninstall(artifact);
234                } else {
235                    log.debug("Same module exists in target store; skipping: " + artifact);
236                    install = false;
237                }
238            }
239    
240            // Copy the configuration into the target configuration store
241            if (install) {
242                log.info("Installing module: " + artifact);
243    
244                File file = sourceRepo.getLocation(artifact);
245                InputStream input = new BufferedInputStream(new FileInputStream(file));
246    
247                try {
248                    FileWriteMonitor monitor = new FileWriteMonitor() {
249                        public void writeStarted(final String file, final int bytes) {
250                            log.debug("Installing module: " + file + " (" + bytes + " bytes)");
251                        }
252    
253                        public void writeProgress(int bytes) {
254                            // empty
255                        }
256    
257                        public void writeComplete(int bytes) {
258                            // empty
259                        }
260                    };
261    
262                    targetStore.install(input, (int) file.length(), artifact, monitor);
263    
264                    installedArtifacts.add(artifact);
265                }
266                finally {
267                    input.close();
268                }
269            }
270    
271            // Install all dependencies of this module
272            installModuleDependencies(artifact);
273        }
274    
275        /**
276         * Install all of the dependencies of the given Geronimo module artifact.
277         *
278         * @param artifact The Geronimo module artifact to be installed; must not be null, must be a module
279         * @throws Exception Failed to install Geronimo module dependencies
280         */
281        private void installModuleDependencies(final Artifact artifact) throws Exception {
282            assert artifact != null;
283            assert isModuleArtifact(artifact);
284    
285            log.debug("Installing module dependencies for artifact: " + artifact);
286    
287            try {
288                ConfigurationData config = targetStore.loadConfiguration(artifact);
289                Environment env = config.getEnvironment();
290                LinkedHashSet deps = new LinkedHashSet();
291    
292                Iterator iter = env.getDependencies().iterator();
293                while (iter.hasNext()) {
294                    Dependency dep = (Dependency) iter.next();
295                    deps.add(dep.getArtifact());
296                }
297    
298                installDependencies(deps);
299            }
300            catch (IOException e) {
301                throw new InvalidConfigException("Unable to load module: " + artifact, e);
302            }
303            catch (NoSuchConfigException e) {
304                throw new InvalidConfigException("Unable to load module: " + artifact, e);
305            }
306        }
307    
308        /**
309         * Install a dependency artifact into the Geronimo repository.
310         *
311         * @param artifact The artifact to be installed; must not be null, or a module artifact
312         * @throws Exception Failed to install artifact dependencies
313         */
314        private void installDependency(final Artifact artifact) throws Exception {
315            assert artifact != null;
316            assert !isModuleArtifact(artifact);
317    
318            boolean install = true;
319    
320            // If the dep already exists, then check if we need to reinstall it
321            if (targetRepo.contains(artifact)) {
322                if (hasDependencyChanged(artifact)) {
323                    File file = targetRepo.getLocation(artifact);
324                    log.debug("Old dependency exists in target repo; deleting: " + file);
325                    FileUtils.forceDelete(file);
326                } else {
327                    log.debug("Same dependency exists in target repo; skipping: " + artifact);
328                    install = false;
329                }
330            }
331    
332            if (install) {
333                log.info("Installing dependency: " + artifact);
334    
335                // Copy the artifact into the target repo
336                File file = sourceRepo.getLocation(artifact);
337                InputStream input = new BufferedInputStream(new FileInputStream(file));
338                try {
339                    FileWriteMonitor monitor = new FileWriteMonitor() {
340                        public void writeStarted(final String file, final int bytes) {
341                            log.debug("Copying dependency: " + file + " (" + bytes + " bytes)");
342                        }
343    
344                        public void writeProgress(int bytes) {
345                            // empty
346                        }
347    
348                        public void writeComplete(int bytes) {
349                            // empty
350                        }
351                    };
352    
353                    targetRepo.copyToRepository(input, (int) file.length(), artifact, monitor);
354    
355                    installedArtifacts.add(artifact);
356                }
357                finally {
358                    input.close();
359                }
360            }
361    
362            // Install all dependencies of this artifact
363            installDependencies(sourceRepo.getDependencies(artifact));
364        }
365    
366        /**
367         * Install a set of dependency artifacts into the Geronimo repository.
368         *
369         * @param dependencies The set of artifacts to be installed; must not be null.
370         * @throws Exception Failed to install artifacts
371         */
372        private void installDependencies(final Set/*<Artifact>*/ dependencies) throws Exception {
373            assert dependencies != null;
374    
375            Set resolved = geronimoArtifactResolver.resolveInClassLoader(dependencies);
376            Iterator iter = resolved.iterator();
377    
378            while (iter.hasNext()) {
379                Artifact a = (Artifact) iter.next();
380                install(a);
381            }
382        }
383    
384        /**
385         * Check if a module has changed by comparing the checksum in the source and target repos.
386         *
387         * @param module The module to inspect; must not be null.
388         * @return Returns true if the module has changed
389         * @throws IOException Failed to load checksum
390         */
391        private boolean hasModuleChanged(final Artifact module) throws IOException {
392            assert module != null;
393    
394            String sourceChecksum = loadChecksum(sourceRepo, module);
395            String targetChecksum = loadChecksum(targetRepo, module);
396    
397            return !sourceChecksum.equals(targetChecksum);
398        }
399    
400        /**
401         * Load the <tt>config.ser</tt> checksum for the given artifact.
402         *
403         * @param repo     The repository to resolve the artifacts location; must not be null.
404         * @param artifact The artifact to retrieve a checksum for; must not be null.
405         * @return Thr artifacts checksum
406         * @throws IOException Failed to load checksums
407         */
408        private String loadChecksum(final Repository repo, final Artifact artifact) throws IOException {
409            assert repo != null;
410            assert artifact != null;
411    
412            File file = repo.getLocation(artifact);
413            BufferedReader reader;
414    
415            if (file.isDirectory()) {
416                File serFile = new File(file, "META-INF/config.ser.sha1");
417                reader = new BufferedReader(new FileReader(serFile));
418            } else {
419                JarFile jarFile = new JarFile(file);
420                ZipEntry entry = jarFile.getEntry("META-INF/config.ser.sha1");
421                reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(entry)));
422            }
423    
424            String checksum = reader.readLine();
425            reader.close();
426    
427            return checksum;
428        }
429    
430        /**
431         * Check if a dependency has changed by checking the file size and last modified for source and target.
432         *
433         * @param artifact The artifact to check; must not be null
434         * @return True if the dependency has changed
435         */
436        private boolean hasDependencyChanged(final Artifact artifact) {
437            assert artifact != null;
438    
439            File source = sourceRepo.getLocation(artifact);
440            File target = targetRepo.getLocation(artifact);
441    
442            return (source.length() != target.length()) ||
443                    (source.lastModified() > target.lastModified());
444        }
445    }