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.artifact; import com.google.common.collect.Iterables; 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.PropertiesAddon; import org.artifactory.addon.rest.AuthorizationRestException; import org.artifactory.addon.rest.MissingRestAddonException; import org.artifactory.addon.rest.RestAddon; import org.artifactory.api.config.CentralConfigService; import org.artifactory.api.context.ContextHelper; import org.artifactory.api.repo.RepositoryBrowsingService; import org.artifactory.api.repo.VirtualRepoItem; import org.artifactory.api.repo.exception.BlackedOutException; import org.artifactory.api.repo.exception.FolderExpectedException; import org.artifactory.api.repo.exception.ItemNotFoundRuntimeException; import org.artifactory.api.rest.artifact.*; import org.artifactory.api.security.AuthorizationService; import org.artifactory.checksum.ChecksumInfo; import org.artifactory.checksum.ChecksumType; import org.artifactory.checksum.ChecksumsInfo; import org.artifactory.descriptor.repo.LocalCacheRepoDescriptor; import org.artifactory.descriptor.repo.LocalRepoDescriptor; import org.artifactory.descriptor.repo.RemoteRepoDescriptor; import org.artifactory.descriptor.repo.VirtualRepoDescriptor; import org.artifactory.fs.FileInfo; import org.artifactory.fs.FolderInfo; import org.artifactory.fs.ItemInfo; import org.artifactory.fs.StatsInfo; import org.artifactory.md.Properties; import org.artifactory.mime.NamingUtils; import org.artifactory.model.common.RepoPathImpl; import org.artifactory.repo.InternalRepoPathFactory; import org.artifactory.repo.Repo; import org.artifactory.repo.RepoPath; import org.artifactory.repo.service.InternalRepositoryService; import org.artifactory.repo.virtual.VirtualRepo; import org.artifactory.rest.common.exception.BadRequestException; import org.artifactory.rest.common.exception.NotFoundException; import org.artifactory.rest.common.list.KeyValueList; import org.artifactory.rest.common.list.StringList; import org.artifactory.rest.common.util.RestUtils; import org.artifactory.util.DoesNotExistException; import org.artifactory.util.HttpUtils; import org.joda.time.format.DateTimeFormat; 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.Nonnull; import javax.annotation.security.RolesAllowed; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.*; import javax.ws.rs.core.*; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Set; import static org.artifactory.api.rest.constant.ArtifactRestConstants.*; /** * @author Eli Givoni */ @Component @Scope(BeanDefinition.SCOPE_PROTOTYPE) @Path(PATH_ROOT + "/{" + PATH_PARAM + ": .+}") @RolesAllowed({ AuthorizationService.ROLE_ADMIN, AuthorizationService.ROLE_USER }) public class ArtifactResource { private static final Logger log = LoggerFactory.getLogger(ArtifactResource.class); private static final String LIST_PARAM = "list"; private static final String DEEP_PARAM = "deep"; private static final String DEPTH_PARAM = "depth"; private static final String LIST_FOLDERS_PARAM = "listFolders"; private static final String MD_TIMESTAMPS_PARAM = "mdTimestamps"; private static final String INCLUDE_ROOT_PATH_PARAM = "includeRootPath"; private static final String LAST_MODIFIED_PARAM = "lastModified"; private static final String PERMISSIONS_PARAM = "permissions"; private static final String STATS_PARAM = "stats"; @Context private HttpServletRequest request; @Context private HttpServletResponse response; @Context private HttpHeaders requestHeaders; @Context private ExtendedUriInfo uriInfo; @Autowired private AuthorizationService authorizationService; @Autowired private AddonsManager addonsManager; @Autowired private InternalRepositoryService repositoryService; @Autowired private RepositoryBrowsingService repoBrowsingService; @Autowired private CentralConfigService centralConfig; @PathParam(PATH_PARAM) String path; @GET @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MT_FOLDER_INFO, MT_FILE_INFO, MT_ITEM_PROPERTIES, MT_FILE_LIST, MT_ITEM_LAST_MODIFIED, MT_ITEM_PERMISSIONS, MT_STATS_INFO }) public Object getStorageInfo() throws IOException { return prepareResponseAccordingToType(); } @PUT public Response savePathProperties(@QueryParam("recursive") String recursive, @QueryParam("properties") KeyValueList properties, @QueryParam("atomic") String atomic) { return restAddon().savePathProperties(path, recursive, properties, atomic); } @DELETE public Response deletePathProperties(@QueryParam("recursive") String recursive, @QueryParam("properties") StringList properties) { return restAddon().deletePathProperties(path, recursive, properties); } private RepoPath repoPathFromRequestPath() { return RestUtils.calcRepoPathFromRequestPath(path); } private Response prepareResponseAccordingToType() throws IOException { if (isFileListRequest()) { return prepareFileListResponse(); } else if (isLastModifiedRequest()) { return prepareLastModifiedResponse(); } else if (isPropertiesRequest()) { return preparePropertiesResponse(); } else if (isPropertiesXmlRequest()) { return preparePropertiesXmlResponse(); } else if (isPermissionsRequest()) { return preparePermissionsResponse(); } else if (isStatsRequest()) { return prepareStatsResponse(); } else { return prepareStorageInfoResponse(); } } private Response prepareStatsResponse() throws IOException { RepoPath repoPath = repoPathFromRequestPath(); if (!authorizationService.canRead(repoPath)) { return prepareUnAuthorizedResponse(repoPath); } if (!repositoryService.exists(repoPath)) { throw new NotFoundException("Unable to find item path"); } StatsInfo statsInfo = repositoryService.getStatsInfo(repoPath); String uri = buildDownloadUri(); if (statsInfo == null) { return okResponse(new ItemStatsInfo(uri, 0, 0, null), MT_STATS_INFO); } if (isMediaTypeAcceptableByUser(MT_STATS_INFO)) { ItemStatsInfo entity = new ItemStatsInfo(uri, statsInfo.getDownloadCount(), statsInfo.getLastDownloaded(), statsInfo.getLastDownloadedBy()); return okResponse(entity, MT_STATS_INFO); } else { return notAcceptableResponse(MT_STATS_INFO); } } private boolean isStatsRequest() { return queryParamsContainKey(STATS_PARAM); } private Response prepareUnAuthorizedResponse(RepoPath unAuthorizedResource) throws IOException { boolean hideUnauthorizedResources = centralConfig.getDescriptor().getSecurity() .isHideUnauthorizedResources(); if (hideUnauthorizedResources) { throw new NotFoundException("Resource not found"); } else { throw new AuthorizationRestException("Request for '" + unAuthorizedResource + "' is forbidden for user '" + authorizationService.currentUsername() + "'."); } } private boolean isFileListRequest() { return queryParamsContainKey(LIST_PARAM); } private boolean isLastModifiedRequest() { return queryParamsContainKey(LAST_MODIFIED_PARAM); } private boolean isPropertiesRequest() { return queryParamsContainKey(PROPERTIES_PARAM); } private boolean isPropertiesXmlRequest() { return queryParamsContainKey(PROPERTIES_XML_PARAM); } private boolean isPermissionsRequest() { return queryParamsContainKey(PERMISSIONS_PARAM); } private boolean queryParamsContainKey(String key) { MultivaluedMap<String, String> queryParameters = queryParams(); return queryParameters.containsKey(key); } private Response prepareFileListResponse() throws IOException { if (authorizationService.isAnonymous()) { throw new AuthorizationRestException("This resource is available to authenticated users only."); } log.debug("Received file list request for: {}. ", path); return writeStreamingFileList(); } private int getQueryParameterAsInt(String parameterName) { if (queryParams().containsKey(parameterName)) { String value = queryParams().getFirst(parameterName); if (StringUtils.isNotBlank(value)) { return convertStringToInt(value); } } return 0; } private MultivaluedMap<String, String> queryParams() { return uriInfo.getQueryParameters(); } private int convertStringToInt(String integer) { return Integer.parseInt(integer); } private Response writeStreamingFileList() throws IOException { try { restAddon().writeStreamingFileList(response, getRequestStorageUri(), path, getQueryParameterAsInt(DEEP_PARAM), getQueryParameterAsInt(DEPTH_PARAM), getQueryParameterAsInt(LIST_FOLDERS_PARAM), getQueryParameterAsInt(MD_TIMESTAMPS_PARAM), getQueryParameterAsInt(INCLUDE_ROOT_PATH_PARAM)); return Response.ok().build(); } catch (IllegalArgumentException iae) { throw new BadRequestException(iae.getMessage()); } catch (DoesNotExistException dnee) { log.debug("Does not exist", dnee); throw new NotFoundException(dnee.getMessage()); } catch (FolderExpectedException fee) { log.debug("Folder expected", fee); throw new BadRequestException(fee.getMessage()); } catch (BlackedOutException boe) { log.debug("Repository is blacked out", boe); throw new NotFoundException(boe.getMessage()); } catch (MissingRestAddonException mrae) { throw mrae; } catch (Exception e) { log.error("Could not retrieve list", e); return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR) .entity("An error occurred while retrieving file list: " + e.getMessage()).build(); } } private String getRequestStorageUri() { RepoPath repoPath = repoPathFromRequestPath(); return RestUtils.buildStorageInfoUri(request, repoPath.getRepoKey(), repoPath.getPath()); } /** * Returns the highest last modified value of the given file or folder (recursively) * * @return Latest modified item */ private Response prepareLastModifiedResponse() throws IOException { fixPathIfNeeded(); try { if (isRequestToNoneLocalRepo()) { throw new BadRequestException("This method can only be invoked on local repositories."); } ItemInfo lastModifiedItem = restAddon().getLastModified(path); String uri = getLastModifiedRequestUri(lastModifiedItem); String lastModifiedAsISo = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") .print(lastModifiedItem.getLastModified()); ItemLastModified itemLastModified = new ItemLastModified(uri, lastModifiedAsISo); Date lastModifiedDate = new Date(lastModifiedItem.getLastModified()); return Response.ok(itemLastModified, MT_ITEM_LAST_MODIFIED).lastModified(lastModifiedDate).build(); } catch (IllegalArgumentException iae) { throw new BadRequestException(iae.getMessage()); } catch (ItemNotFoundRuntimeException infre) { throw new NotFoundException(infre.getMessage()); } } private String getLastModifiedRequestUri(@Nonnull ItemInfo lastModifiedItem) { return RestUtils.buildStorageInfoUri(request, lastModifiedItem.getRepoKey(), lastModifiedItem.getRelPath()); } private boolean isMediaTypeAcceptableByUser(String mediaTypeToCheckString) { List<MediaType> mediaTypesAcceptableByUser = requestHeaders.getAcceptableMediaTypes(); MediaType mediaTypeToCheck = MediaType.valueOf(mediaTypeToCheckString); for (MediaType acceptableMediaType : mediaTypesAcceptableByUser) { //Always accept application/json for backwards compatibility if (mediaTypeToCheck.isCompatible(acceptableMediaType) || MediaType.APPLICATION_JSON_TYPE.equals(acceptableMediaType)) { return true; } } return false; } private Response preparePropertiesResponse() throws IOException { if (isMediaTypeAcceptableByUser(MT_ITEM_PROPERTIES)) { return getPropertiesResponse(); } else { return notAcceptableResponse(MT_ITEM_PROPERTIES); } } private Response getPropertiesResponse() throws IOException { ItemProperties itemProperties = new ItemProperties(); Properties propertiesAnnotatingItem = resolveProperties(); if (propertiesAnnotatingItem != null) { StringList requestProperties = new StringList(queryParams().getFirst(PROPERTIES_PARAM)); if (!requestProperties.isEmpty()) { for (String propertyName : requestProperties) { Set<String> propertySet = propertiesAnnotatingItem.get(propertyName); if ((propertySet != null) && !propertySet.isEmpty()) { itemProperties.properties.put(propertyName, Iterables.toArray(propertySet, String.class)); } } } else { for (String propertyName : propertiesAnnotatingItem.keySet()) { itemProperties.properties.put(propertyName, Iterables.toArray(propertiesAnnotatingItem.get(propertyName), String.class)); } } } if (!itemProperties.properties.isEmpty()) { itemProperties.slf = getRequestStorageUri(); return Response.ok(itemProperties, MT_ITEM_PROPERTIES) .header(org.apache.http.HttpHeaders.CACHE_CONTROL, "no-store").build(); } throw new NotFoundException("No properties could be found."); } private Response preparePropertiesXmlResponse() throws IOException { Properties properties = resolveProperties(); if (properties != null && !properties.isEmpty()) { return Response.ok(properties, MediaType.APPLICATION_XML) .header(org.apache.http.HttpHeaders.CACHE_CONTROL, "no-store").build(); } throw new NotFoundException("No properties could be found."); } private Properties resolveProperties() throws IOException { fixPathIfNeeded(); RepoPath repoPath = RestUtils.calcRepoPathFromRequestPath(path); Repo repo = repositoryService.repositoryByKey(repoPath.getRepoKey()); Properties properties = null; if (repo == null) { return properties; } if (repo.isLocal() || repo.isCache()) { if (repo.isCache()) { updateProperties(repoPath, repo); } properties = repositoryService.getProperties(repoPathFromRequestPath()); } else { VirtualRepo virtualRepo = repositoryService.virtualRepositoryByKey(repoPath.getRepoKey()); if (virtualRepo != null) { VirtualRepoItem virtualRepoItem = virtualRepo.getVirtualRepoItem(repoPath); if (virtualRepoItem != null) { for (String repoKey : virtualRepoItem.getRepoKeys()) { properties = repositoryService.getProperties(new RepoPathImpl(repoKey, repoPath.getPath())); if (properties != null && !properties.isEmpty()) { return properties; } } } } } return properties; } /** * Updates remote properties if cache has expired * * @param repoPath * @param repo */ private void updateProperties(RepoPath repoPath, Repo repo) { PropertiesAddon propertiesAddon = ContextHelper.get().beanForType(AddonsManager.class) .addonByType(PropertiesAddon.class); propertiesAddon.updateRemoteProperties(repo, repoPath); } private void fixPathIfNeeded() { // In case that the path reference to remote repository, then change the path to remote local RepoPath repoPath = RestUtils.calcRepoPathFromRequestPath(path); LocalRepoDescriptor descriptor = repositoryService.localOrCachedRepoDescriptorByKey(repoPath.getRepoKey()); if (descriptor != null && descriptor.isCache()) { path = StringUtils.replaceOnce(path, repoPath.getRepoKey(), descriptor.getKey()); } } private Response preparePermissionsResponse() throws IOException { if (isMediaTypeAcceptableByUser(MT_ITEM_PERMISSIONS)) { if (isRequestToNoneLocalRepo()) { throw new BadRequestException("This method can only be invoked on local repositories."); } return getPermissionsResponse(path); } else { return notAcceptableResponse(MT_ITEM_PERMISSIONS); } } private Response getPermissionsResponse(String path) throws IOException { try { ItemPermissions itemPermissions = restAddon().getItemPermissions(request, path); return okResponse(itemPermissions, MT_ITEM_PERMISSIONS); } catch (IllegalArgumentException iae) { throw new BadRequestException(iae.getMessage()); } catch (ItemNotFoundRuntimeException infre) { throw new NotFoundException(infre.getMessage()); } } private Response prepareStorageInfoResponse() throws IOException { RepoPath repoPath = repoPathFromRequestPath(); String repoKey = repoPath.getRepoKey(); RestBaseStorageInfo storageInfoRest; org.artifactory.fs.ItemInfo itemInfo = null; if (isLocalRepo(repoKey)) { try { if (!authorizationService.canRead(repoPath)) { return prepareUnAuthorizedResponse(repoPath); } itemInfo = repositoryService.getItemInfo(repoPath); } catch (ItemNotFoundRuntimeException e) { //no item found, will send 404 } } else if (isVirtualRepo(repoKey)) { VirtualRepoItem virtualRepoItem = repoBrowsingService.getVirtualRepoItem(repoPath); if (virtualRepoItem == null) { return Response.status(Response.Status.NOT_FOUND).build(); } itemInfo = virtualRepoItem.getItemInfo(); } if (itemInfo == null) { throw new NotFoundException("Unable to find item"); } storageInfoRest = createStorageInfoData(repoKey, itemInfo); // we don't use the repo key from the item info because we want to set the virtual repo key if it came // from a virtual repository storageInfoRest.repo = repoKey; if (itemInfo.isFolder()) { if (isMediaTypeAcceptableByUser(MT_FOLDER_INFO)) { return okResponse(storageInfoRest, MT_FOLDER_INFO); } else { return notAcceptableResponse(MT_FOLDER_INFO); } } else { if (isMediaTypeAcceptableByUser(MT_FILE_INFO)) { return okResponse(storageInfoRest, MT_FILE_INFO); } else { return notAcceptableResponse(MT_FILE_INFO); } } } private RestBaseStorageInfo createStorageInfoData(String repoKey, ItemInfo itemInfo) { if (itemInfo.isFolder()) { return createFolderInfoData(repoKey, (FolderInfo) itemInfo); } else { return createFileInfoData((FileInfo) itemInfo, repoKey); } } private boolean isVirtualRepo(String repoKey) { VirtualRepoDescriptor virtualRepoDescriptor = repositoryService.virtualRepoDescriptorByKey(repoKey); return virtualRepoDescriptor != null; } private boolean isLocalRepo(String repoKey) { LocalRepoDescriptor descriptor = repositoryService.localOrCachedRepoDescriptorByKey(repoKey); return descriptor != null && !(descriptor.isCache() && !descriptor.getKey().equals(repoKey)); } private String buildDownloadUrl(org.artifactory.fs.FileInfo fileInfo) { LocalRepoDescriptor descriptor = repositoryService.localOrCachedRepoDescriptorByKey(fileInfo.getRepoKey()); if (descriptor == null || !descriptor.isCache()) { return null; } RemoteRepoDescriptor remoteRepoDescriptor = ((LocalCacheRepoDescriptor) descriptor).getRemoteRepo(); StringBuilder sb = new StringBuilder(remoteRepoDescriptor.getUrl()); sb.append("/").append(fileInfo.getRelPath()); return sb.toString(); } private RestFileInfo createFileInfoData(FileInfo itemInfo, String repoKey) { RestFileInfo fileInfo = new RestFileInfo(); setBaseStorageInfo(fileInfo, itemInfo, repoKey); fileInfo.mimeType = NamingUtils.getMimeTypeByPathAsString(path); fileInfo.downloadUri = buildDownloadUri(); fileInfo.remoteUrl = buildDownloadUrl(itemInfo); fileInfo.size = String.valueOf(itemInfo.getSize()); ChecksumsInfo checksumInfo = itemInfo.getChecksumsInfo(); ChecksumInfo sha1 = checksumInfo.getChecksumInfo(ChecksumType.sha1); ChecksumInfo md5 = checksumInfo.getChecksumInfo(ChecksumType.md5); String originalSha1 = sha1 != null ? sha1.getOriginal() : checksumInfo.getSha1(); String originalMd5 = md5 != null ? md5.getOriginal() : checksumInfo.getMd5(); String sha256 = getSha256(itemInfo.getRepoPath()); fileInfo.checksums = new RestFileInfo.Checksums(checksumInfo.getSha1(), checksumInfo.getMd5(), sha256); fileInfo.originalChecksums = new RestFileInfo.Checksums(originalSha1, originalMd5); return fileInfo; } /** * get sha256 checksum property and return it * * @param repoPath - file repo path * @return sha 256 */ private String getSha256(RepoPath repoPath) { Properties properties = ContextHelper.get().getRepositoryService().getProperties(repoPath); if (properties != null) { String sha256 = properties.getFirst("sha256"); if (StringUtils.isNotBlank(sha256)) { return sha256; } } return null; } private RestFolderInfo createFolderInfoData(String repoKey, FolderInfo itemInfo) { RestFolderInfo folderInfo = new RestFolderInfo(); setBaseStorageInfo(folderInfo, itemInfo, repoKey); RepoPath folderRepoPath = InternalRepoPathFactory.create(repoKey, itemInfo.getRepoPath().getPath()); folderInfo.children = new ArrayList<>(); //if local or cache repo if (isLocalRepo(repoKey)) { List<ItemInfo> children = repositoryService.getChildren(folderRepoPath); for (ItemInfo child : children) { folderInfo.children.add(new RestFolderInfo.DirItem("/" + child.getName(), child.isFolder())); } //for virtual repo } else { List<VirtualRepoItem> virtualRepoItems = repoBrowsingService.getVirtualRepoItems(folderRepoPath); for (VirtualRepoItem item : virtualRepoItems) { folderInfo.children.add(new RestFolderInfo.DirItem("/" + item.getName(), item.isFolder())); } } return folderInfo; } private void setBaseStorageInfo(RestBaseStorageInfo storageInfoRest, ItemInfo itemInfo, String repoKey) { storageInfoRest.slf = RestUtils.buildStorageInfoUri(request, repoKey, itemInfo.getRelPath()); storageInfoRest.path = "/" + itemInfo.getRelPath(); storageInfoRest.created = RestUtils.toIsoDateString(itemInfo.getCreated()); storageInfoRest.createdBy = itemInfo.getCreatedBy(); storageInfoRest.lastModified = RestUtils.toIsoDateString(itemInfo.getLastModified()); storageInfoRest.modifiedBy = itemInfo.getModifiedBy(); storageInfoRest.lastUpdated = RestUtils.toIsoDateString(itemInfo.getLastUpdated()); } private String buildDownloadUri() { String servletContextUrl = HttpUtils.getServletContextUrl(request); StringBuilder sb = new StringBuilder(servletContextUrl); sb.append("/").append(path); return sb.toString(); } private Response okResponse(Object entity, String mediaType) { return Response.ok(entity, mediaType).build(); } private Response notAcceptableResponse(String producedMediaType) { return Response.status(HttpStatus.SC_NOT_ACCEPTABLE).entity("Resource produces " + producedMediaType + " but client only accepts " + requestHeaders.getAcceptableMediaTypes()).build(); } private boolean isRequestToNoneLocalRepo() { return !isLocalRepo(repoPathFromRequestPath().getRepoKey()); } private RestAddon restAddon() { return addonsManager.addonByType(RestAddon.class); } }