/* * Copyright [2013] [Cloud4SOA, www.cloud4soa.eu] * * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright 2009-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.cloudfoundry.client.lib.rest; import org.cloudfoundry.client.lib.CloudCredentials; import org.cloudfoundry.client.lib.CloudFoundryException; import org.cloudfoundry.client.lib.HttpProxyConfiguration; import org.cloudfoundry.client.lib.RestLogCallback; import org.cloudfoundry.client.lib.domain.CloudSpace; import org.cloudfoundry.client.lib.util.CloudUtil; import org.cloudfoundry.client.lib.util.RestUtil; import org.cloudfoundry.client.lib.util.UploadApplicationPayloadHttpMessageConverter; import org.cloudfoundry.client.lib.domain.UploadApplicationPayload; import org.codehaus.jackson.JsonParseException; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; import org.springframework.util.Assert; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.HttpServerErrorException; import org.springframework.web.client.RequestCallback; import org.springframework.web.client.ResponseExtractor; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Abstract implementation of the CloudControllerClient intended to serve as the base for v1 and v2 implementations. * * @author Ramnivas Laddad * @author A.B.Srinivasan * @author Jennifer Hickey * @author Dave Syer * @author Thomas Risberg */ public abstract class AbstractCloudControllerClient implements CloudControllerClient { private static final String AUTHORIZATION_HEADER_KEY = "Authorization"; private static final String PROXY_USER_HEADER_KEY = "Proxy-User"; protected static final MediaType JSON_MEDIA_TYPE = new MediaType( MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("UTF-8")); // This map only contains framework/runtime mapping for frameworks that we actively support private static Map<String, Integer> FRAMEWORK_DEFAULT_MEMORY = new HashMap<String, Integer>() {{ put("spring", 512); put("lift", 512); put("grails", 512); put("java_web", 512); }}; private static int DEFAULT_MEMORY = 256; private RestTemplate restTemplate; private URL cloudControllerUrl; protected RestUtil restUtil; protected CloudCredentials cloudCredentials; protected URL authorizationEndpoint; protected String token; public AbstractCloudControllerClient(URL cloudControllerUrl, RestUtil restUtil, CloudCredentials cloudCredentials, URL authorizationEndpoint, HttpProxyConfiguration httpProxyConfiguration) { Assert.notNull(cloudControllerUrl, "CloudControllerUrl cannot be null"); Assert.notNull(restUtil, "RestUtil cannot be null"); this.restUtil = restUtil; this.cloudCredentials = cloudCredentials; if (cloudCredentials != null && cloudCredentials.getToken() != null) { this.token = cloudCredentials.getToken(); } this.cloudControllerUrl = cloudControllerUrl; if (authorizationEndpoint != null) { this.authorizationEndpoint = determineAuthorizationEndPointToUse(authorizationEndpoint, cloudControllerUrl); } else { this.authorizationEndpoint = null; } this.restTemplate = restUtil.createRestTemplate(httpProxyConfiguration); configureCloudFoundryRequestFactory(restTemplate); this.restTemplate.setErrorHandler(new ErrorHandler()); this.restTemplate.setMessageConverters(getHttpMessageConverters()); } protected URL determineAuthorizationEndPointToUse(URL authorizationEndpoint, URL cloudControllerUrl) { if (cloudControllerUrl.getProtocol().equals("http") && authorizationEndpoint.getProtocol().equals("https")) { try { URL newUrl = new URL("http", authorizationEndpoint.getHost(), authorizationEndpoint.getPort(), authorizationEndpoint.getFile()); return newUrl; } catch (MalformedURLException e) { // this shouldn't happen return authorizationEndpoint; } } return authorizationEndpoint; } public URL getCloudControllerUrl() { return this.cloudControllerUrl; } public List<CloudSpace> getSpaces() { ArrayList<CloudSpace> list = new ArrayList<CloudSpace>(); return list; } public int[] getApplicationMemoryChoices() { // TODO: Get it from cloudcontroller's 'info/resources' end point int[] generalChoices = new int[] {64, 128, 256, 512, 1024, 2048}; int maxMemory = getInfo().getLimits().getMaxTotalMemory(); int length = 0; for (int generalChoice : generalChoices) { if (generalChoice <= maxMemory) { length++; } } int[] result = new int[length]; System.arraycopy(generalChoices, 0, result, 0, length); return result; } public int getDefaultApplicationMemory(String framework) { Integer memory = FRAMEWORK_DEFAULT_MEMORY.get(framework); if (memory == null) { return DEFAULT_MEMORY; } return memory; } public void updatePassword(String newPassword) { updatePassword(cloudCredentials, newPassword); } public void updateHttpProxyConfiguration(HttpProxyConfiguration httpProxyConfiguration) { ClientHttpRequestFactory requestFactory = restUtil.createRequestFactory(httpProxyConfiguration); restTemplate.setRequestFactory(requestFactory); configureCloudFoundryRequestFactory(restTemplate); } protected RestTemplate getRestTemplate() { return this.restTemplate; } protected String getUrl(String path) { return cloudControllerUrl + (path.startsWith("/") ? path : "/" + path); } public void registerRestLogListener(RestLogCallback callBack) { if (getRestTemplate() instanceof LoggingRestTemplate) { ((LoggingRestTemplate)getRestTemplate()).registerRestLogListener(callBack); } } public void unRegisterRestLogListener(RestLogCallback callBack) { if (getRestTemplate() instanceof LoggingRestTemplate) { ((LoggingRestTemplate)getRestTemplate()).unRegisterRestLogListener(callBack); } } private void configureCloudFoundryRequestFactory(RestTemplate restTemplate) { ClientHttpRequestFactory requestFactory = restTemplate.getRequestFactory(); restTemplate.setRequestFactory( new CloudFoundryClientHttpRequestFactory(requestFactory)); } private List<HttpMessageConverter<?>> getHttpMessageConverters() { List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>(); messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(new StringHttpMessageConverter()); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new UploadApplicationPayloadHttpMessageConverter()); messageConverters.add(getFormHttpMessageConverter()); messageConverters.add(new MappingJacksonHttpMessageConverter()); return messageConverters; } private FormHttpMessageConverter getFormHttpMessageConverter() { FormHttpMessageConverter formPartsMessageConverter = new CloudFoundryFormHttpMessageConverter(); formPartsMessageConverter.setPartConverters(getFormPartsMessageConverters()); return formPartsMessageConverter; } private List<HttpMessageConverter<?>> getFormPartsMessageConverters() { List<HttpMessageConverter<?>> partConverters = new ArrayList<HttpMessageConverter<?>>(); StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(); stringConverter.setSupportedMediaTypes(Collections.singletonList(JSON_MEDIA_TYPE)); stringConverter.setWriteAcceptCharset(false); partConverters.add(stringConverter); partConverters.add(new ResourceHttpMessageConverter()); partConverters.add(new UploadApplicationPayloadHttpMessageConverter()); return partConverters; } private class CloudFoundryClientHttpRequestFactory implements ClientHttpRequestFactory { private static final String LEGACY_TOKEN_PREFIX = "0408"; private ClientHttpRequestFactory delegate; public CloudFoundryClientHttpRequestFactory(ClientHttpRequestFactory delegate) { this.delegate = delegate; } public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { ClientHttpRequest request = delegate.createRequest(uri, httpMethod); if (token != null) { String header = token; if (!header.startsWith(LEGACY_TOKEN_PREFIX) && !header.toLowerCase().startsWith("bearer")) { header = "Bearer " + header; // UAA token without OAuth prefix } request.getHeaders().add(AUTHORIZATION_HEADER_KEY, header); } if (cloudCredentials != null && cloudCredentials.getProxyUser() != null) { request.getHeaders().add(PROXY_USER_HEADER_KEY, cloudCredentials.getProxyUser()); } return request; } } public static class ErrorHandler extends DefaultResponseErrorHandler { @Override public void handleError(ClientHttpResponse response) throws IOException { HttpStatus statusCode = response.getStatusCode(); switch (statusCode.series()) { case CLIENT_ERROR: CloudFoundryException exception = new CloudFoundryException(statusCode, response.getStatusText()); ObjectMapper mapper = new ObjectMapper(); // can reuse, share globally if (response.getBody() != null) { try { @SuppressWarnings("unchecked") Map<String, Object> map = mapper.readValue(response.getBody(), Map.class); exception.setDescription(CloudUtil.parse(String.class, map.get("description"))); } catch (JsonParseException e) { exception.setDescription("Client error"); } catch (IOException e) { exception.setDescription("Client error"); } } else { exception.setDescription("Client error"); } throw exception; case SERVER_ERROR: throw new HttpServerErrorException(statusCode, response.getStatusText()); default: throw new RestClientException("Unknown status code [" + statusCode + "]"); } } } public static class CloudFoundryFormHttpMessageConverter extends FormHttpMessageConverter { @Override protected String getFilename(Object part) { if (part instanceof UploadApplicationPayload) { return ((UploadApplicationPayload) part).getArchive().getFilename(); } return super.getFilename(part); } } protected String doGetFile(String urlPath, Object app, int instanceIndex, String filePath, int startPosition, int endPosition) { Assert.isTrue(startPosition >= -1, "Invalid start position value: " + startPosition); Assert.isTrue(endPosition >= -1, "Invalid end position value: " + endPosition); Assert.isTrue(startPosition < 0 || endPosition < 0 || endPosition >= startPosition, "The end position (" + endPosition + ") can't be less than the start position (" + startPosition + ")"); int start, end; if (startPosition == -1 && endPosition == -1) { start = 0; end = -1; } else { start = startPosition; end = endPosition; } final String range = "bytes=" + (start == -1 ? "" : start) + "-" + (end == -1 ? "" : end); boolean supportsRanges = false; try { supportsRanges = getRestTemplate().execute(getUrl(urlPath), HttpMethod.HEAD, new RequestCallback() { public void doWithRequest(ClientHttpRequest request) throws IOException { request.getHeaders().set("Range", "bytes=0-"); } }, new ResponseExtractor<Boolean>() { public Boolean extractData(ClientHttpResponse response) throws IOException { if (response.getStatusCode().equals(HttpStatus.PARTIAL_CONTENT)) { return true; } return false; } }, app, instanceIndex, filePath); } catch (CloudFoundryException e) { if (e.getStatusCode().equals(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE)) { // must be a 0 byte file return ""; } else { throw e; } } HttpHeaders headers = new HttpHeaders(); if (supportsRanges) { headers.set("Range", range); } HttpEntity<Object> requestEntity = new HttpEntity<Object>(headers); ResponseEntity<String> responseEntity = getRestTemplate().exchange(getUrl(urlPath), HttpMethod.GET, requestEntity, String.class, app, instanceIndex, filePath); String response = responseEntity.getBody(); boolean partialFile = false; if (responseEntity.getStatusCode().equals(HttpStatus.PARTIAL_CONTENT)) { partialFile = true; } if (!partialFile && response != null) { if (start == -1) { return response.substring(response.length() - end); } else { if (start >= response.length()) { if (response.length() == 0) { return ""; } throw new CloudFoundryException(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE, "The starting position " + start + " is past the end of the file content."); } if (end != -1) { if (end >= response.length()) { end = response.length() - 1; } return response.substring(start, end + 1); } else { return response.substring(start); } } } return response; } }