Java tutorial
/* * Copyright 2000-2011 JetBrains s.r.o. * * 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 jetbrains.buildServer.server.rest; import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.Function; import jetbrains.buildServer.ExtensionHolder; import jetbrains.buildServer.controllers.BaseController; import jetbrains.buildServer.plugins.bean.ServerPluginInfo; import jetbrains.buildServer.server.rest.jersey.JerseyWebComponent; import jetbrains.buildServer.server.rest.request.Constants; import jetbrains.buildServer.serverSide.SBuildServer; import jetbrains.buildServer.serverSide.SecurityContextEx; import jetbrains.buildServer.serverSide.TeamCityProperties; import jetbrains.buildServer.util.FuncThrow; import jetbrains.buildServer.util.StringUtil; import jetbrains.buildServer.web.openapi.WebControllerManager; import jetbrains.buildServer.web.util.WebUtil; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.web.context.ServletContextAware; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.ModelAndView; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.*; /** * @author Yegor.Yarko * Date: 23.03.2009 */ public class APIController extends BaseController implements ServletContextAware { final Logger LOG = Logger.getInstance(APIController.class.getName()); private JerseyWebComponent myWebComponent; private final ConfigurableApplicationContext myConfigurableApplicationContext; private final SecurityContextEx mySecurityContext; private final ExtensionHolder myExtensionHolder; private final ClassLoader myClassloader; private String myAuthToken; private RequestPathTransformInfo myRequestPathTransformInfo; public APIController(final SBuildServer server, WebControllerManager webControllerManager, final ConfigurableApplicationContext configurableApplicationContext, final SecurityContextEx securityContext, final RequestPathTransformInfo requestPathTransformInfo, final ServerPluginInfo pluginDescriptor, final ExtensionHolder extensionHolder) throws ServletException { super(server); myExtensionHolder = extensionHolder; setSupportedMethods(new String[] { METHOD_GET, METHOD_HEAD, METHOD_POST, "PUT", "OPTIONS", "DELETE" }); myConfigurableApplicationContext = configurableApplicationContext; mySecurityContext = securityContext; myRequestPathTransformInfo = requestPathTransformInfo; final List<String> originalBindPaths = getBindPaths(pluginDescriptor); List<String> bindPaths = new ArrayList<String>(originalBindPaths); bindPaths.addAll(addPrefix(originalBindPaths, StringUtil.removeTailingSlash(WebUtil.HTTP_AUTH_PREFIX))); bindPaths.addAll(addPrefix(originalBindPaths, StringUtil.removeTailingSlash(WebUtil.GUEST_AUTH_PREFIX))); Map<String, String> transformBindPaths = new HashMap<String, String>(); addEntries(transformBindPaths, bindPaths, Constants.API_URL); addEntries(transformBindPaths, addSuffix(bindPaths, Constants.EXTERNAL_APPLICATION_WADL_NAME), Constants.JERSEY_APPLICATION_WADL_NAME); myRequestPathTransformInfo.setPathMapping(transformBindPaths); LOG.debug("Will use request mapping: " + myRequestPathTransformInfo); registerController(webControllerManager, originalBindPaths); myClassloader = getClass().getClassLoader(); if (TeamCityProperties.getBoolean("rest.use.authToken")) { try { myAuthToken = URLEncoder.encode(UUID.randomUUID().toString() + (new Date()).toString().hashCode(), "UTF-8"); LOG.info("Authentication token for superuser generated: '" + myAuthToken + "'."); } catch (UnsupportedEncodingException e) { LOG.warn(e); } } } private static void addEntries(final Map<String, String> map, final List<String> keys, final String value) { for (String key : keys) { map.put(key, value); } } private List<String> addPrefix(final List<String> paths, final String prefix) { List<String> result = new ArrayList<String>(paths.size()); for (String path : paths) { result.add(prefix + path); } return result; } private List<String> addSuffix(final List<String> paths, final String suffix) { List<String> result = new ArrayList<String>(paths.size()); for (String path : paths) { result.add(path + suffix); } return result; } private void registerController(final WebControllerManager webControllerManager, final List<String> bindPaths) { try { for (String controllerBindPath : bindPaths) { LOG.debug("Binding REST API to path '" + controllerBindPath + "'"); webControllerManager.registerController(controllerBindPath + "/**", this); } } catch (Exception e) { LOG.error("Error registering controller", e); } } private List<String> getBindPaths(final ServerPluginInfo pluginDescriptor) { String bindPath = pluginDescriptor.getParameterValue(Constants.BIND_PATH_PROPERTY_NAME); if (bindPath == null) { return Collections.singletonList(Constants.API_URL); } final String[] bindPaths = bindPath.split(","); if (bindPath.length() == 0) { LOG.error("Invalid REST API bind path in plugin descriptor: '" + bindPath + "', using defaults"); return Collections.singletonList(Constants.API_URL); } return Arrays.asList(bindPaths); } private void init() throws ServletException { myWebComponent = new JerseyWebComponent(); myWebComponent.setExtensionHolder(myExtensionHolder); myWebComponent.setWebApplicationContext(myConfigurableApplicationContext); myWebComponent.init(createJerseyConfig()); } private FilterConfig createJerseyConfig() { return new FilterConfig() { Map<String, String> initParameters = new HashMap<String, String>(); { // initParameters.put("com.sun.jersey.config.property.WadlGeneratorConfig", "jetbrains.buildServer.server.rest.WadlGenerator"); initParameters.put("com.sun.jersey.config.property.packages", "jetbrains.buildServer.server.rest.request;" + getPackagesFromExtensions()); } private String getPackagesFromExtensions() { return StringUtil.join(myServer.getExtensions(RESTControllerExtension.class), new Function<RESTControllerExtension, String>() { public String fun(final RESTControllerExtension restControllerExtension) { return restControllerExtension.getPackage(); } }, ";"); } public String getFilterName() { return "jerseyFilter"; } public ServletContext getServletContext() { //return APIController.this.getServletContext(); // workaround for http://jetbrains.net/tracker/issue2/TW-7656 for (ApplicationContext ctx = getApplicationContext(); ctx != null; ctx = ctx.getParent()) { if (ctx instanceof WebApplicationContext) { return ((WebApplicationContext) ctx).getServletContext(); } } throw new RuntimeException("WebApplication context was not found."); } public String getInitParameter(final String s) { return initParameters.get(s); } public Enumeration getInitParameterNames() { return new Vector<String>(initParameters.keySet()).elements(); } }; } static final boolean ENABLE_DISABLING_CHECK = TeamCityProperties.getBoolean("rest.enable.disabling.check"); protected ModelAndView doHandle(final HttpServletRequest request, final HttpServletResponse response) throws Exception { if (ENABLE_DISABLING_CHECK) { //necessary until TW-16750 is fixed if (TeamCityProperties.getBoolean("rest.disable")) { response.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "REST API is disabled on TeamCity server with 'rest.disable' internal property."); return null; } } final long requestStartProcessing = System.nanoTime(); if (LOG.isDebugEnabled()) { LOG.debug("REST API request received: " + WebUtil.getRequestDump(request)); } ensureInitialized(); boolean runAsSystem = false; if (TeamCityProperties.getBoolean("rest.use.authToken")) { String authToken = request.getParameter("authToken"); if (StringUtil.isNotEmpty(authToken) && StringUtil.isNotEmpty(getAuthToken())) { if (authToken.equals(getAuthToken())) { runAsSystem = true; } else { synchronized (this) { Thread.sleep(10000); //to prevent bruteforcing } response.sendError(403, "Wrong authToken specified"); return null; } } } final boolean runAsSystemActual = runAsSystem; // workaround for http://jetbrains.net/tracker/issue2/TW-7656 jetbrains.buildServer.util.Util.doUnderContextClassLoader(getClass().getClassLoader(), new FuncThrow<Void, Exception>() { public Void apply() throws Exception { // patching request final HttpServletRequest actualRequest = new RequestWrapper( patchRequest(request, "Accept", "overrideAccept"), myRequestPathTransformInfo); if (runAsSystemActual) { try { LOG.debug("Executing request with system security level"); mySecurityContext.runAsSystem(new SecurityContextEx.RunAsAction() { public void run() throws Throwable { myWebComponent.doFilter(actualRequest, response, null); } }); } catch (Throwable throwable) { LOG.debug(throwable.getMessage(), throwable); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, throwable.getMessage()); } } else { myWebComponent.doFilter(actualRequest, response, null); } return null; } }); if (LOG.isDebugEnabled()) { final long requestFinishProcessing = System.nanoTime(); LOG.debug("REST API request processing finished in " + (requestFinishProcessing - requestStartProcessing) / 1000000 + " ms"); } return null; } //todo: move to RequestWrapper private HttpServletRequest patchRequest(final HttpServletRequest request, final String headerName, final String parameterName) { final String newValue = request.getParameter(parameterName); if (!StringUtil.isEmpty(newValue)) { return modifyRequestHeader(request, headerName, newValue); } return request; } private HttpServletRequest modifyRequestHeader(final HttpServletRequest request, final String headerName, final String newValue) { return new HttpServletRequestWrapper(request) { @Override public String getHeader(final String name) { if (headerName.equalsIgnoreCase(name)) { return newValue; } return super.getHeader(name); } @Override public Enumeration getHeaders(final String name) { if (headerName.equalsIgnoreCase(name)) { return Collections.enumeration(Collections.singletonList(newValue)); } return super.getHeaders(name); } }; } private void ensureInitialized() throws ServletException { //todo: check synchronization synchronized (this) { // workaround for http://jetbrains.net/tracker/issue2/TW-7656 if (myWebComponent == null) { final ClassLoader cl = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(myClassloader); try { init(); } catch (RuntimeException e) { //otherwise exception here is swallowed and logged nowhere LOG.error("Error initializing REST API: ", e); myWebComponent = null; throw e; } catch (Error e) { LOG.error("Error initializing REST API: ", e); myWebComponent = null; throw e; } catch (ServletException e) { LOG.error("Error initializing REST API: ", e); myWebComponent = null; throw e; } finally { Thread.currentThread().setContextClassLoader(cl); } } } } private String getAuthToken() { return myAuthToken; } }