/*
* 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.ranger.service.filter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import org.apache.log4j.Logger;
import org.apache.ranger.common.PropertiesUtil;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import com.sun.jersey.api.container.filter.LoggingFilter;
import com.sun.jersey.api.uri.UriTemplate;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerResponse;
public class RangerRESTAPIFilter extends LoggingFilter {
Logger logger = Logger.getLogger(RangerRESTAPIFilter.class);
static volatile boolean initDone = false;
boolean logStdOut = true;
HashMap<String, String> regexPathMap = new HashMap<String, String>();
HashMap<String, Pattern> regexPatternMap = new HashMap<String, Pattern>();
List<String> regexList = new ArrayList<String>();
List<String> loggedRestPathErrors = new ArrayList<String>();
void init() {
if (initDone) {
return;
}
synchronized (RangerRESTAPIFilter.class) {
if (initDone) {
return;
}
logStdOut = PropertiesUtil.getBooleanProperty(
"xa.restapi.log.enabled", false);
// Build hash map
try {
loadPathPatterns();
} catch (Throwable t) {
logger.error(
"Error parsing REST classes for PATH patterns. Error ignored, but should be fixed immediately",
t);
}
initDone = true;
}
}
/*
* (non-Javadoc)
*
* @see
* com.sun.jersey.spi.container.ContainerRequestFilter#filter(com.sun.jersey
* .spi.container.ContainerRequest)
*/
@Override
public ContainerRequest filter(ContainerRequest request) {
if (!initDone) {
init();
}
if (logStdOut) {
String path = request.getRequestUri().getPath();
if ((request.getMediaType() == null || !"multipart".equals(request.getMediaType()
.getType()))
&& !path.endsWith("/service/general/logs")) {
try {
request = super.filter(request);
} catch (Throwable t) {
logger.error("Error FILTER logging. path=" + path, t);
}
}
}
return request;
}
/*
* (non-Javadoc)
*
* @see
* com.sun.jersey.spi.container.ContainerResponseFilter#filter(com.sun.jersey
* .spi.container.ContainerRequest,
* com.sun.jersey.spi.container.ContainerResponse)
*/
@Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
if (logStdOut) {
// If it is image, then don't call super
if (response.getMediaType() == null) {
logger.info("DELETE ME: Response= mediaType is null");
}
if (response.getMediaType() == null
|| !"image".equals(response.getMediaType().getType())) {
response = super.filter(request, response);
}
}
return response;
}
private void loadPathPatterns() throws ClassNotFoundException {
String pkg = "org.apache.ranger.service";
// List<Class> cList = findClasses(new File(dir), pkg);
@SuppressWarnings("rawtypes")
List<Class> cList = findClasses(pkg);
for (@SuppressWarnings("rawtypes")
Class klass : cList) {
Annotation[] annotations = klass.getAnnotations();
for (Annotation annotation : annotations) {
if (!(annotation instanceof Path)) {
continue;
}
Path path = (Path) annotation;
if (path.value().startsWith("crud")) {
continue;
}
// logger.info("path=" + path.value());
// Loop over the class methods
for (Method m : klass.getMethods()) {
Annotation[] methodAnnotations = m.getAnnotations();
String httpMethod = null;
String servicePath = null;
for (int ma = 0; ma < methodAnnotations.length; ma++) {
if (methodAnnotations[ma] instanceof GET) {
httpMethod = "GET";
} else if (methodAnnotations[ma] instanceof PUT) {
httpMethod = "PUT";
} else if (methodAnnotations[ma] instanceof POST) {
httpMethod = "POST";
} else if (methodAnnotations[ma] instanceof DELETE) {
httpMethod = "DELETE";
} else if (methodAnnotations[ma] instanceof Path) {
servicePath = ((Path) methodAnnotations[ma])
.value();
}
}
if (httpMethod == null) {
continue;
}
String fullPath = path.value();
String regEx = httpMethod + ":" + path.value();
if (servicePath != null) {
if (!servicePath.startsWith("/")) {
servicePath = "/" + servicePath;
}
UriTemplate ut = new UriTemplate(servicePath);
regEx = httpMethod + ":" + path.value()
+ ut.getPattern().getRegex();
fullPath += servicePath;
}
Pattern regexPattern = Pattern.compile(regEx);
if (regexPatternMap.containsKey(regEx)) {
logger.warn("Duplicate regex=" + regEx + ", fullPath="
+ fullPath);
}
regexList.add(regEx);
regexPathMap.put(regEx, fullPath);
regexPatternMap.put(regEx, regexPattern);
logger.info("path=" + path.value() + ", servicePath="
+ servicePath + ", fullPath=" + fullPath
+ ", regEx=" + regEx);
}
}
}
// ReOrder list
int i = 0;
for (i = 0; i < 10; i++) {
boolean foundMatches = false;
List<String> tmpList = new ArrayList<String>();
for (int x = 0; x < regexList.size(); x++) {
boolean foundMatch = false;
String rX = regexList.get(x);
for (int y = 0; y < x; y++) {
String rY = regexList.get(y);
Matcher matcher = regexPatternMap.get(rY).matcher(rX);
if (matcher.matches()) {
foundMatch = true;
foundMatches = true;
// logger.info("rX " + rX + " matched with rY=" + rY
// + ". Moving rX to the top. Loop count=" + i);
break;
}
}
if (foundMatch) {
tmpList.add(0, rX);
} else {
tmpList.add(rX);
}
}
regexList = tmpList;
if (!foundMatches) {
logger.info("Done rearranging. loopCount=" + i);
break;
}
}
if (i == 10) {
logger.warn("Couldn't rearrange even after " + i + " loops");
}
logger.info("Loaded " + regexList.size() + " API methods.");
// for (String regEx : regexList) {
// logger.info("regEx=" + regEx);
// }
}
@SuppressWarnings("rawtypes")
private List<Class> findClasses(String packageName)
throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(
true);
// scanner.addIncludeFilter(new
// AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class));
for (BeanDefinition bd : scanner.findCandidateComponents(packageName)) {
classes.add(Class.forName(bd.getBeanClassName()));
}
return classes;
}
}