Java tutorial
/****************************************************************************** ** This data and information is proprietary to, and a valuable trade secret ** of, Basis Technology Corp. It is given in confidence by Basis Technology ** and may only be used as permitted under the license agreement under which ** it has been distributed, and in no other way. ** ** Copyright (c) 2014 Basis Technology Corporation All rights reserved. ** ** The technical data and information provided herein are provided with ** `limited rights', and the computer software provided herein is provided ** with `restricted rights' as those terms are defined in DAR and ASPR ** 7-104.9(a). ******************************************************************************/ package com.basistech; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; import com.google.common.collect.Maps; import com.google.common.io.ByteSource; import com.google.common.io.Files; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.cxf.Bus; import org.apache.cxf.BusFactory; import org.apache.cxf.jaxrs.client.ClientConfiguration; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.cxf.transport.Conduit; import org.apache.cxf.transport.http.HTTPConduit; import org.apache.cxf.transport.http.asyncclient.AsyncHTTPConduit; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Scm; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Proxy; import org.apache.maven.settings.Server; import org.apache.maven.settings.Settings; import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest; import org.apache.maven.settings.crypto.SettingsDecrypter; import org.apache.maven.settings.crypto.SettingsDecryptionResult; import org.codehaus.plexus.PlexusConstants; import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.component.repository.exception.ComponentLookupException; import org.codehaus.plexus.context.Context; import org.codehaus.plexus.context.ContextException; import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable; import org.slf4j.impl.StaticLoggerBinder; import javax.ws.rs.ClientErrorException; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.ClientRequestContext; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.GenericType; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.xml.bind.DatatypeConverter; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.Properties; /** * Write release notes file to github's release/tag structure. */ @Mojo(name = "write-release-notes", defaultPhase = LifecyclePhase.VERIFY) public class ReleaseNoteMojo extends AbstractMojo implements Contextualizable { /** * Location of the file containing the release notes. */ @Parameter(defaultValue = "${basedir}/RELEASE-NOTES.md", required = true) private File releaseNotes; /** * Skip execution if this is true. */ @Parameter private boolean skip; /** * SCM tag version. If this is not set, the plugin will look for an existng property named 'scm.tag'. * Failing that, * it will look for a file named 'release.properties', read it, and lookm for that property. * Default is scm.tag property. Thus, if you run this goal after release:prepare and before * release:perform, it will read the value from the file. */ @Parameter(property = "tag") private String tag; /** * The user name for authentication. You probably want to use a server in settings.xml. */ @Parameter(defaultValue = "${github.global.userName}") private String userName; /** * The password for authentication. You probably want to use a server in settings.xml. */ @Parameter(defaultValue = "{github.global.password}") private String password; /** * The oauth2 token for authentication. You probably want to create a server in settings.xml with * your token in the password element and no username element. The plugin will use that password * as an oauth2 token. */ @Parameter(defaultValue = "${github.global.oauth2Token}") private String oauth2Token; /** * The Host for API calls. By default, the plugin reads this from the scm developerConnection. */ @Parameter(defaultValue = "${github.global.host}") private String host; /** * The <em>id</em> of the server to use to retrieve the Github credentials. This id must identify a * <em>server</em> in your <em>setting.xml</em> file. */ @Parameter(defaultValue = "${github.global.server}", property = "server") private String serverId; /** * If you are using this plugin with github-for-enterprise, and your server's SSL certificate is * signed with a certificate that is not trusted by the JRE, you must supply a keystore file * that serves as a trust store to reassure Java about the server's certificate. */ @Parameter private File keystore; @Component private MavenProject project; @Component private MavenSession session; @Component private Settings settings; private String owner; private String repoName; @Requirement private PlexusContainer container; /** * {@inheritDoc} */ public void contextualize(Context context) throws ContextException { container = (PlexusContainer) context.get(PlexusConstants.PLEXUS_KEY); } private static class GithubUnprocessible422 { @JsonProperty String message; @JsonProperty List<Map<String, String>> errors; Map<String, Object> others = Maps.newHashMap(); @JsonAnySetter public void setter(String key, Object value) { others.put(key, value); } } public void execute() throws MojoExecutionException { StaticLoggerBinder.getSingleton().setLog(getLog()); if (skip) { getLog().info("Github Release Notes Plugin execution skipped"); return; } String releaseNoteContent; try { releaseNoteContent = FileUtils.readFileToString(releaseNotes, "utf-8"); } catch (IOException e) { throw new MojoExecutionException("Failed to read notes", e); } parseScm(); Client client = createClient(); String tagName = getTagName(); ReleaseInfo releaseInfo = new ReleaseInfo(tagName, null, tagName, releaseNoteContent, false, false); String uri; if ("github.com".equals(host)) { uri = String.format("https://api.github.com/repos/%s/%s/releases", owner, repoName); } else { uri = String.format("https://%s/api/v3/repos/%s/%s/releases", host, owner, repoName); } WebTarget target = client.target(uri); String existingRelease = getExistingRelease(tagName, target); createRelease(existingRelease, releaseInfo, target); } private String getExistingRelease(String tagName, WebTarget target) { Invocation.Builder invocationBuilder = target.request(MediaType.APPLICATION_JSON_TYPE); List<Map<String, Object>> releases = invocationBuilder.get(new GenericType<List<Map<String, Object>>>() { }); for (Map<String, Object> release : releases) { if (tagName.equals(release.get("tag_name"))) { return release.get("id").toString(); // map integer to string } } return null; } private void createRelease(String existingRelease, ReleaseInfo releaseInfo, WebTarget target) throws MojoExecutionException { if (existingRelease != null) { target = target.path(existingRelease); } Invocation.Builder invocationBuilder = target.request(MediaType.APPLICATION_JSON_TYPE); setupProxy(invocationBuilder); Invocation invocation; if (existingRelease != null) { invocation = invocationBuilder.build("PATCH", Entity.json(releaseInfo)); } else { invocation = invocationBuilder.buildPost(Entity.json(releaseInfo)); } try { Map<String, Object> response = invocation.invoke(new GenericType<Map<String, Object>>() { }); getLog().info((String) response.get("url")); } catch (ClientErrorException cee) { if (cee.getResponse().getStatus() == 422) { GithubUnprocessible422 info = cee.getResponse().readEntity(GithubUnprocessible422.class); getLog().error("Error 422 " + info.message); for (Map<String, String> error : info.errors) { getLog().error("error item:"); for (Map.Entry<String, String> me : error.entrySet()) { getLog().error(String.format("%s: %s", me.getKey(), me.getValue())); } } } else { Map<String, Object> info = cee.getResponse().readEntity(new GenericType<Map<String, Object>>() { }); getLog().error("Error " + cee.getResponse().getStatus() + " " + info.get("message")); } throw cee; } } private Client createClient() throws MojoExecutionException { Bus bus = BusFactory.getDefaultBus(); // insist on the async connector to use PATCH. bus.setProperty(AsyncHTTPConduit.USE_ASYNC, Boolean.TRUE); ClientBuilder builder = ClientBuilder.newBuilder(); builder.register(new JacksonJsonProvider()); if (keystore != null) { try { builder.trustStore(readTrustStore(keystore)); } catch (Exception e) { throw new MojoExecutionException("Exception setting up SSL trust store", e); } } Client client = builder.build(); if (!getAuthFromSettings(client)) { if (oauth2Token != null) { setupOauth2(client, oauth2Token); } else if (userName != null) { setupBasicAuthentication(client, userName, password); } } return client; } protected Server getServer(final Settings settings, final String serverId) { if (settings == null || serverId == null) { return null; } List<Server> servers = settings.getServers(); if (servers == null || servers.isEmpty()) { return null; } for (Server server : servers) { if (serverId.equals(server.getId())) { return server; } } return null; } private void setupBasicAuthentication(Client client, final String username, final String password) { client.register(new ClientRequestFilter() { public void filter(ClientRequestContext requestContext) throws IOException { MultivaluedMap<String, Object> headers = requestContext.getHeaders(); final String basicAuthentication = getBasicAuthentication(); headers.add("Authorization", basicAuthentication); } private String getBasicAuthentication() { String token = username + ":" + password; try { return "BASIC " + DatatypeConverter.printBase64Binary(token.getBytes("UTF-8")); } catch (UnsupportedEncodingException ex) { throw new IllegalStateException("Cannot encode with UTF-8", ex); } } }); } private boolean getAuthFromSettings(Client client) throws MojoExecutionException { if (serverId == null) { return false; } Server server = getServer(settings, serverId); if (server == null) { throw new MojoExecutionException( MessageFormat.format("Server ''{0}'' not found in settings", serverId)); } getLog().debug(MessageFormat.format("Using ''{0}'' server credentials", serverId)); try { SettingsDecrypter settingsDecrypter = container.lookup(SettingsDecrypter.class); SettingsDecryptionResult result = settingsDecrypter .decrypt(new DefaultSettingsDecryptionRequest(server)); server = result.getServer(); } catch (ComponentLookupException cle) { throw new MojoExecutionException("Unable to lookup SettingsDecrypter: " + cle.getMessage(), cle); } String serverUsername = server.getUsername(); String serverPassword = server.getPassword(); if (serverUsername != null && serverUsername.length() > 0 && serverPassword != null && serverPassword.length() > 0) { getLog().debug("Using basic authentication with username: " + serverUsername); setupBasicAuthentication(client, serverUsername, serverPassword); return true; } // A server password without a username is assumed to be an OAuth2 token if (serverPassword != null && serverPassword.length() > 0) { getLog().debug("Using OAuth2 access token authentication"); setupOauth2(client, serverPassword); return true; } getLog().debug(MessageFormat.format("Server ''{0}'' is missing username/password credentials", serverId)); return false; } private void setupOauth2(Client client, final String token) { client.register(new ClientRequestFilter() { public void filter(ClientRequestContext requestContext) throws IOException { MultivaluedMap<String, Object> headers = requestContext.getHeaders(); headers.add("Authorization", "token " + token); } }); } /** * Get proxy from settings * * @param settings * @param serverId must be non-null and non-empty * @return proxy or null if none matching */ protected Proxy getProxy(final Settings settings, final String serverId) { if (settings == null) return null; List<Proxy> proxies = settings.getProxies(); if (proxies == null || proxies.isEmpty()) return null; // search id match first if (serverId != null && !serverId.isEmpty()) { for (Proxy proxy : proxies) { if (proxy.isActive()) { final String proxyId = proxy.getId(); if (proxyId != null && !proxyId.isEmpty()) { if (proxyId.equalsIgnoreCase(serverId)) { if (("http".equalsIgnoreCase(proxy.getProtocol()) || "https".equalsIgnoreCase(proxy.getProtocol()))) { if (matchNonProxy(proxy)) return null; else return proxy; } } } } } } // search active proxy for (Proxy proxy : proxies) if (proxy.isActive() && ("http".equalsIgnoreCase(proxy.getProtocol()) || "https".equalsIgnoreCase(proxy.getProtocol()))) { if (matchNonProxy(proxy)) return null; else return proxy; } return null; } /** * Check hostname that matched nonProxy setting * * @param proxy Maven Proxy. * @return matching result. true: match nonProxy */ protected boolean matchNonProxy(final Proxy proxy) { // code from org.apache.maven.plugins.site.AbstractDeployMojo#getProxyInfo final String nonProxyHosts = proxy.getNonProxyHosts(); if (null != nonProxyHosts) { final String[] nonProxies = nonProxyHosts.split("(,)|(;)|(\\|)"); for (final String nonProxyHost : nonProxies) { //if ( StringUtils.contains( nonProxyHost, "*" ) ) if (null != nonProxyHost && nonProxyHost.contains("*")) { // Handle wildcard at the end, beginning or middle of the nonProxyHost final int pos = nonProxyHost.indexOf('*'); String nonProxyHostPrefix = nonProxyHost.substring(0, pos); String nonProxyHostSuffix = nonProxyHost.substring(pos + 1); // prefix* if (nonProxyHostPrefix.length() > 0 && host.startsWith(nonProxyHostPrefix) && nonProxyHostSuffix.length() == 0) { return true; } // *suffix if (nonProxyHostPrefix.length() == 0 && nonProxyHostSuffix.length() > 0 && host.endsWith(nonProxyHostSuffix)) { return true; } // prefix*suffix if (nonProxyHostPrefix.length() > 0 && host.startsWith(nonProxyHostPrefix) && nonProxyHostSuffix.length() > 0 && host.endsWith(nonProxyHostSuffix)) { return true; } } else if (host.equals(nonProxyHost)) { return true; } } } return false; } private void setupProxy(Invocation.Builder target) throws MojoExecutionException { Proxy proxy = getProxy(settings, serverId); if (null != proxy) { ClientConfiguration cxfConfig = WebClient.getConfig(target); Conduit conduit = cxfConfig.getConduit(); try { SettingsDecrypter settingsDecrypter = container.lookup(SettingsDecrypter.class); SettingsDecryptionResult result = settingsDecrypter .decrypt(new DefaultSettingsDecryptionRequest(proxy)); proxy = result.getProxy(); } catch (ComponentLookupException cle) { throw new MojoExecutionException("Unable to lookup SettingsDecrypter: " + cle.getMessage(), cle); } getLog().debug(MessageFormat.format("Found Proxy {0}:{1}", proxy.getHost(), proxy.getPort())); HTTPConduit http = (HTTPConduit) conduit; http.getClient().setProxyServer(proxy.getHost()); http.getClient().setProxyServerPort(proxy.getPort()); http.getProxyAuthorization().setUserName(proxy.getUsername()); http.getProxyAuthorization().setPassword(proxy.getPassword()); } } private void parseScm() { final Scm scm = project.getScm(); // the util class from egit is broken. // assume git: url for now. //scm:git:git@git.basistech.net:benson/test-release-notes.git String devUrl = scm.getDeveloperConnection(); int atx = devUrl.indexOf('@'); String some = devUrl.substring(atx + 1); int colidx = some.indexOf(':'); if (host == null) { host = some.substring(0, colidx); } String ownerRepo = some.substring(colidx + 1); int slidx = ownerRepo.indexOf('/'); owner = ownerRepo.substring(0, slidx); ownerRepo = ownerRepo.substring(slidx + 1); int dotidx = ownerRepo.indexOf('.'); repoName = ownerRepo.substring(0, dotidx); } private String getTagName() throws MojoExecutionException { if (tag != null) { return tag; } String scmTag = (String) project.getProperties().get("scm.tag"); if (scmTag != null) { return scmTag; } File releasePropsFile = new File(project.getBasedir(), "release.properties"); if (releasePropsFile.exists()) { Properties releaseProps = new Properties(); InputStream is = null; try { is = new FileInputStream(releasePropsFile); releaseProps.load(is); } catch (IOException ie) { throw new MojoExecutionException("Failed to read release.properties", ie); } finally { IOUtils.closeQuietly(is); } String propTag = (String) releaseProps.get("scm.tag"); if (propTag != null) { return propTag; } } throw new MojoExecutionException("No scm tag information available."); } /* * Read a trust store. Trust stores are represented as KeyStore objects, just to confuse us. */ private KeyStore readTrustStore(File trustStore) throws IOException, NoSuchAlgorithmException, KeyStoreException, CertificateException, KeyManagementException { KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); ByteSource keystoreByteSource = Files.asByteSource(trustStore); InputStream keystoreStream = null; try { keystoreStream = keystoreByteSource.openStream(); //TODO: deal with the actual password whatever it is. // there's no good reason to use a fancy password on a trust store. keystore.load(keystoreStream, "changeit".toCharArray()); } finally { IOUtils.closeQuietly(keystoreStream); } return keystore; } }