Java tutorial
/* * Artifactory is a binaries repository manager. * Copyright (C) 2012 JFrog Ltd. * * Artifactory is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Artifactory is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Artifactory. If not, see <http://www.gnu.org/licenses/>. */ package org.artifactory.rest.common.service; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.thoughtworks.xstream.annotations.XStreamAlias; import org.apache.commons.compress.utils.IOUtils; import org.apache.http.Header; import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.artifactory.addon.AddonsManager; import org.artifactory.addon.CoreAddons; import org.artifactory.addon.ha.HaCommonAddon; import org.artifactory.api.config.CentralConfigService; import org.artifactory.api.config.VersionInfo; import org.artifactory.api.context.ContextHelper; import org.artifactory.api.jackson.JacksonReader; import org.artifactory.api.request.ArtifactoryResponse; import org.artifactory.api.rest.search.result.VersionRestResult; import org.artifactory.descriptor.repo.HttpRepoDescriptor; import org.artifactory.descriptor.repo.ProxyDescriptor; import org.artifactory.features.VersionFeature; import org.artifactory.features.matrix.SmartRepoVersionFeatures; import org.artifactory.repo.HttpRepositoryConfiguration; import org.artifactory.repo.HttpRepositoryConfigurationImpl; import org.artifactory.security.crypto.CryptoHelper; import org.artifactory.util.HttpClientConfigurator; import org.artifactory.util.PathUtils; import org.codehaus.jackson.type.TypeReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import javax.annotation.Nullable; import java.io.IOException; import java.io.Serializable; import java.net.URI; import java.util.List; import static org.artifactory.request.ArtifactoryRequest.ARTIFACTORY_ORIGINATED; /** * Service used to discover capabilities of another artifactory * * @author michaelp */ @Component public class ResearchService extends AbstractResearchService { private static final Logger log = LoggerFactory.getLogger(ResearchService.class); private static final String ARTIFACTORY_SYSTEM_VERSION_PATH = "/api/system/version"; private static final String ARTIFACTORY_REPOSITORIES_PATH = "/api/repositories"; private static final String ARTIFACTORY_APP_PATH = "/artifactory"; @Lazy(true) @Autowired private SmartRepoVersionFeatures smartRepoVersionFeatures; @Autowired private CentralConfigService configService; public ResearchResponse getSmartRepoCapabilities(HttpRepositoryConfigurationImpl configuration) { if (!Strings.isNullOrEmpty(configuration.getUrl())) { URI uri = URI.create(configuration.getUrl()); if (uri != null) { if (uri.getPath().startsWith(ARTIFACTORY_APP_PATH)) { return getArtifactoryCapabilities(getHttpClient(configuration), uri, true); } else { return getArtifactoryCapabilities(getHttpClient(configuration), uri, false); } } else { log.debug("Url is malformed."); } } else { log.debug("Url is a mandatory (query) parameter."); } return ResearchResponse.notArtifactory(); } /** * Checks whether given target is another artifactory instance * and if true, retrieves SmartRepo capabilities based on artifactory * version * * @param httpRepoDescriptor {@link HttpRepoDescriptor} * * @return {@link ResearchResponse} */ public ResearchResponse getSmartRepoCapabilities(HttpRepoDescriptor httpRepoDescriptor) { if (!Strings.isNullOrEmpty(httpRepoDescriptor.getUrl())) { URI uri = URI.create(httpRepoDescriptor.getUrl()); if (uri != null) { if (uri.getPath().startsWith(ARTIFACTORY_APP_PATH)) { return getArtifactoryCapabilities(getHttpClient(httpRepoDescriptor), uri, true); } else { return getArtifactoryCapabilities(getHttpClient(httpRepoDescriptor), uri, false); } } else { log.debug("Url is malformed."); } } else { log.debug("Url is a mandatory (query) parameter."); } return ResearchResponse.notArtifactory(); } /** * Checks whether given target is another artifactory instance * and if true, retrieves SmartRepo capabilities based on artifactory * version * * @param url url to test against * @param client http client to be used * * @return {@link ResearchResponse} * * @return boolean */ public ResearchResponse getSmartRepoCapabilities(String url, CloseableHttpClient client) { assert client != null : "HttpClient cannot be empty"; if (!Strings.isNullOrEmpty(url)) { URI uri = URI.create(url); VersionInfo versionInfo; if (uri != null) { if (uri.getPath().startsWith(ARTIFACTORY_APP_PATH)) { return getArtifactoryCapabilities(client, uri, true); } else { return getArtifactoryCapabilities(client, uri, false); } } else { log.debug("Url is malformed."); } } else { log.debug("Url is a mandatory (query) parameter."); } return ResearchResponse.notArtifactory(); } /** * Fetches artifactory capabilities (if target is artifactory) * * @param client http client to use * @param uri remote uri to test against * @param inArtifactoryContext whether given app deployed * in /artifactory context or not * * @return {@link VersionInfo} if target is artifactory or null */ private ResearchResponse getArtifactoryCapabilities(CloseableHttpClient client, URI uri, boolean inArtifactoryContext) { assert client != null : "HttpClient cannot be empty"; CloseableHttpResponse response = null; String requestUrl = produceVersionUrl(uri, inArtifactoryContext); HttpGet getMethod = new HttpGet(requestUrl); addOriginatedHeader(getMethod); try { response = client.execute(getMethod); String returnedInfo = null; if (response != null) { if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { returnedInfo = EntityUtils.toString(response.getEntity()); if (!Strings.isNullOrEmpty(returnedInfo)) { VersionRestResult vrr = parseVersionRestResult(returnedInfo); if (vrr != null && !Strings.isNullOrEmpty(vrr.version) && validateLicense(vrr.license, getArtifactoryId(response))) { Boolean isRealRepo = isRealRepo(client, uri, // make sure it not a virt repo PathUtils.getLastPathElement(uri.getPath()), inArtifactoryContext); if (isRealRepo == null) { // we were unable to check repoType // what may happen if config doesn't // have credentials or has insufficient permissions // (api requires it even if anonymous login is on) log.debug("We were unable to check repoType, it may happen if config doesn't have " + "credentials or user permissions insufficient"); } if (isRealRepo == null || isRealRepo.booleanValue()) { log.debug( "Repo '{}' is artifactory repository (not virtual) and has supported version for SmartRepo"); VersionInfo versionInfo = vrr.toVersionInfo(); return ResearchResponse.artifactoryMeta(true, versionInfo, smartRepoVersionFeatures.getFeaturesByVersion(versionInfo)); } else { log.debug("Virtual repository is not supported in this version of SmartRepo"); } } else { log.debug("Unsupported version: {}", vrr); } } } else if (response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { return ResearchResponse.artifactory(); } } } catch (IOException | IllegalArgumentException e) { log.debug("Checking remote artifactory version has failed: {}.", e); } finally { IOUtils.closeQuietly(response); } return ResearchResponse.notArtifactory(); } /** * add originated header to request * * @param request - http servlet request */ private static void addOriginatedHeader(HttpRequestBase request) { String hostId = ContextHelper.get().beanForType(AddonsManager.class).addonByType(HaCommonAddon.class) .getHostId(); request.addHeader(ARTIFACTORY_ORIGINATED, hostId); } /** * Validates that remote Artifactory uses different license and it is PRO license * * @param license remote license * @param artifactoryId remote artifactory id * * @return true if both constraints are true otherwise false */ private boolean validateLicense(String license, String artifactoryId) { AddonsManager addonsManager = ContextHelper.get().beanForType(AddonsManager.class); CoreAddons coreAddons = addonsManager.addonByType(CoreAddons.class); boolean isDifferentLicense = coreAddons.validateTargetHasDifferentLicense(license, artifactoryId); boolean isProLicensed = addonsManager.isProLicensed(license); boolean result = isDifferentLicense && isProLicensed; if (!result) { if (!isDifferentLicense) log.warn("License uniqueness validation against target repository " + "has failed, SmartRepo capabilities won't be enabled"); if (!isProLicensed) log.warn("License PRO validation against target repository has failed, " + "SmartRepo capabilities won't be enabled"); } return result; } /** * Fetches ARTIFACTORY_ID header from {@link CloseableHttpResponse} * * @param response * * @return ArtifactoryId if present or null */ @Nullable private String getArtifactoryId(CloseableHttpResponse response) { assert response != null : "HttpResponse cannot be empty"; Header artifactoryIdHeader = response.getFirstHeader(ArtifactoryResponse.ARTIFACTORY_ID); if (artifactoryIdHeader != null && !Strings.isNullOrEmpty(artifactoryIdHeader.getValue())) { return artifactoryIdHeader.getValue(); } return null; } /** * Produces url to be used against target host * @param uri original URI * @param inArtifactoryContext if application resides under * /artifactory path * * @return url to be used */ private String produceVersionUrl(URI uri, boolean inArtifactoryContext) { return new StringBuilder().append(uri.getScheme()).append("://").append(uri.getHost()) .append(uri.getPort() != -1 ? ":" + uri.getPort() : "") .append(getServiceName(uri, inArtifactoryContext)).append(ARTIFACTORY_SYSTEM_VERSION_PATH) .toString(); } /** * Fetches service name from the original URI * * @param uri * @param inArtifactoryContext * @return service name */ private String getServiceName(URI uri, boolean inArtifactoryContext) { if (inArtifactoryContext) return ARTIFACTORY_APP_PATH; if (uri.getPath() != null) { String[] parts = uri.getPath().split("/"); if (parts.length >= 2) { if (!(parts.length == 2 && uri.getPath().startsWith("/") && uri.getPath().endsWith("/"))) return "/" + PathUtils.getFirstPathElement(uri.getPath()); // .../serviceName/repoName } } return ""; // repoName } /** * Produces CloseableHttpClient * * @param configuration {@link HttpRepositoryConfigurationImpl} * * @return CloseableHttpClient */ private CloseableHttpClient getHttpClient(HttpRepositoryConfigurationImpl configuration) { ProxyDescriptor proxyDescriptor = configService.getDescriptor().getProxy(configuration.getProxy()); return new HttpClientConfigurator().hostFromUrl(configuration.getUrl()) .connectionTimeout(configuration.getSocketTimeoutMillis()) .soTimeout(configuration.getSocketTimeoutMillis()).staleCheckingEnabled(true).retry(0, false) .localAddress(configuration.getLocalAddress()).proxy(proxyDescriptor) .authentication(configuration.getUsername(), CryptoHelper.decryptIfNeeded(configuration.getPassword()), configuration.isAllowAnyHostAuth()) .enableCookieManagement(configuration.isEnableCookieManagement()).getClient(); } /** * Produces CloseableHttpClient * * @param httpRepoDescriptor {@link HttpRepoDescriptor} * * @return CloseableHttpClient */ private CloseableHttpClient getHttpClient(HttpRepoDescriptor httpRepoDescriptor) { return new HttpClientConfigurator().hostFromUrl(httpRepoDescriptor.getUrl()) .connectionTimeout(httpRepoDescriptor.getSocketTimeoutMillis()) .soTimeout(httpRepoDescriptor.getSocketTimeoutMillis()).staleCheckingEnabled(true).retry(0, false) .localAddress(httpRepoDescriptor.getLocalAddress()).proxy(httpRepoDescriptor.getProxy()) .authentication(httpRepoDescriptor.getUsername(), CryptoHelper.decryptIfNeeded(httpRepoDescriptor.getPassword()), httpRepoDescriptor.isAllowAnyHostAuth()) .enableCookieManagement(httpRepoDescriptor.isEnableCookieManagement()).getClient(); } /** * Unmarshals VersionRestResult from string response * * @param versionRestResult * @return {@link VersionRestResult} * * @throws IOException if conversion fails */ private VersionRestResult parseVersionRestResult(String versionRestResult) throws IOException { return getObjectMapper().readValue(getJsonParser(versionRestResult.getBytes()), new TypeReference<VersionRestResult>() { }); } /** * Checks if target repository is Virtual * * @param client * @param repoKey * @param inArtifactoryContext * * @return true if not virtual, otherwise false */ private final Boolean isRealRepo(CloseableHttpClient client, URI uri, String repoKey, boolean inArtifactoryContext) { assert client != null : "HttpClient cannot be empty"; CloseableHttpResponse response = null; String requestUrl = produceRepoInfoUrl(uri, repoKey, inArtifactoryContext); HttpGet getMethod = new HttpGet(requestUrl); addOriginatedHeader(getMethod); try { response = client.execute(getMethod); if (response != null) { if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { HttpRepositoryConfiguration httpRepositoryConfiguration = JacksonReader.streamAsClass( response.getEntity().getContent(), HttpRepositoryConfigurationImpl.class); if (httpRepositoryConfiguration != null) { if (httpRepositoryConfiguration.getType().equals("virtual")) { log.debug("Found virtual repository '{}'", repoKey); return false; } else { log.debug("Found real repository '{}'", repoKey); return true; } } else { log.debug("Cannot fetch '{}' metadata, no response received", repoKey); } } else { log.debug("Cannot fetch '{}' metadata, cause: ", response); } } } catch (IOException | IllegalArgumentException e) { log.debug("Checking remote artifactory type has failed: {}.", e.getMessage()); } finally { IOUtils.closeQuietly(response); } return null; } /** * Generates repository info URL * * @param uri default target URI * @param repoKey * @param inArtifactoryContext * * @return url */ private String produceRepoInfoUrl(URI uri, String repoKey, boolean inArtifactoryContext) { return new StringBuilder().append(uri.getScheme()).append("://").append(uri.getHost()) .append(uri.getPort() != -1 ? ":" + uri.getPort() : "") .append(getServiceName(uri, inArtifactoryContext)).append(ARTIFACTORY_REPOSITORIES_PATH).append("/") .append(repoKey).toString(); } /** * ResearchResponse */ @XStreamAlias("researchResponse") public static class ResearchResponse implements Serializable { private final boolean isArtifactory; private final VersionInfo versionInfo; private final List<VersionFeature> features; /** * Produces response with artifactory=false * * @return {@link ResearchResponse} */ private ResearchResponse() { this.isArtifactory = false; this.versionInfo = null; this.features = Lists.newLinkedList(); } /** * Produces response with metadata describing remote * artifactory instance * * @param isArtifactory * @param versionInfo * @param features * * @return {@link ResearchResponse} */ private ResearchResponse(boolean isArtifactory, VersionInfo versionInfo, List<VersionFeature> features) { this.isArtifactory = isArtifactory; this.versionInfo = versionInfo; this.features = features; } /** * @return definition of remote host being artifactory or not */ public boolean isArtifactory() { return isArtifactory; } /** * @return remote artifactory version */ public VersionInfo getVersion() { return versionInfo; } /** * @return available features for remote artifactory * (based on its version) */ public List<VersionFeature> getFeatures() { return features; } /** * Produces response with artifactory=false * * @return {@link ResearchResponse} */ public static ResearchResponse notArtifactory() { return new ResearchResponse(); } /** * Produces response with metadata describing remote * artifactory instance * * @param isArtifactory * @param versionInfo * @param features * * @return {@link ResearchResponse} */ public static ResearchResponse artifactoryMeta(boolean isArtifactory, VersionInfo versionInfo, List<VersionFeature> features) { return new ResearchResponse(isArtifactory, versionInfo, features); } /** * Produces response without any metadata, but implies artifactory=true * * @return {@link ResearchResponse} */ public static ResearchResponse artifactory() { return new ResearchResponse(true, null, Lists.newLinkedList()); } } }