/******************************************************************************* * Copyright (c) 2015 Red Hat, Inc. Distributed under license by Red Hat, Inc. * All rights reserved. This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, and is * available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: Red Hat, Inc. ******************************************************************************/ package com.openshift.internal.restclient; import com.openshift.restclient.IApiTypeMapper; import com.openshift.restclient.IApiTypeMapper.IVersionedApiResource; import com.openshift.restclient.OpenShiftException; import com.openshift.restclient.ResourceKind; import com.openshift.restclient.UnsupportedEndpointException; import com.openshift.restclient.http.IHttpConstants; import com.openshift.restclient.model.IResource; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; /** * Helper class to build the URL connection string in the proper * format * * @author Jeff Cantrill */ public class URLBuilder { private static final Logger LOG = LoggerFactory.getLogger(URLBuilder.class); private String baseUrl; private String kind; private String name; private ArrayList<AbstractMap.SimpleEntry<String,String>> params = new ArrayList<>(); private final IApiTypeMapper typeMappings; private String apiVersion; private String namespace; private String subResource; private String subContext; /** * * @param baseUrl * @param typeMappings the map of kinds to endpoint * @param resource */ URLBuilder(URL baseUrl, IApiTypeMapper typeMappings, IResource resource) { this(baseUrl, typeMappings); resource(resource); } /** * * @param baseUrl * @param typeMappings the map of kinds to endpoint */ public URLBuilder(URL baseUrl, IApiTypeMapper typeMappings) { this.baseUrl = baseUrl.toString().replaceAll("/*$", ""); this.typeMappings = typeMappings; } public URLBuilder apiVersion(String apiVersion){ this.apiVersion = apiVersion; return this; } public URLBuilder namespace(String namespace){ if(StringUtils.isBlank(namespace)) return this; this.namespace = namespace; return this; } public URLBuilder name(String name) { this.name = name; return this; } public URLBuilder kind(String kind) { if(!ResourceKind.values().contains(kind)) { LOG.warn(String.format("There kind '%s' is not recognized by this client; this operation may fail.", kind)); } this.kind = kind; return this; } public URLBuilder resource(IResource resource) { if (resource == null) return this; this.name = resource.getName(); kind(resource.getKind()); namespace(resource.getNamespace()); return this; } public URLBuilder addParmeter(String key, String value) { params.add(new AbstractMap.SimpleEntry<>( key, value )); return this; } public URLBuilder subresource(String value) { this.subResource = value; return this; } public URLBuilder subContext(String value) { this.subContext = value; return this; } /** * Builds a URL based on the information provided. Either a resource or * a resource kind must be provided * @return */ public URL build() { StringBuilder url = new StringBuilder(baseUrl); if (kind == null) throw new RuntimeException( "Unable to build a URL because the ResourceKind is unknown"); buildWithNamespaceInPath(url); try { if(LOG.isDebugEnabled()) { LOG.debug(String.format("Built url: %s", url.toString())); } return new URL(url.toString()); } catch (MalformedURLException e) { throw new RuntimeException(e); } } private void buildWithNamespaceInPath(StringBuilder url) { if(!typeMappings.isSupported(apiVersion, kind)) { throw new UnsupportedEndpointException("Unable to determine the api endpoint for kind '%s'", kind); } url.append("/"); IVersionedApiResource apiResource = typeMappings.getEndpointFor(apiVersion, kind); url.append(apiResource.getPrefix()).append("/").append(apiResource.getVersion()); if(namespace == null && apiResource.isNamespaced()) { LOG.debug("The api endpoint for kind '{}' requires a namespace but none was provided. Will only work for priviledged user.", kind); } if(!ResourceKind.PROJECT.equals(kind) && namespace != null) { url.append("/namespaces/") .append(namespace); } url.append("/").append(apiResource.getName()); if (name != null) { url.append("/").append(name); } if(StringUtils.isNotBlank(subResource) && !apiResource.isSupported(subResource)){ throw new OpenShiftException("The api endpoint for kind '%s' && subresource '%s' is not supported by the cluster", kind, subResource); } if(StringUtils.isNotBlank(subResource)) { url.append("/").append(subResource); } if (StringUtils.isNotBlank(subContext)){ url.append("/").append(subContext); } url = appendParameters(url); } private StringBuilder appendParameters(StringBuilder url) { if (!params.isEmpty()) { url.append(IHttpConstants.QUESTION_MARK); for ( Iterator<AbstractMap.SimpleEntry<String,String>> iterator = params.iterator(); iterator.hasNext(); ) { AbstractMap.SimpleEntry<String,String> entry = iterator.next(); try { if(StringUtils.isNotBlank(entry.getValue())) { url.append(entry.getKey()) .append(IHttpConstants.EQUALS) .append(URLEncoder.encode(entry.getValue(), "UTF-8")); }else { LOG.error("Unable to append parameter: {} since it is blank", entry.getKey()); } } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } if (iterator.hasNext()) { url.append(IHttpConstants.AMPERSAND); } } } return url; } public URLBuilder watch() { addParmeter("watch", "true"); return this; } public String websocket() { String url = build().toString(); url = "wss" + url.substring(url.indexOf(":")); if(LOG.isDebugEnabled()) { LOG.debug(String.format("Built url: %s", url)); } return url; } public URLBuilder addParameters(Map<String, String> params) { params.forEach((k,v)->addParmeter(k, v)); return this; } }