org.glassfish.nexus.client.NexusClientImpl Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2012-2014 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.nexus.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.MessageProcessingException;
import javax.ws.rs.client.ClientException;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.nexus.client.beans.ContentItem;
import org.glassfish.nexus.client.beans.ContentItems;
import org.glassfish.nexus.client.beans.Failures;
import org.glassfish.nexus.client.beans.MavenArtifactInfo;
import org.glassfish.nexus.client.beans.MavenInfo;
import org.glassfish.nexus.client.beans.Repo;
import org.glassfish.nexus.client.beans.RepoDetail;
import org.glassfish.nexus.client.beans.RepoDetails;
import org.glassfish.nexus.client.beans.Repos;
import org.glassfish.nexus.client.beans.StagingOperationRequest;
import org.glassfish.nexus.client.beans.StagingOperationRequestData;
import org.glassfish.nexus.client.beans.StagingProfile;
import org.glassfish.nexus.client.beans.StagingProfileRepo;
import org.glassfish.nexus.client.beans.StagingProfileRepos;
import org.glassfish.nexus.client.beans.StagingProfiles;
import org.glassfish.nexus.client.logging.CustomHandler;
import org.glassfish.nexus.client.logging.CustomPrinter;
import org.glassfish.nexus.client.logging.DefaultNexusClientPrinter;
import org.glassfish.nexus.client.logging.DefaultRestClientPrinter;
/**
*
* @author Romain Grecourt
*/
public final class NexusClientImpl implements NexusClient {
private final RestClient restClient;
private final String nexusUrl;
private static final String REPOSITORY_GROUP_PATH = "service/local/repo_groups";
private static final String REPOSITORIES_PATH = "service/local/repositories";
private static final String STAGING_REPO_BULK_PATH = "service/local/staging/bulk";
private static final String PROFILES_PATH = "/service/local/staging/profiles";
private static final String PROFILES_REPOS_PATH = "/service/local/staging/profile_repositories";
private static final String SEARCH_PATH = "service/local/lucene/search";
private static final Logger LOGGER;
private static final CustomHandler LOGGER_HANDLER;
private final SyncedClose syncedClose = new SyncedClose();
private final SyncedDrop syncedDrop = new SyncedDrop();
private final SyncedPromote syncedPromote = new SyncedPromote();
private static NexusClientImpl instance;
private static List stagingProfileRepositories = null;
private static HashMap stagingProfileRepositoriesMap;
static {
LOGGER = Logger.getLogger(NexusClientImpl.class.getSimpleName());
LOGGER_HANDLER = new CustomHandler();
LOGGER.addHandler(LOGGER_HANDLER);
LOGGER.setUseParentHandlers(false);
LOGGER.setLevel(Level.ALL);
}
public static NexusClient init(
RestClient restClient,
String nexusUrl,
CustomPrinter p){
LOGGER_HANDLER.setPrinter(p);
instance = new NexusClientImpl(restClient, nexusUrl);
LOGGER.log(Level.FINE, instance.toString());
return instance;
}
private interface SyncedOperation {
void doOperation(String desc, String[] repoIds, String[] params);
Object doReturn(StagingProfileRepo repo);
boolean isOperationComplete(StagingProfileRepo repo);
}
private class SyncedClose implements SyncedOperation {
public void doOperation(String desc, String[] repoIds, String[] params) {
_closeStagingRepo(desc, repoIds);
}
public Object doReturn(StagingProfileRepo repo) {
return null;
}
public boolean isOperationComplete(StagingProfileRepo repo) {
return repo!=null && !repo.isOpen();
}
}
private class SyncedPromote implements SyncedOperation {
public void doOperation(String desc, String[] repoIds, String[] params) {
if(params == null || params.length != 1){
throw new IllegalArgumentException(
"params must contain promotion profile");
}
_promoteStagingRepo(params[0], desc, repoIds);
}
public Object doReturn(StagingProfileRepo repo) {
StagingProfileRepo srepo =
getStagingProfileRepo(repo.getParentGroupId());
if(srepo != null){
return new Repo(srepo);
}
return null;
}
public boolean isOperationComplete(StagingProfileRepo repo) {
return repo != null && repo.getParentGroupId() != null;
}
}
private class SyncedDrop implements SyncedOperation {
public void doOperation(String desc, String[] repoIds, String[] params) {
_dropStagingRepo(desc, repoIds);
}
public Object doReturn(StagingProfileRepo repo) {
return null;
}
public boolean isOperationComplete(StagingProfileRepo repo) {
return repo == null;
}
}
public String getNexusURL() {
return nexusUrl;
}
private static enum Operation {
close, promote, drop
}
public static NexusClient getInstance(){
return instance;
}
private NexusClientImpl(RestClient restClient, String nexusUrl) {
this.restClient = restClient;
this.nexusUrl = nexusUrl;
}
WebTarget target(String path){
return restClient.getClient().target(nexusUrl).path(path);
}
Builder request(String path){
return target(path).request(MediaType.APPLICATION_JSON);
}
private Response get(String path) throws NexusClientException {
try {
return request(path).get();
} catch (ClientException ex) {
throw new NexusClientException(ex);
}
}
private void refreshStagingRepos() throws NexusClientException {
StagingProfileRepos retVal =
(StagingProfileRepos) handleResponse(
get(PROFILES_REPOS_PATH),
StagingProfileRepos.class);
stagingProfileRepositories = Arrays.asList(retVal.getData());
stagingProfileRepositoriesMap = new HashMap();
for (StagingProfileRepo profileRepo : stagingProfileRepositories) {
stagingProfileRepositoriesMap.put(profileRepo.getRepositoryId(), profileRepo);
}
}
private static String checksum(File datafile) {
try {
MessageDigest md = MessageDigest.getInstance("SHA1");
FileInputStream fis = new FileInputStream(datafile);
byte[] dataBytes = new byte[1024];
int nread;
while ((nread = fis.read(dataBytes)) != -1) {
md.update(dataBytes, 0, nread);
}
byte[] mdbytes = md.digest();
//convert the byte to hex format
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < mdbytes.length; i++) {
sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
} catch (NoSuchAlgorithmException ex) {
throw new NexusClientException(ex);
} catch (IOException ex) {
throw new NexusClientException(ex);
}
}
private Response stagingOperation(
Operation op,
String[] repoIds,
String profileGroup,
String desc)
throws NexusClientException {
if (repoIds == null || repoIds.length == 0) {
throw new NexusClientException(
"repoId list is null or empty, can't perform a staging operation");
}
Response response = null;
try {
response = request(STAGING_REPO_BULK_PATH + '/' + op).post(
Entity.entity(new StagingOperationRequestData(
new StagingOperationRequest(repoIds, profileGroup, op + " - " +String.valueOf(desc)))
,MediaType.APPLICATION_JSON));
} catch (ClientException ex) {
throw new NexusClientException(ex);
}
refreshStagingRepos();
return response;
}
private Object retry(
SyncedOperation r,
String desc,
String[] repoIds,
String extraParams[],
int retryCount,
long timeout)
throws NexusClientException {
int currentRetryCount = 0;
long startTime = System.currentTimeMillis(), currentTimeout = 0;
r.doOperation(desc, repoIds, extraParams);
while(true){
if(currentRetryCount > retryCount){
throw new NexusClientException("retryCount reached: "+currentRetryCount);
}
if(currentTimeout > timeout){
throw new NexusClientException("timeout reached: "+timeout);
}
refreshStagingRepos();
StagingProfileRepo repo = null;
boolean operationComplete = true;
for(String repoId : repoIds){
repo = getStagingProfileRepo(repoId);
if(!r.isOperationComplete(repo)){
operationComplete = false;
break;
}
}
if(operationComplete){
return r.doReturn(repo);
} else {
long currentTime = System.currentTimeMillis();
currentTimeout = currentTime - startTime;
currentRetryCount ++;
LOGGER.log(Level.INFO,
"State of operation ({0}) isn''t verified yet... retrying({1})",
new Object[]{
r.getClass().getSimpleName(),
currentRetryCount
});
}
}
}
private static Object handleResponse(
Response r,
Class c) throws NexusClientException {
// 400 means failure entity
if (r.getStatus() == 400) {
try {
throw new NexusFailureException(r.readEntity(Failures.class));
} catch (MessageProcessingException ex) {
throw new NexusClientException(ex);
}
}
// something is wrong
if (r.getStatus() >= 299) {
throw new NexusResponseException(r.getStatus());
}
// TODO throw exception if content type isn't application/json
if (c != null) {
try {
return r.readEntity(c);
} catch (MessageProcessingException ex) {
throw new NexusClientException(ex);
}
}
return null;
}
public Repo getStagingRepo(String repoName) throws NexusClientException {
if (stagingProfileRepositories == null) {
refreshStagingRepos();
}
return ((Repos) handleResponse(get(
REPOSITORIES_PATH + "/" + repoName), Repos.class)).getData();
}
public Set getGroupTree(String repoId){
Set repos = new HashSet();
for(ContentItem item : ((ContentItems) handleResponse(
get(PROFILES_REPOS_PATH+"/tree/"+repoId),ContentItems.class)).getData()){
String[] tokens = item.getResourceURI().split("/");
repos.add(new Repo(stagingProfileRepositoriesMap.get(tokens[tokens.length-1])));
}
return repos;
}
public StagingProfileRepo getStagingProfileRepo(String repoId){
return stagingProfileRepositoriesMap.get(repoId);
}
public void closeStagingRepo(
final String desc,
final String[] repoIds,
final int retryCount,
final long timeout)
throws NexusClientException {
retry(syncedClose, desc, repoIds, repoIds, retryCount, timeout);
}
public void closeStagingRepo(
final String desc,
final String[] repoIds)
throws NexusClientException {
retry(
syncedClose,
desc,
repoIds,
null,
StagingOperation.DEFAULT_RETRY_COUNT,
StagingOperation.DEFAULT_TIMEOUT);
}
public void dropStagingRepo(
final String desc,
final String[] repoIds,
final int retryCount,
final long timeout)
throws NexusClientException {
retry(syncedDrop,desc, repoIds, null, retryCount, timeout);
}
public void dropStagingRepo(
final String desc,
final String[] repoIds)
throws NexusClientException {
retry(
syncedDrop,
desc,
repoIds,
null,
StagingOperation.DEFAULT_RETRY_COUNT,
StagingOperation.DEFAULT_TIMEOUT);
}
public Repo promoteStagingRepo(
final String promotionProfile,
final String desc,
final String[] repoIds,
final int retryCount,
final long timeout)
throws NexusClientException {
return (Repo) retry(
syncedPromote,
desc,
repoIds,
new String[]{promotionProfile},
retryCount,
timeout);
}
public Repo promoteStagingRepo(
final String promotionProfile,
final String desc,
final String[] repoIds)
throws NexusClientException {
return (Repo) retry(
syncedPromote,
desc,
repoIds,
new String[]{promotionProfile},
StagingOperation.DEFAULT_RETRY_COUNT,
StagingOperation.DEFAULT_TIMEOUT);
}
private void _closeStagingRepo(
String desc,
String[] repoIds)
throws NexusClientException {
LOGGER.info(" ");
LOGGER.log(Level.INFO,
"-- closing {0} --",
Arrays.toString(repoIds));
handleResponse(stagingOperation(Operation.close, repoIds, null,desc), null);
}
private void _dropStagingRepo(String desc, String[] repoIds) throws NexusClientException {
LOGGER.info(" ");
LOGGER.log(Level.INFO,
"-- droping {0} --",
Arrays.toString(repoIds));
handleResponse(stagingOperation(Operation.drop, repoIds, null, desc), null);
}
private Repo _promoteStagingRepo(
String promotionProfile,
String desc,
String[] repoIds)
throws NexusClientException {
LOGGER.info(" ");
LOGGER.log(Level.INFO,
"-- searching for the promotion profile id of \"{0}\" --",
promotionProfile);
for (StagingProfile profile : ((StagingProfiles) handleResponse(
get(PROFILES_PATH),
StagingProfiles.class))
.getData()) {
if (profile.getMode().equals("GROUP")) {
if (profile.getName().equals(promotionProfile)) {
LOGGER.info(" ");
LOGGER.log(Level.INFO,
"-- promoting {0} with promotion profile \"{1}\" --",
new Object[]{Arrays.toString(repoIds), promotionProfile});
StagingProfileRepo repo = getStagingProfileRepo(repoIds[0]);
if(repo == null){
throw new NexusClientException(
"unable to find the staging repository for id "+Arrays.toString(repoIds));
}
if(repo.isOpen()){
throw new NexusClientException(
"can't promote, repository is still in 'open' state "+Arrays.toString(repoIds));
}
if(repo.getParentGroupId() == null){
// promote if not already promoted
handleResponse(
stagingOperation(
Operation.promote,
repoIds,
profile.getId(),
desc), null);
}
// try to return the repo or null if not found.
repo = getStagingProfileRepo(repoIds[0]);
if(repo != null && repo.getParentGroupId() != null){
return new Repo(
stagingProfileRepositoriesMap.get(
repo.getParentGroupId()));
} else {
return null;
}
}
}
}
throw new NexusClientException(
"unable to find the following promotion profile\""
+ promotionProfile
+ "\"");
}
private static StringBuilder getRepoContentURL(String repoId){
StringBuilder sb = new StringBuilder(REPOSITORIES_PATH);
sb.append('/');
sb.append(repoId);
sb.append('/');
sb.append("content");
sb.append('/');
return sb;
}
private ContentItems getRepoContent(StringBuilder contentPath, String path){
ContentItems content =
(ContentItems) handleResponse(
request(contentPath.append(path).toString()).get()
, ContentItems.class);
return content;
}
private void scrubRepo(
String repoId,
String path,
Set artifacts)
throws NexusClientException {
StringBuilder repoContentURL = getRepoContentURL(repoId);
String root = repoContentURL.toString();
ContentItems content = getRepoContent(repoContentURL, path);
for(ContentItem item : content.getData()){
// if file
if(item.getLeaf()){
if(item.isValidArtifactFile()){
MavenArtifactInfo artifact =
((MavenInfo) handleResponse(
target(root+"/"+item.getRelativePath())
.queryParam("describe", "maven2")
.request(MediaType.APPLICATION_JSON).get(),
MavenInfo.class)).getData()[0];
LOGGER.log(Level.INFO, "found {0}", artifact);
artifacts.add(artifact);
}
} else {
// if directory
if(item.getSizeOnDisk() == -1){
scrubRepo(repoId, item.getRelativePath(), artifacts);
}
}
}
}
public boolean existsInRepoGroup(String repoGroup, MavenArtifactInfo artifact)
throws NexusClientException {
ContentItem[] items = ((ContentItems) handleResponse(
get(REPOSITORY_GROUP_PATH+'/'+repoGroup+"/"+artifact.getRepositoryRelativePath()),
ContentItems.class)).getData();
return (items!= null && items.length > 0);
}
public Set getArtifactsInRepo(
String repoId)
throws NexusClientException {
LOGGER.info(" ");
LOGGER.log(Level.INFO
, "-- retrieving full content of repository [{0}] --"
, repoId);
Set artifacts = new HashSet();
scrubRepo(repoId, "", artifacts);
return artifacts;
}
public Set getArtifactsInRepo(String repoId, String path)
throws NexusClientException {
LOGGER.info(" ");
LOGGER.log(Level.INFO
, "-- retrieving content of [{0}] in repository [{1}] --"
, new Object[]{repoId});
Set artifacts = new HashSet();
scrubRepo(repoId, "", artifacts);
return artifacts;
}
public void deleteContent(
String repoId,
String path)
throws NexusClientException {
LOGGER.info(" ");
LOGGER.log(Level.INFO
,"-- deleting content of [{0}] in repository [{1}]] --"
, new Object[]{path, nexusUrl});
StringBuilder repoContentURL = getRepoContentURL(repoId);
ContentItems repoContent = getRepoContent(repoContentURL, path);
if (repoContent != null && repoContent.getData() != null) {
for (ContentItem item : repoContent.getData()) {
if (item.getLeaf()) {
String itemPath = item.getResourceURI().replace(nexusUrl, "");
handleResponse(target(itemPath).request().delete(), null);
LOGGER.log(Level.INFO, "deleted {0}", item.getRelativePath());
}
}
}
}
public Repo getStagingRepo(
String stagingProfile,
MavenArtifactInfo refArtifact)
throws NexusClientException {
String refChecksum = checksum(refArtifact.getFile());
LOGGER.info(" ");
LOGGER.info("-- searching for the staging repository --");
if (stagingProfileRepositories == null)
refreshStagingRepos();
for (StagingProfileRepo repo : stagingProfileRepositories) {
if (repo.getProfileName().equals(stagingProfile)
&& repo.getUserId().equals(restClient.getUsername())) {
// get checksum from repo
StringBuilder sb = new StringBuilder(REPOSITORIES_PATH);
sb.append('/');
sb.append(repo.getRepositoryId());
sb.append('/');
sb.append("content");
sb.append('/');
sb.append(refArtifact.getRepositoryRelativePath());
sb.append(".sha1");
String checksum = null;
try{
checksum = (String) handleResponse(request(sb.toString()).get(),String.class);
} catch (NexusResponseException ex){
LOGGER.log(Level.INFO,
"[{0}] does not contain the ref artifact",
new Object[]{repo.getRepositoryId()});
}
// if the checksum is not null and not empty, the coordinates exist
// we always return the staging repo, even if the checksum does not match
// since there can be only one representation of a coordinate
if (checksum != null) {
if (refChecksum.equals(checksum)) {
LOGGER.log(Level.INFO,
"found staging repository: [{0}]",
new Object[]{repo.getRepositoryId()});
} else {
LOGGER.log(Level.WARNING,
"[{0}] contains a different version of {1}",
new Object[]{repo.getRepositoryId(), refArtifact});
}
return getStagingRepo(repo.getRepositoryId());
}
}
}
throw new NexusClientException(
"unable to find an open staging repository for staging profile \""
+ stagingProfile
+ "\" and ref artifact \""
+ refArtifact
+ "\"");
}
public Repo getStagingRepo(File f) throws NexusClientException {
RepoDetail[] results = ((RepoDetails) handleResponse(
target(SEARCH_PATH)
.queryParam("sha1", checksum(f))
.request(MediaType.APPLICATION_JSON).get(),
RepoDetails.class)).getRepoDetails();
if (results != null) {
for (RepoDetail detail : results) {
if (!detail.isGroup()) {
return getStagingRepo(detail.getRepositoryId());
}
}
}
return null;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder("NexusClient{");
sb.append("URL=");
sb.append(nexusUrl);
sb.append(", ");
sb.append(restClient.toString());
sb.append("}");
return sb.toString();
}
public static void main(String[] args) {
NexusClient nexusClient = NexusClientImpl.init(
new RestClient(
null, 0,
"user", "password",
true,
new DefaultRestClientPrinter())
,"https://maven.java.net"
, new DefaultNexusClientPrinter());
Repo repo = nexusClient.getStagingRepo(
"org-glassfish",
new MavenArtifactInfo("groupId", "artifactId", "version", "classifier", "extension", null));
repo.close("Add some message here");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy