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.resource.ci; import com.google.common.base.Function; import com.google.common.collect.Sets; import com.sun.istack.internal.Nullable; import com.sun.jersey.api.core.ExtendedUriInfo; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpStatus; import org.artifactory.addon.AddonsManager; import org.artifactory.addon.rest.AuthorizationRestException; import org.artifactory.addon.rest.RestAddon; import org.artifactory.api.bintray.BintrayService; import org.artifactory.api.build.BuildRunComparators; import org.artifactory.api.build.BuildService; import org.artifactory.api.common.BasicStatusHolder; import org.artifactory.api.repo.exception.ItemNotFoundRuntimeException; import org.artifactory.api.rest.artifact.PromotionResult; import org.artifactory.api.rest.build.BuildInfo; import org.artifactory.api.rest.build.Builds; import org.artifactory.api.rest.build.BuildsByName; import org.artifactory.api.rest.constant.BintrayRestConstants; import org.artifactory.api.rest.constant.BuildRestConstants; import org.artifactory.api.rest.constant.RestConstants; import org.artifactory.api.search.SearchService; import org.artifactory.api.security.AuthorizationService; import org.artifactory.build.BuildRun; import org.artifactory.build.DetailedBuildRunImpl; import org.artifactory.build.InternalBuildService; import org.artifactory.exception.CancelException; import org.artifactory.rest.common.exception.BadRequestException; import org.artifactory.rest.common.exception.NotFoundException; import org.artifactory.rest.common.exception.RestException; import org.artifactory.rest.common.list.StringList; import org.artifactory.rest.common.util.BintrayRestHelper; import org.artifactory.rest.common.util.RestUtils; import org.artifactory.sapi.common.RepositoryRuntimeException; import org.artifactory.util.DoesNotExistException; import org.jfrog.build.api.Build; import org.jfrog.build.api.BuildRetention; import org.jfrog.build.api.Module; import org.jfrog.build.api.dependency.BuildPatternArtifacts; import org.jfrog.build.api.dependency.BuildPatternArtifactsRequest; import org.jfrog.build.api.release.BintrayUploadInfoOverride; import org.jfrog.build.api.release.Promotion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import javax.annotation.security.RolesAllowed; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import java.io.IOException; import java.net.URLDecoder; import java.text.ParseException; import java.util.Comparator; import java.util.List; import java.util.Set; import static com.google.common.collect.Lists.transform; import static org.artifactory.rest.common.util.RestUtils.getServletContextUrl; /** * A resource to manage the build actions * * @author Noam Y. Tenne */ @Component @Scope(BeanDefinition.SCOPE_PROTOTYPE) @Path(BuildRestConstants.PATH_ROOT) @RolesAllowed({ AuthorizationService.ROLE_ADMIN, AuthorizationService.ROLE_USER }) public class BuildResource { private static final Logger log = LoggerFactory.getLogger(BuildResource.class); @Autowired private AddonsManager addonsManager; @Autowired private BuildService buildService; @Autowired private AuthorizationService authorizationService; @Autowired private SearchService searchService; @Autowired private BintrayService bintrayService; @Context private HttpServletRequest request; @Context private HttpServletResponse response; @Context private ExtendedUriInfo uriInfo; public static final String anonAccessDisabledMsg = "Anonymous access to build info is disabled"; /** * Assemble all, last created, available builds with the last * * @return Builds json object */ @GET @Produces({ BuildRestConstants.MT_BUILDS, MediaType.APPLICATION_JSON }) public Builds getAllBuilds() throws IOException { if (authorizationService.isAnonUserAndAnonBuildInfoAccessDisabled()) { throw new AuthorizationRestException(anonAccessDisabledMsg); } Set<BuildRun> latestBuildsByName = searchService.getLatestBuilds(); if (!latestBuildsByName.isEmpty()) { //Add our builds to the list of build resources Builds builds = new Builds(); builds.slf = RestUtils.getBaseBuildsHref(request); for (BuildRun buildRun : latestBuildsByName) { String buildHref = RestUtils.getBuildRelativeHref(buildRun.getName()); builds.builds.add(new Builds.Build(buildHref, buildRun.getStarted())); } return builds; } throw new NotFoundException("No builds were found"); } /** * Get the build name from the request url and assemble all builds under that name. * * @return BuildsByName json object */ @GET @Path("/{buildName: .+}") @Produces({ BuildRestConstants.MT_BUILDS_BY_NAME, MediaType.APPLICATION_JSON }) public BuildsByName getAllSpecificBuilds(@PathParam("buildName") String buildName) throws IOException { if (authorizationService.isAnonUserAndAnonBuildInfoAccessDisabled()) { throw new AuthorizationRestException(anonAccessDisabledMsg); } Set<BuildRun> buildsByName; try { buildsByName = buildService.searchBuildsByName(buildName); } catch (RepositoryRuntimeException e) { buildsByName = Sets.newHashSet(); } if (!buildsByName.isEmpty()) { BuildsByName builds = new BuildsByName(); builds.slf = RestUtils.getBaseBuildsHref(request) + RestUtils.getBuildRelativeHref(buildName); for (BuildRun buildRun : buildsByName) { String versionHref = RestUtils.getBuildNumberRelativeHref(buildRun.getNumber()); builds.buildsNumbers.add(new BuildsByName.Build(versionHref, buildRun.getStarted())); } return builds; } throw new NotFoundException(String.format("No build was found for build name: %s", buildName)); } /** * Get the build name and number from the request url and send back the exact build for those parameters * * @return BuildInfo json object */ @GET @Path("/{buildName: .+}/{buildNumber: .+}") @Produces({ BuildRestConstants.MT_BUILD_INFO, BuildRestConstants.MT_BUILDS_DIFF, MediaType.APPLICATION_JSON }) public Response getBuildInfo(@PathParam("buildName") String buildName, @PathParam("buildNumber") String buildNumber, @QueryParam("started") String buildStarted, @QueryParam("diff") String diffNumber) throws IOException { if (authorizationService.isAnonUserAndAnonBuildInfoAccessDisabled()) { throw new AuthorizationRestException(anonAccessDisabledMsg); } if (!authorizationService.canDeployToLocalRepository()) { throw new AuthorizationRestException(); } Build build = null; if (StringUtils.isNotBlank(buildStarted)) { BuildRun buildRun = buildService.getBuildRun(buildName, buildNumber, buildStarted); if (buildRun != null) { build = buildService.getBuild(buildRun); } } else { build = buildService.getLatestBuildByNameAndNumber(buildName, buildNumber); } if (build == null) { String msg = String.format("No build was found for build name: %s, build number: %s %s", buildName, buildNumber, StringUtils.isNotBlank(buildStarted) ? ", build started: " + buildStarted : ""); throw new NotFoundException(msg); } if (queryParamsContainKey("diff")) { Build secondBuild = buildService.getLatestBuildByNameAndNumber(buildName, diffNumber); if (secondBuild == null) { throw new NotFoundException(String.format( "No build was found for build name: %s , build number: %s ", buildName, diffNumber)); } BuildRun buildRun = buildService.getBuildRun(build.getName(), build.getNumber(), build.getStarted()); BuildRun secondBuildRun = buildService.getBuildRun(secondBuild.getName(), secondBuild.getNumber(), secondBuild.getStarted()); Comparator<BuildRun> comparator = BuildRunComparators.getBuildStartDateComparator(); if (comparator.compare(buildRun, secondBuildRun) < 0) { throw new BadRequestException( "Build number should be greater than the build number to compare against."); } return prepareBuildDiffResponse(build, secondBuild, request); } else { return prepareGetBuildResponse(build); } } private Response prepareGetBuildResponse(Build build) throws IOException { BuildInfo buildInfo = new BuildInfo(); buildInfo.slf = RestUtils.getBuildInfoHref(request, build.getName(), build.getNumber()); buildInfo.buildInfo = build; return Response.ok(buildInfo).build(); } private Response prepareBuildDiffResponse(Build firstBuild, Build secondBuild, HttpServletRequest request) { RestAddon restAddon = addonsManager.addonByType(RestAddon.class); return restAddon.getBuildsDiff(firstBuild, secondBuild, request); } /** * Returns the outputs of build matching the request * * @param buildPatternArtifactsRequests contains build name and build number or keyword * @return build outputs (build dependencies and generated artifacts). * The returned array will always be the same size as received, returning nulls on non-found builds. */ @POST @Path("/patternArtifacts") @Consumes({ BuildRestConstants.MT_BUILD_PATTERN_ARTIFACTS_REQUEST, RestConstants.MT_LEGACY_ARTIFACTORY_APP, MediaType.APPLICATION_JSON }) @Produces({ BuildRestConstants.MT_BUILD_PATTERN_ARTIFACTS_RESULT, MediaType.APPLICATION_JSON }) public List<BuildPatternArtifacts> getBuildPatternArtifacts( final List<BuildPatternArtifactsRequest> buildPatternArtifactsRequests) { if (authorizationService.isAnonUserAndAnonBuildInfoAccessDisabled()) { throw new AuthorizationRestException(anonAccessDisabledMsg); } final RestAddon restAddon = addonsManager.addonByType(RestAddon.class); final String contextUrl = getServletContextUrl(request); return transform(buildPatternArtifactsRequests, new Function<BuildPatternArtifactsRequest, BuildPatternArtifacts>() { @Override public BuildPatternArtifacts apply(BuildPatternArtifactsRequest input) { return restAddon.getBuildPatternArtifacts(input, contextUrl); } }); } /** * Adds the given build information to the DB * * @param build Build to add */ @PUT @Consumes({ BuildRestConstants.MT_BUILD_INFO, RestConstants.MT_LEGACY_ARTIFACTORY_APP, MediaType.APPLICATION_JSON }) public void addBuild(Build build) throws Exception { log.info("Adding build '{} #{}'", build.getName(), build.getNumber()); if (authorizationService.isAnonUserAndAnonBuildInfoAccessDisabled()) { throw new AuthorizationRestException(anonAccessDisabledMsg); } if (!authorizationService.canDeployToLocalRepository()) { throw new AuthorizationRestException(); } try { buildService.addBuild(build); } catch (CancelException e) { if (log.isDebugEnabled()) { log.debug("An error occurred while adding the build '" + build.getName() + " #" + build.getNumber() + "'.", e); } throw new RestException(e.getErrorCode(), e.getMessage()); } log.info("Added build '{} #{}'", build.getName(), build.getNumber()); BuildRetention retention = build.getBuildRetention(); if (retention != null) { RestAddon restAddon = addonsManager.addonByType(RestAddon.class); BasicStatusHolder multiStatusHolder = new BasicStatusHolder(); restAddon.discardOldBuilds(build.getName(), retention, multiStatusHolder); if (multiStatusHolder.hasErrors()) { throw new RestException("Errors have occurred while maintaining " + "build retention. Please review the system logs for further information."); } else if (multiStatusHolder.hasWarnings()) { throw new RestException("Warnings have been produced while " + "maintaining build retention. Please review the system logs for further information."); } } } /** * Adds the given module information to an existing build * * @param buildName The name of the parent build that should receive the module * @param buildNumber The number of the parent build that should receive the module * @param module Module to add */ @POST @Path("/append/{buildName: .+}/{buildNumber: .+}") @Consumes({ BuildRestConstants.MT_BUILD_INFO_MODULE, RestConstants.MT_LEGACY_ARTIFACTORY_APP, MediaType.APPLICATION_JSON }) public void addModule(@PathParam("buildName") String buildName, @PathParam("buildNumber") String buildNumber, @QueryParam("started") String buildStarted, List<Module> modules) throws Exception { log.info("Adding module to build '{} #{}'", buildName, buildNumber); if (authorizationService.isAnonUserAndAnonBuildInfoAccessDisabled()) { throw new AuthorizationRestException(anonAccessDisabledMsg); } if (!authorizationService.canDeployToLocalRepository()) { throw new AuthorizationRestException(); } Build build = null; if (StringUtils.isNotBlank(buildStarted)) { BuildRun buildRun = buildService.getBuildRun(buildName, buildNumber, buildStarted); if (buildRun != null) { build = buildService.getBuild(buildRun); } } else { build = buildService.getLatestBuildByNameAndNumber(buildName, buildNumber); } if (build == null) { throw new NotFoundException("No builds were found"); } build.getModules().addAll(modules); try { ((InternalBuildService) buildService).updateBuild(new DetailedBuildRunImpl(build)); } catch (CancelException e) { if (log.isDebugEnabled()) { log.debug("An error occurred while adding a module to the build '" + build.getName() + " #" + build.getNumber() + "'.", e); } throw new RestException(e.getErrorCode(), e.getMessage()); } } /** * Promotes a build * * @param buildName Name of build to promote * @param buildNumber Number of build to promote * @param promotion Promotion settings * @return Promotion result */ @POST @Path("/promote/{buildName: .+}/{buildNumber: .+}") @Consumes({ BuildRestConstants.MT_PROMOTION_REQUEST, MediaType.APPLICATION_JSON }) @Produces({ BuildRestConstants.MT_PROMOTION_RESULT, MediaType.APPLICATION_JSON }) public Response promote(@PathParam("buildName") String buildName, @PathParam("buildNumber") String buildNumber, Promotion promotion) throws IOException { if (authorizationService.isAnonUserAndAnonBuildInfoAccessDisabled()) { throw new AuthorizationRestException(anonAccessDisabledMsg); } RestAddon restAddon = addonsManager.addonByType(RestAddon.class); try { if (RestUtils.shouldDecodeParams(request)) { buildName = URLDecoder.decode(buildName, "UTF-8"); buildNumber = URLDecoder.decode(buildNumber, "UTF-8"); } PromotionResult promotionResult = restAddon.promoteBuild(buildName, buildNumber, promotion); return Response.status( promotionResult.errorsOrWarningHaveOccurred() ? HttpStatus.SC_BAD_REQUEST : HttpStatus.SC_OK) .entity(promotionResult).build(); } catch (IllegalArgumentException | ItemNotFoundRuntimeException iae) { throw new BadRequestException(iae.getMessage()); } catch (DoesNotExistException dnee) { throw new NotFoundException(dnee.getMessage()); } catch (ParseException pe) { throw new RestException("Unable to parse given build start date: " + pe.getMessage()); } } /** * Renames structure, content and properties of build info objects * * @param to Replacement build name */ @POST @Path("/rename/{buildName: .+}") public String renameBuild(@PathParam("buildName") String buildName, @QueryParam("to") String to) throws IOException { if (authorizationService.isAnonUserAndAnonBuildInfoAccessDisabled()) { throw new AuthorizationRestException(anonAccessDisabledMsg); } RestAddon restAddon = addonsManager.addonByType(RestAddon.class); try { String from; if (RestUtils.shouldDecodeParams(request)) { from = URLDecoder.decode(buildName, "UTF-8"); } else { from = buildName; } restAddon.renameBuilds(from, to); response.setStatus(HttpStatus.SC_OK); return String.format("Build renaming of '%s' to '%s' was successfully started.\n", from, to); } catch (IllegalArgumentException iae) { throw new BadRequestException(iae.getMessage()); } catch (DoesNotExistException dnne) { throw new NotFoundException(dnne.getMessage()); } } /** * Removes the build with the given name and number * * @return Status message */ @DELETE @Path("/{buildName: .+}") public void deleteBuilds(@PathParam("buildName") String buildName, @QueryParam("artifacts") int artifacts, @QueryParam("buildNumbers") StringList buildNumbers, @QueryParam("deleteAll") int deleteAll) throws IOException { RestAddon restAddon = addonsManager.addonByType(RestAddon.class); try { if (RestUtils.shouldDecodeParams(request)) { buildName = URLDecoder.decode(buildName, "UTF-8"); } restAddon.deleteBuilds(response, buildName, buildNumbers, artifacts, deleteAll); } catch (IllegalArgumentException iae) { throw new BadRequestException(iae.getMessage()); } catch (DoesNotExistException dnne) { throw new NotFoundException(dnne.getMessage()); } response.flushBuffer(); } /** * Pushes a build to Bintray, expects to find the bintrayBuildInfo.json as one of the build's artifacts * * @param buildName Name of build to promote * @param buildNumber Number of build to promote * @param gpgPassphrase (optional) the Passphrase to use in conjunction with the key stored in Bintray to * sign the version * @return result of the operation */ @POST @Path("/pushToBintray/{buildName: .+}/{buildNumber: .+}") @Consumes({ BuildRestConstants.MT_BINTRAY_DESCRIPTOR_OVERRIDE, MediaType.APPLICATION_JSON }) @Produces({ BintrayRestConstants.MT_BINTRAY_PUSH_RESPONSE, MediaType.APPLICATION_JSON }) public Response pushToBintray(@PathParam("buildName") String buildName, @PathParam("buildNumber") String buildNumber, @QueryParam("gpgPassphrase") @Nullable String gpgPassphrase, @QueryParam("gpgSign") @Nullable Boolean gpgSignOverride, @Nullable BintrayUploadInfoOverride override) throws IOException { if (!BintrayRestHelper.isPushToBintrayAllowed()) { throw new AuthorizationRestException(); } BuildRun buildRun; BasicStatusHolder status; try { if (RestUtils.shouldDecodeParams(request)) { buildName = URLDecoder.decode(buildName, "UTF-8"); buildNumber = URLDecoder.decode(buildNumber, "UTF-8"); gpgPassphrase = URLDecoder.decode(gpgPassphrase, "UTF-8"); } buildRun = BuildResourceHelper.validateParamsAndGetBuildInfo(buildName, buildNumber, null); Build build = buildService.getBuild(buildRun); status = bintrayService.pushPromotedBuild(build, gpgPassphrase, gpgSignOverride, override); return BintrayRestHelper.createAggregatedResponse(status, buildName + " #" + buildNumber); } catch (IllegalArgumentException | ParseException iae) { throw new BadRequestException(iae.getMessage()); } catch (DoesNotExistException | ItemNotFoundRuntimeException nee) { throw new NotFoundException(nee.getMessage()); } } private boolean queryParamsContainKey(String key) { MultivaluedMap<String, String> queryParameters = queryParams(); return queryParameters.containsKey(key); } private MultivaluedMap<String, String> queryParams() { return uriInfo.getQueryParameters(); } }