Java tutorial
package com.isomorphic.maven.mojo; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import static com.isomorphic.maven.packaging.License.ANALYTICS_MODULE; import static com.isomorphic.maven.packaging.License.ENTERPRISE; import static com.isomorphic.maven.packaging.License.MESSAGING_MODULE; import static com.isomorphic.maven.packaging.License.POWER; import java.io.File; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.filefilter.FileFilterUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.io.filefilter.TrueFileFilter; import org.apache.commons.io.filefilter.WildcardFileFilter; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.maven.model.Model; import org.apache.maven.model.building.DefaultModelBuildingRequest; import org.apache.maven.model.building.ModelBuilder; import org.apache.maven.model.building.ModelBuildingException; import org.apache.maven.model.building.ModelBuildingRequest; import org.apache.maven.model.building.ModelBuildingResult; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Server; import org.apache.maven.settings.Settings; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.isomorphic.maven.packaging.Distribution; import com.isomorphic.maven.packaging.Downloads; import com.isomorphic.maven.packaging.License; import com.isomorphic.maven.packaging.Module; import com.isomorphic.maven.packaging.Product; /** * A base class meant to deal with prerequisites to install / deploy goals, which are basically * to resolve the files in a given distribution to a collection of Maven artifacts suitable for * installation or deployment to some Maven repository. * <p/> * The resulting artifacts are provided to this object's {@link #doExecute(Set)} method. */ public abstract class AbstractPackagerMojo extends AbstractMojo { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractPackagerMojo.class); /** * If true, the optional analytics module (bundled and distributed separately) has been licensed and should be * downloaded with the distribution specified by {@link #license}. * * @since 1.0.0 */ @Parameter(property = "includeAnalytics", defaultValue = "false") protected Boolean includeAnalytics; /** * The date on which the Isomorphic build was made publicly available at * <a href="http://www.smartclient.com/builds/">http://www.smartclient.com/builds/</a>, * in yyyy-MM-dd format. e.g., 2013-25-12. Used to determine both remote and local file locations. * <br/> * <b>Default value is</b>: <tt>The current date</tt>. * * @since 1.0.0 */ @Parameter(property = "buildDate") protected String buildDate; /** * The Isomorphic version number of the specified {@link #product}. e.g., 9.1d, 4.0p. * Used to determine both remote and local file locations. * * @since 1.0.0 */ @Parameter(property = "buildNumber", required = true) protected String buildNumber; /** * Typically one of: LGPL, EVAL, PRO, POWER, ENTERPRISE. Although it is also valid to * specify optional modules ANALYTICS_MODULE or MESSAGING_MODULE, generally * prefer the {@link #includeAnalytics} / {@link #includeMessaging} properties, respectively, to cause * the optional modules to be included with the base installation / deployment. * * @since 1.0.0 */ @Parameter(property = "license", required = true) protected License license; /** * If true, the optional messaging module (bundled and distributed separately) has been licensed and should be * downloaded with the distribution specified by {@link #license}. * * @since 1.0.0 */ @Parameter(property = "includeMessaging", defaultValue = "false") protected Boolean includeMessaging; /** * If true, any file previously downloaded / unpacked will be overwritten with this execution. Useful in * the case of an interrupted download. Note that this setting has no effect on unzip operations. * * @since 1.0.0 */ @Parameter(property = "overwrite", defaultValue = "false") protected Boolean overwrite; /** * If true, no attempt is made to download any remote distribution. Files will be loaded instead * from a path constructed of the following parts (e.g., C:/downloads/SmartGWT/PowerEdition/4.1d/2013-12-25/zip): * <p/> * <ul> * <li>{@link #workdir}</li> * <li>{@link #product}</li> * <li>{@link #license}</li> * <li>{@link #buildNumber}</li> * <li>{@link #buildDate}</li> * <li>"zip"</li> * </ul> * * @since 1.0.0 */ @Parameter(property = "skipDownload", defaultValue = "false") protected Boolean skipDownload; /** * If true, no attempt it made to extract the contents of any distribution. Only useful in the case * where some manual intervention is required between download and another step. For example, it * would be possible to first run the download goal, manipulate the version number of some dependency in some POM, * and then run the install goal with skipExtraction=false to prevent the modified POM from being overwritten. * <p/> * This is the kind of thing that should generally be avoided, however. */ @Parameter(property = "skipExtraction", defaultValue = "false") protected Boolean skipExtraction; /** * One of SMARTGWT, SMARTCLIENT, or SMARTGWT_MOBILE. * * @since 1.0.0 */ @Parameter(property = "product", defaultValue = "SMARTGWT") protected Product product; /** * If true, artifacts should be * <a href="http://books.sonatype.com/mvnref-book/reference/pom-relationships-sect-pom-syntax.html#pom-reationships-sect-versions">versioned</a> * with the 'SNAPSHOT' qualifier, in the case of development builds only. The setting has no effect on patch builds. * <p/> * If false, each artifact's POM file is modified to remove the unwanted qualifier. This can be useful if you need to deploy a development * build to a production environment. * * @since 1.0.0 */ @Parameter(property = "snapshots", defaultValue = "true") protected Boolean snapshots; /** * The path to some directory that is to be used for storing downloaded files, working copies, and so on. * * @since 1.0.0 */ @Parameter(property = "workdir", defaultValue = "${java.io.tmpdir}/${project.artifactId}") protected File workdir; /** * The id of a <a href="http://maven.apache.org/settings.html#Servers">server configuration</a> containing authentication credentials for the * smartclient.com website, used to download licensed products. * <p/> * Not strictly necessary for unprotected (LGPL) distributions. * * @since 1.0.0 */ @Parameter(property = "serverId", defaultValue = "smartclient-developer") protected String serverId; @Parameter(readonly = true, defaultValue = "${repositorySystemSession}") protected RepositorySystemSession repositorySystemSession; @Component protected ModelBuilder modelBuilder; @Component protected MavenProject project; @Component protected RepositorySystem repositorySystem; @Component protected Settings settings; /** * The point where a subclass is able to manipulate the collection of artifacts prepared for it by this object's * {@link #execute()} method. * * @param artifacts A collection of Maven artifacts resulting from the download and preparation of a supported Isomorphic SDK. * @throws MojoExecutionException When any fatal error occurs. e.g., there is no distribution to work with. * @throws MojoFailureException When any non-fatal error occurs. e.g., documentation cannot be copied to some other folder. */ public abstract void doExecute(Set<Module> artifacts) throws MojoExecutionException, MojoFailureException; /** * Provides some initialization and validation steps around the collection and transformation of an Isomorphic SDK. * * @throws MojoExecutionException When any fatal error occurs. * @throws MojoFailureException When any non-fatal error occurs. */ public void execute() throws MojoExecutionException, MojoFailureException { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false); if (buildDate == null) { buildDate = dateFormat.format(new Date()); } try { dateFormat.parse(buildDate); } catch (ParseException e) { throw new MojoExecutionException( String.format("buildDate '%s' must take the form yyyy-MM-dd.", buildDate)); } LOGGER.debug("buildDate set to '{}'", buildDate); String buildNumberFormat = "\\d.*\\.\\d.*[d|p]"; if (!buildNumber.matches(buildNumberFormat)) { throw new MojoExecutionException( String.format("buildNumber '%s' must take the form [major].[minor].[d|p]. e.g., 4.1d", buildNumber, buildNumberFormat)); } File basedir = FileUtils.getFile(workdir, product.toString(), license.toString(), buildNumber, buildDate); //add optional modules to the list of downloads List<License> licenses = new ArrayList<License>(); licenses.add(license); if (license == POWER || license == ENTERPRISE) { if (includeAnalytics) { licenses.add(ANALYTICS_MODULE); } if (includeMessaging) { licenses.add(MESSAGING_MODULE); } } //collect the maven artifacts and send them along to the abstract method Set<Module> artifacts = collect(licenses, basedir); File bookmarkable = new File(basedir.getParent(), "latest"); LOGGER.info("Copying distribution to '{}'", bookmarkable.getAbsolutePath()); try { FileUtils.forceMkdir(bookmarkable); FileUtils.cleanDirectory(bookmarkable); FileUtils.copyDirectory(basedir, bookmarkable, FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("zip"))); } catch (IOException e) { throw new MojoFailureException("Unable to copy distribution contents", e); } String[] executables = { "bat", "sh", "command" }; Collection<File> scripts = FileUtils.listFiles(basedir, executables, true); scripts.addAll(FileUtils.listFiles(bookmarkable, executables, true)); for (File script : scripts) { script.setExecutable(true); LOGGER.debug("Enabled execute permissions on file '{}'", script.getAbsolutePath()); } doExecute(artifacts); } /** * Download the specified distributions, if necessary, extract resources from them, and use the results to create Maven artifacts as appropriate: * <p/> * Try to install all of the main artifacts - e.g., those found in lib/*.jar and assembly/*.zip <br/> * Try to match main artifacts to 'subartifacts' by name and attach them (with classifiers as necessary) * * @param downloads The list of licenses top be included in the distribution. Multiple licenses are only allowed to support the inclusion of optional modules. * @param basedir The directory into which results should be written * @return A collection of Maven artifacts resulting from the download and preparation of a supported Isomorphic SDK. * @throws MojoExecutionException When any fatal error occurs. */ private Set<Module> collect(List<License> downloads, File basedir) throws MojoExecutionException { //allow execution to proceed without login credentials - it may be that they're not required Server server = settings.getServer(serverId); String username = null; String password = null; if (server != null) { username = server.getUsername(); password = server.getPassword(); } else { LOGGER.warn("No server configured with id '{}'. Will be unable to authenticate.", serverId); } UsernamePasswordCredentials credentials = null; if (username != null) { credentials = new UsernamePasswordCredentials(username, password); } File downloadTo = new File(basedir, "zip"); downloadTo.mkdirs(); Downloads downloadManager = new Downloads(credentials); downloadManager.setToFolder(downloadTo); downloadManager.setProxyConfiguration(settings.getActiveProxy()); downloadManager.setOverwriteExistingFiles(overwrite); File[] existing = downloadTo.listFiles(); List<Distribution> distributions = new ArrayList<Distribution>(); try { if (!skipDownload) { distributions.addAll( downloadManager.fetch(product, buildNumber, buildDate, downloads.toArray(new License[0]))); } else if (existing != null) { LOGGER.info("Creating local distribution from '{}'", downloadTo.getAbsolutePath()); Distribution distribution = Distribution.get(product, license, buildNumber, buildDate); distribution.getFiles().addAll(Arrays.asList(existing)); distributions.add(distribution); } if (!skipExtraction) { LOGGER.info("Unpacking downloaded file/s to '{}'", basedir); for (Distribution distribution : distributions) { distribution.unpack(basedir); } } //it doesn't strictly read this way, but we're looking for lib/*.jar, pom/*.xml, assembly/*.zip //TODO it'd be better if this didn't have to know where the files were located after unpacking Collection<File> files = FileUtils.listFiles(basedir, FileFilterUtils.or(FileFilterUtils.suffixFileFilter("jar"), FileFilterUtils.suffixFileFilter("xml"), FileFilterUtils.suffixFileFilter("zip")), FileFilterUtils.or(FileFilterUtils.nameFileFilter("lib"), FileFilterUtils.nameFileFilter("pom"), FileFilterUtils.nameFileFilter("assembly"))); if (files.isEmpty()) { throw new MojoExecutionException(String.format( "There don't appear to be any files to work with at '%s'. Check earlier log entries for clues.", basedir.getAbsolutePath())); } Set<Module> result = new TreeSet<Module>(); for (File file : files) { try { String base = FilenameUtils.getBaseName(file.getName().replaceAll("_", "-")); //poms don't need anything else if ("xml".equals(FilenameUtils.getExtension(file.getName()))) { result.add(new Module(getModelFromFile(file))); continue; } //for each jar/zip, find the matching pom IOFileFilter filter = new WildcardFileFilter(base + ".pom"); Collection<File> poms = FileUtils.listFiles(basedir, filter, TrueFileFilter.INSTANCE); if (poms.size() != 1) { LOGGER.warn( "Expected to find exactly 1 POM matching artifact with name '{}', but found {}. Skpping installation.", base, poms.size()); continue; } Model model = getModelFromFile(poms.iterator().next()); Module module = new Module(model, file); /* * Find the right javadoc bundle, matched on prefix. e.g., * smartgwt-eval -> smartgwt-javadoc * isomorphic-core-rpc -> isomorphic-javadoc * and add it to the main artifact with the javadoc classifier. This seems appropriate as long as * a) there is no per-jar javadoc * b) naming conventions are adhered to (or can be corrected by plugin at extraction) */ int index = base.indexOf("-"); String prefix = base.substring(0, index); Collection<File> doc = FileUtils.listFiles(new File(basedir, "doc"), FileFilterUtils.prefixFileFilter(prefix), FileFilterUtils.nameFileFilter("lib")); if (doc.size() != 1) { LOGGER.debug("Found {} javadoc attachments with prefix '{}'. Skipping attachment.", doc.size(), prefix); } else { module.attach(doc.iterator().next(), "javadoc"); } result.add(module); } catch (ModelBuildingException e) { throw new MojoExecutionException("Error building model from POM", e); } } return result; } catch (IOException e) { throw new MojoExecutionException("Failure during assembly collection", e); } } /** * Read the given POM so it can be used as the source of coordinates, etc. during artifact construction. Note that * if this object's {@link #snapshots} property is true, and we're working with a development build ({@link #buildNumber} * ends with 'd'), the POM is modified to remove the SNAPSHOT qualifier. * * @param pom the POM file containing the artifact metadata * @return A Maven model to be used at {@link com.isomorphic.maven.packaging.Module#Module(Model)} Module construction * @throws ModelBuildingException if the Model cannot be built from the given POM * @throws IOException if the Model cannot be built from the given POM */ private Model getModelFromFile(File pom) throws ModelBuildingException, IOException { if (buildNumber.endsWith("d") && !snapshots) { LOGGER.info("Rewriting file to remove SNAPSHOT qualifier from development POM '{}'", pom.getName()); String content = FileUtils.readFileToString(pom); content = content.replaceAll("-SNAPSHOT", ""); FileUtils.write(pom, content); } ModelBuildingRequest request = new DefaultModelBuildingRequest(); request.setPomFile(pom); ModelBuildingResult result = modelBuilder.build(request); return result.getEffectiveModel(); } }