blob: 15b0997c2712b3d9350f5062eaa1887ef3d415a3 [file] [log] [blame]
/**
* 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.
*/
package org.apache.falcon.ambari.view;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.inject.Singleton;
import javax.inject.Inject;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.ambari.view.URLStreamProvider;
import org.apache.ambari.view.ViewContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is a class used to bridge the communication between the falcon-ui and
* the falcon API executing inside ambari.
*/
@Singleton
public class FalconProxyImpersonator {
private static final Logger LOG = LoggerFactory
.getLogger(FalconProxyImpersonator.class);
private static final String GET_METHOD = "GET";
private static final String POST_METHOD = "POST";
private static final String DELETE_METHOD = "DELETE";
private static final String FALCON_ERROR = "<result><status>FAILED</status>";
private static final String[] FORCE_JSON_RESPONSE = { "/entities/list/", "admin/version", };
public static final String VIEW_KERBEROS_PRINCIPAL = "view.kerberos.principal";
public static final String VIEW_KERBEROS_PRINCIPAL_KEYTAB = "view.kerberos.principal.keytab";
private ViewContext viewContext;
/**
* Constructor to get the default viewcontext.
* @param viewContext
*/
@Inject
public FalconProxyImpersonator(ViewContext viewContext) {
this.viewContext = viewContext;
}
/**
* Method to set the ambari user.
* @param headers
* @param ui
* @return
*/
@GET
@Path("/")
public Response setUser(@Context HttpHeaders headers, @Context UriInfo ui) {
try {
String userName = viewContext.getUsername();
return Response.ok(userName).type(getResponseType(userName))
.build();
} catch (Exception ex) {
LOG.error(ex.getMessage(), ex);
return Response.status(Response.Status.BAD_REQUEST)
.entity(ex.toString()).build();
}
}
/**
* Method to attend all the GET calls.
* @param headers
* @param ui
* @return
*/
@GET
@Path("/{path: .*}")
public Response getUsage(@Context HttpHeaders headers, @Context UriInfo ui) {
try {
String serviceURI = buildURI(ui);
return consumeService(headers, serviceURI, GET_METHOD, null);
} catch (Exception ex) {
LOG.error(ex.getMessage(), ex);
return Response.status(Response.Status.BAD_REQUEST)
.entity(ex.toString()).build();
}
}
/**
* Method to attend all the POST calls.
* @param xml
* @param headers
* @param ui
* @return
* @throws IOException
*/
@POST
@Path("/{path: .*}")
public Response handlePost(String xml, @Context HttpHeaders headers,
@Context UriInfo ui) throws IOException {
try {
String serviceURI = buildURI(ui);
return consumeService(headers, serviceURI, POST_METHOD, xml);
} catch (Exception ex) {
LOG.error(ex.getMessage(), ex);
return Response.status(Response.Status.BAD_REQUEST)
.entity(ex.toString()).build();
}
}
/**
* Method to attend all the DELETE calls.
* @param headers
* @param ui
* @return
* @throws IOException
*/
@DELETE
@Path("/{path: .*}")
public Response handleDelete(@Context HttpHeaders headers,
@Context UriInfo ui) throws IOException {
try {
String serviceURI = buildURI(ui);
return consumeService(headers, serviceURI, DELETE_METHOD, null);
} catch (Exception ex) {
LOG.error(ex.getMessage(), ex);
return Response.status(Response.Status.BAD_REQUEST)
.entity(ex.toString()).build();
}
}
/**
* Method set the parametters and cast them to a String.
* @param ui
* @return
*/
private String buildURI(UriInfo ui) {
String serviceURI = getFalconURL();
serviceURI += getUIURI(ui);
StringBuilder urlBuilder = new StringBuilder(serviceURI);
MultivaluedMap<String, String> parameters = ui.getQueryParameters();
boolean firstEntry = true;
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
if (firstEntry) {
urlBuilder.append("?");
} else {
urlBuilder.append("&");
}
boolean firstVal = true;
for (String val : entry.getValue()) {
urlBuilder.append(firstVal ? "" : "&").append(entry.getKey())
.append("=").append(val);
firstVal = false;
}
firstEntry = false;
}
return urlBuilder.toString();
}
private String getUIURI(UriInfo uriInfo) {
String uriPath = uriInfo.getAbsolutePath().getPath();
int index = uriPath.indexOf("proxy/") + 5;
return uriPath.substring(index);
}
/**
* Method to consume the API from the URLStreamProvider.
* @param headers
* @param urlToRead
* @param method
* @param xml
* @return
* @throws Exception
*/
private Response consumeService(HttpHeaders headers, String urlToRead,
String method, String xml) throws Exception {
Response response;
URLStreamProvider streamProvider = viewContext.getURLStreamProvider();
Map<String, String> newHeaders = getHeaders(headers);
newHeaders.put("user.name", viewContext.getUsername());
if (checkForceJsonRepsonse(urlToRead, newHeaders)) {
newHeaders.put("Accept", MediaType.APPLICATION_JSON);
}
LOG.info(String.format("Falcon Url[%s]", urlToRead));
InputStream stream = null;
if (isSecurityEnabled()) {
stream = streamProvider.readAsCurrent(urlToRead, method, xml,
newHeaders);
} else {
stream = streamProvider
.readFrom(urlToRead, method, xml, newHeaders);
}
String sresponse = getStringFromInputStream(stream);
if (sresponse.contains(FALCON_ERROR)
|| sresponse.contains(Response.Status.BAD_REQUEST.name())) {
response = Response.status(Response.Status.BAD_REQUEST)
.entity(sresponse).type(MediaType.TEXT_PLAIN).build();
} else {
return Response.status(Response.Status.OK).entity(sresponse)
.type(getResponseType(sresponse)).build();
}
return response;
}
private boolean isSecurityEnabled() {
return !"simple".equals(viewContext.getProperties().get(
"falcon.authentication.type"));
}
private String getFalconURL() {
String falconUri = "";
if (viewContext.getCluster() != null) {
String tlsEnabled = viewContext.getCluster().getConfigurationValue(
"falcon_startup.properties", "falcon.enableTLS");
String scheme = Boolean.parseBoolean(tlsEnabled) ? "https" : "http";
String falconHost = viewContext.getCluster()
.getHostsForServiceComponent("FALCON", "FALCON_SERVER")
.get(0);
String falconPort = viewContext.getCluster().getConfigurationValue(
"falcon-env", "falcon_port");
falconUri = scheme + "://" + falconHost + ":" + falconPort;
} else {
falconUri = viewContext.getProperties().get("falcon.service.uri");
}
LOG.info("Falcon URI==" + falconUri);
return falconUri;
}
/**
* Method to read the response and send it to the front.
* @param is
* @return
*/
private String getStringFromInputStream(InputStream is) {
BufferedReader br = null;
StringBuilder sb = new StringBuilder();
String line;
try {
br = new BufferedReader(new InputStreamReader(is));
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
LOG.error(e.getMessage(), e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
}
return sb.toString();
}
/**
* Method to cast the response type.
* @param response
* @return
*/
private String getResponseType(String response) {
if (response.startsWith("{")) {
return MediaType.TEXT_PLAIN;
} else if (response.startsWith("<")) {
return MediaType.TEXT_XML;
} else {
return MediaType.TEXT_PLAIN;
}
}
private boolean checkForceJsonRepsonse(String urlToRead,
Map<String, String> headers) throws Exception {
for (int i = 0; i < FORCE_JSON_RESPONSE.length; i++) {
if (urlToRead.contains(FORCE_JSON_RESPONSE[i])) {
return true;
}
}
return false;
}
private Map<String, String> getHeaders(HttpHeaders headers) {
MultivaluedMap<String, String> requestHeaders = headers
.getRequestHeaders();
Set<Entry<String, List<String>>> headerEntrySet = requestHeaders
.entrySet();
HashMap<String, String> headersMap = new HashMap<String, String>();
for (Entry<String, List<String>> headerEntry : headerEntrySet) {
String key = headerEntry.getKey();
List<String> values = headerEntry.getValue();
headersMap.put(key, strJoin(values, ","));
}
return headersMap;
}
// TODO use one of libraries.
private String strJoin(List<String> strings, String separator) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0, il = strings.size(); i < il; i++) {
if (i > 0) {
stringBuilder.append(separator);
}
stringBuilder.append(strings.get(i));
}
return stringBuilder.toString();
}
}