Java tutorial
/* * #%L * xcode-maven-plugin * %% * Copyright (C) 2012 SAP AG * %% * Licensed 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. * #L% */ package com.sap.prd.mobile.ios.mios; import static java.lang.String.format; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.sonatype.aether.RepositorySystem; import org.sonatype.aether.RepositorySystemSession; import org.sonatype.aether.artifact.Artifact; import org.sonatype.aether.repository.RemoteRepository; import org.sonatype.aether.util.artifact.DefaultArtifact; import com.sap.prd.mobile.ios.mios.XCodeContext.SourceCodeLocation; import com.sap.prd.mobile.ios.mios.validationchecks.v_1_0_0.Check; import com.sap.prd.mobile.ios.mios.validationchecks.v_1_0_0.Checks; /** * Provides the possibility to perform validation checks.<br> * The check classes and their severities are described in an additional xml document, defined in * <code>xcode.verification.checks.definitionFile</code>.<br> * The specific checks have to be implemented in a separate project. The coordinates of that * projects needs to be provided on the <code>check</code> node belonging to the test as attributes * <code>groupId</code>, <code>artifactId</code> and <code>version</code>.<br> * The classpath for this goal will be extended by the jars found under the specified GAVs. <br> * Example checks definition: * * <pre> * <checks> * <check groupId="my.group.id" artifactId="artifactId" version="1.0.0" severity="ERROR" class="com.my.MyValidationCheck1"/> * <check groupId="my.group.id" artifactId="artifactId" version="1.0.0" severity="WARNING" class="com.my.MyValidationCheck2"/> * </checks> * </pre> * * @goal validation-check * */ public class XCodeValidationCheckMojo extends BuildContextAwareMojo { private final static String COLON = ":"; private enum Protocol { HTTP() { @Override Reader getCheckDefinitions(String location) throws IOException { HttpClient httpClient = new DefaultHttpClient(); HttpGet get = new HttpGet(getName() + COLON + location); String response = httpClient.execute(get, new BasicResponseHandler()); return new StringReader(response); } }, HTTPS() { @Override Reader getCheckDefinitions(String location) throws IOException { HttpClient httpClient = new DefaultHttpClient(); HttpGet get = new HttpGet(getName() + COLON + location); String response = httpClient.execute(get, new BasicResponseHandler()); return new StringReader(response); } }, FILE() { @Override Reader getCheckDefinitions(String location) throws IOException { if (location.startsWith("//")) location = location.substring(2); final File f = new File(location); if (!f.canRead()) { throw new IOException("Cannot read checkDefintionFile '" + f + "'."); } return new InputStreamReader((new FileInputStream(f)), "UTF-8"); } }; abstract Reader getCheckDefinitions(String location) throws IOException; String getName() { return name().toLowerCase(Locale.ENGLISH); } static String getProtocols() { final StringBuilder sb = new StringBuilder(16); for (Protocol p : Protocol.values()) { if (sb.length() != 0) sb.append(", "); sb.append(p.getName()); } return sb.toString(); } } static class NoProtocolException extends XCodeException { private static final long serialVersionUID = -7510547403353515108L; NoProtocolException(String message) { this(message, null); } NoProtocolException(String message, Throwable cause) { super(message, cause); } }; static class InvalidProtocolException extends XCodeException { private static final long serialVersionUID = -5510547403353515108L; InvalidProtocolException(String message) { super(message); } }; /** * The entry point to Aether, i.e. the component doing all the work. * * @component */ protected RepositorySystem repoSystem; /** * The current repository/network configuration of Maven. * * @parameter default-value="${repositorySystemSession}" * @readonly */ protected RepositorySystemSession repoSession; /** * The project's remote repositories to use for the resolution of project dependencies. * * @parameter default-value="${project.remoteProjectRepositories}" * @readonly */ protected List<RemoteRepository> projectRepos; /** * Parameter, which conrols the validation goal execution. By default, the validation goal will be * skipped. * * @parameter expression="${xcode.verification.checks.skip}" default-value="true" * @since 1.9.3 */ private boolean skip; /** * The location where the check definition file is present. Could be a file on the local file * system or a remote located file, accessed via HTTP. * * @parameter expression="${xcode.verification.checks.definitionFile}" * @since 1.9.3 */ private String checkDefinitionFile; @Override public void execute() throws MojoExecutionException, MojoFailureException { if (skip) { getLog().info( "Verification check goal has been skipped intentionally since parameter 'xcode.verification.checks.skip' is '" + skip + "'."); return; } try { final Checks checks = getChecks(checkDefinitionFile); extendClasspath(checks); performChecks(checks); } catch (XCodeException e) { throw new MojoExecutionException(e.getMessage(), e); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } catch (JAXBException e) { throw new MojoExecutionException(e.getMessage(), e); } } private void performChecks(final Checks checks) throws MojoExecutionException { String verificationCheckClassName = null; Map<com.sap.prd.mobile.ios.mios.validationchecks.v_1_0_0.Check, Exception> failedChecks = new HashMap<com.sap.prd.mobile.ios.mios.validationchecks.v_1_0_0.Check, Exception>(); try { if (checks.getCheck().isEmpty()) { getLog().warn("No checks configured in '" + checkDefinitionFile + "'."); } for (final com.sap.prd.mobile.ios.mios.validationchecks.v_1_0_0.Check checkDesc : checks.getCheck()) { verificationCheckClassName = checkDesc.getClazz(); final Class<?> clazz = Class.forName(verificationCheckClassName); for (final String configuration : getConfigurations()) { for (final String sdk : getSDKs()) { getLog().info("Executing verification check: '" + clazz.getName() + "' for configuration '" + configuration + "' and sdk '" + sdk + "'."); final ValidationCheck check = (ValidationCheck) clazz.newInstance(); check.setXcodeContext(getXCodeContext(SourceCodeLocation.WORKING_COPY, configuration, sdk)); check.setMavenProject(project); check.setLog(getLog()); try { check.check(); } catch (VerificationException ex) { failedChecks.put(checkDesc, ex); } catch (Exception ex) { failedChecks.put(checkDesc, ex); } } } } handleExceptions(failedChecks); } catch (MojoExecutionException e) { throw e; } catch (ClassNotFoundException e) { throw new MojoExecutionException("Could not load verification check '" + verificationCheckClassName + "'. May be your classpath has not been properly extended. Additional dependencies need to be provided with 'xcode.additionalClasspathElements'. " + e.getMessage(), e); } catch (InstantiationException e) { throw new MojoExecutionException(e.getMessage(), e); } catch (IllegalAccessException e) { throw new MojoExecutionException(e.getMessage(), e); } } private void handleExceptions( Map<com.sap.prd.mobile.ios.mios.validationchecks.v_1_0_0.Check, Exception> failedChecks) throws MojoExecutionException { boolean mustFailedTheBuild = false; for (com.sap.prd.mobile.ios.mios.validationchecks.v_1_0_0.Check failedCheck : failedChecks.keySet()) { handleException(failedCheck, failedChecks.get(failedCheck)); if (failedCheck.getSeverity().equalsIgnoreCase("ERROR")) { mustFailedTheBuild = true; } } if (mustFailedTheBuild) { throw new MojoExecutionException("Validation checks failed. See the log file for details."); } } private void handleException(com.sap.prd.mobile.ios.mios.validationchecks.v_1_0_0.Check failedCheck, final Exception e) throws MojoExecutionException { final String message; if (e instanceof VerificationException) { message = "Verification check '" + failedCheck.getClazz() + " failed. " + e.getMessage(); } else { message = "Cannot perform check: " + failedCheck.getClazz() + ". Error during test setup " + e.getMessage(); } if (failedCheck.getSeverity().equalsIgnoreCase("WARNING")) { getLog().warn(message); } else { getLog().error(message); } } private void extendClasspath(Checks checks) throws MojoExecutionException { final Set<Artifact> dependencies = parseDependencies(checks, getLog()); final ClassRealm classRealm; final ClassLoader loader = this.getClass().getClassLoader(); if (loader instanceof ClassRealm) { classRealm = (ClassRealm) loader; } else { throw new RuntimeException("Could not add jar to classpath. Class loader '" + loader + "' is not an instance of '" + ClassRealm.class.getName() + "'."); } for (Artifact dependencyArtifact : dependencies) { final File jar; try { Artifact artifact = new XCodeDownloadManager(projectRepos, repoSystem, repoSession) .resolveArtifact(dependencyArtifact); jar = artifact.getFile(); } catch (SideArtifactNotFoundException e1) { throw new MojoExecutionException(e1.getMessage(), e1); } try { classRealm.addURL(jar.toURI().toURL()); } catch (final MalformedURLException e) { throw new MojoExecutionException( "Failed to add file '" + jar.getAbsolutePath() + "' to classloader: ", e); } } } static Set<Artifact> parseDependencies(final Checks checks, final Log log) throws MojoExecutionException { final Set<Artifact> result = new HashSet<Artifact>(); for (Check check : checks.getCheck()) { final String coords = StringUtils .join(Arrays.asList(check.getGroupId(), check.getArtifactId(), check.getVersion()), ":"); if (coords.equals("::")) { log.info("No coordinates maintained for check represented by class '" + check.getClazz() + "'. Assuming this check is already contained in the classpath."); continue; } if (coords.matches("^:.*|.*:$|.*::.*")) throw new MojoExecutionException("Invalid coordinates: '" + coords + "' maintained for check represented by class '" + check.getClazz() + "'. At least one of groupId, artifactId or version is missing."); result.add(new DefaultArtifact(coords)); } return result; } static Checks getChecks(final String checkDefinitionFileLocation) throws XCodeException, IOException, JAXBException { Reader checkDefinitions = null; try { checkDefinitions = getChecksDescriptor(checkDefinitionFileLocation); return (Checks) JAXBContext.newInstance(Checks.class).createUnmarshaller().unmarshal(checkDefinitions); } finally { IOUtils.closeQuietly(checkDefinitions); } } static Reader getChecksDescriptor(final String checkDefinitionFileLocation) throws XCodeException, IOException { if (checkDefinitionFileLocation == null || checkDefinitionFileLocation.trim().isEmpty()) { throw new XCodeException("CheckDefinitionFile was not configured. Cannot perform verification checks"); } Location location; try { location = Location.getLocation(checkDefinitionFileLocation); } catch (NoProtocolException e) { throw new NoProtocolException(format("No protocol found: %s. Provide a protocol [%s]" + " for parameter 'xcode.verification.checks.definitionFile', e.g. http://example.com/checkDefinitions.xml.", checkDefinitionFileLocation, Protocol.getProtocols()), e); } try { Protocol protocol = Protocol.valueOf(location.protocol); return protocol.getCheckDefinitions(location.location); } catch (IllegalArgumentException ex) { throw new InvalidProtocolException(format("Invalid protocol provided: '%s'. Supported values are:'%s'.", location.protocol, Protocol.getProtocols())); } catch (IOException ex) { throw new IOException(format("Cannot get check definitions from '%s'.", checkDefinitionFileLocation), ex); } } static class Location { static Location getLocation(final String locationUriString) throws NoProtocolException { URI uri = URI.create(locationUriString.trim()); String protocol = uri.getScheme(); if (protocol == null) throw new NoProtocolException(locationUriString); String location = uri.getPath(); if (location == null) { location = uri.getSchemeSpecificPart(); } return new Location(protocol, location); } final String protocol; final String location; public Location(String protocol, String location) { this.protocol = protocol.toUpperCase(Locale.ENGLISH); this.location = location; } } }