001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.oozie.servlet; 020 021import org.apache.commons.lang.StringEscapeUtils; 022import org.apache.oozie.client.OozieClient.SYSTEM_MODE; 023import org.apache.oozie.client.rest.JsonBean; 024import org.apache.oozie.client.rest.RestConstants; 025import org.apache.oozie.service.DagXLogInfoService; 026import org.apache.oozie.service.InstrumentationService; 027import org.apache.oozie.service.ProxyUserService; 028import org.apache.oozie.service.Services; 029import org.apache.oozie.service.XLogService; 030import org.apache.oozie.util.Instrumentation; 031import org.apache.oozie.util.LogUtils; 032import org.apache.oozie.util.ParamChecker; 033import org.apache.oozie.util.XLog; 034import org.apache.oozie.ErrorCode; 035import org.json.simple.JSONObject; 036import org.json.simple.JSONStreamAware; 037 038import javax.servlet.ServletConfig; 039import javax.servlet.ServletException; 040import javax.servlet.http.HttpServlet; 041import javax.servlet.http.HttpServletRequest; 042import javax.servlet.http.HttpServletResponse; 043 044import java.io.IOException; 045import java.security.AccessControlException; 046import java.util.*; 047import java.util.concurrent.atomic.AtomicLong; 048 049/** 050 * Base class for Oozie web service API Servlets. <p> This class provides common instrumentation, error logging and 051 * other common functionality. 052 */ 053public abstract class JsonRestServlet extends HttpServlet { 054 055 static final String JSON_UTF8 = RestConstants.JSON_CONTENT_TYPE + "; charset=\"UTF-8\""; 056 057 protected static final String XML_UTF8 = RestConstants.XML_CONTENT_TYPE + "; charset=\"UTF-8\""; 058 059 protected static final String TEXT_UTF8 = RestConstants.TEXT_CONTENT_TYPE + "; charset=\"UTF-8\""; 060 061 protected static final String AUDIT_OPERATION = "audit.operation"; 062 protected static final String AUDIT_PARAM = "audit.param"; 063 protected static final String AUDIT_ERROR_CODE = "audit.error.code"; 064 protected static final String AUDIT_ERROR_MESSAGE = "audit.error.message"; 065 protected static final String AUDIT_HTTP_STATUS_CODE = "audit.http.status.code"; 066 067 private XLog auditLog; 068 XLog.Info logInfo; 069 private XLog LOG = XLog.getLog(getClass()); 070 071 072 /** 073 * This bean defines a query string parameter. 074 */ 075 public static class ParameterInfo { 076 private String name; 077 private Class type; 078 private List<String> methods; 079 private boolean required; 080 081 /** 082 * Creates a ParameterInfo with querystring parameter definition. 083 * 084 * @param name querystring parameter name. 085 * @param type type for the parameter value, valid types are: <code>Integer, Boolean and String</code> 086 * @param required indicates if the parameter is required. 087 * @param methods HTTP methods the parameter is used by. 088 */ 089 public ParameterInfo(String name, Class type, boolean required, List<String> methods) { 090 this.name = ParamChecker.notEmpty(name, "name"); 091 if (type != Integer.class && type != Boolean.class && type != String.class) { 092 throw new IllegalArgumentException("Type must be integer, boolean or string"); 093 } 094 this.type = ParamChecker.notNull(type, "type"); 095 this.required = required; 096 this.methods = ParamChecker.notNull(methods, "methods"); 097 } 098 099 } 100 101 /** 102 * This bean defines a REST resource. 103 */ 104 public static class ResourceInfo { 105 private String name; 106 private boolean wildcard; 107 private List<String> methods; 108 private Map<String, ParameterInfo> parameters = new HashMap<String, ParameterInfo>(); 109 110 /** 111 * Creates a ResourceInfo with a REST resource definition. 112 * 113 * @param name name of the REST resource, it can be an fixed resource name, empty or a wildcard ('*'). 114 * @param methods HTTP methods supported by the resource. 115 * @param parameters parameters supported by the resource. 116 */ 117 public ResourceInfo(String name, List<String> methods, List<ParameterInfo> parameters) { 118 this.name = name; 119 wildcard = name.equals("*"); 120 for (ParameterInfo parameter : parameters) { 121 this.parameters.put(parameter.name, parameter); 122 } 123 this.methods = ParamChecker.notNull(methods, "methods"); 124 } 125 } 126 127 /** 128 * Name of the instrumentation group for the WS layer, value is 'webservices'. 129 */ 130 protected static final String INSTRUMENTATION_GROUP = "webservices"; 131 132 private static final String INSTR_TOTAL_REQUESTS_SAMPLER = "requests"; 133 private static final String INSTR_TOTAL_REQUESTS_COUNTER = "requests"; 134 private static final String INSTR_TOTAL_FAILED_REQUESTS_COUNTER = "failed"; 135 private static AtomicLong TOTAL_REQUESTS_SAMPLER_COUNTER; 136 137 private Instrumentation instrumentation; 138 private String instrumentationName; 139 private AtomicLong samplerCounter = new AtomicLong(); 140 private ThreadLocal<Instrumentation.Cron> requestCron = new ThreadLocal<Instrumentation.Cron>(); 141 private List<ResourceInfo> resourcesInfo = new ArrayList<ResourceInfo>(); 142 private boolean allowSafeModeChanges; 143 144 /** 145 * Creates a servlet with a specified instrumentation sampler name for its requests. 146 * 147 * @param instrumentationName instrumentation name for timer and samplers for the servlet. 148 * @param resourcesInfo list of resource definitions supported by the servlet, empty and wildcard resources must be 149 * the last ones, in that order, first empty and the wildcard. 150 */ 151 public JsonRestServlet(String instrumentationName, ResourceInfo... resourcesInfo) { 152 this.instrumentationName = ParamChecker.notEmpty(instrumentationName, "instrumentationName"); 153 if (resourcesInfo.length == 0) { 154 throw new IllegalArgumentException("There must be at least one ResourceInfo"); 155 } 156 this.resourcesInfo = Arrays.asList(resourcesInfo); 157 auditLog = XLog.getLog("oozieaudit"); 158 auditLog.setMsgPrefix(""); 159 logInfo = new XLog.Info(XLog.Info.get()); 160 } 161 162 /** 163 * Enable HTTP POST/PUT/DELETE methods while in safe mode. 164 * 165 * @param allow <code>true</code> enabled safe mode changes, <code>false</code> disable safe mode changes 166 * (default). 167 */ 168 protected void setAllowSafeModeChanges(boolean allow) { 169 allowSafeModeChanges = allow; 170 } 171 172 /** 173 * Define an instrumentation sampler. <p> Sampling period is 60 seconds, the sampling frequency is 1 second. <p> 174 * The instrumentation group used is {@link #INSTRUMENTATION_GROUP}. 175 * 176 * @param samplerName sampler name. 177 * @param samplerCounter sampler counter. 178 */ 179 private void defineSampler(String samplerName, final AtomicLong samplerCounter) { 180 instrumentation.addSampler(INSTRUMENTATION_GROUP, samplerName, 60, 1, new Instrumentation.Variable<Long>() { 181 public Long getValue() { 182 return samplerCounter.get(); 183 } 184 }); 185 } 186 187 /** 188 * Add an instrumentation cron. 189 * 190 * @param name name of the timer for the cron. 191 * @param cron cron to add to a instrumentation timer. 192 */ 193 protected void addCron(String name, Instrumentation.Cron cron) { 194 instrumentation.addCron(INSTRUMENTATION_GROUP, name, cron); 195 } 196 197 /** 198 * Start the request instrumentation cron. 199 */ 200 protected void startCron() { 201 requestCron.get().start(); 202 } 203 204 /** 205 * Stop the request instrumentation cron. 206 */ 207 protected void stopCron() { 208 requestCron.get().stop(); 209 } 210 211 /** 212 * Initializes total request and servlet request samplers. 213 */ 214 public void init(ServletConfig servletConfig) throws ServletException { 215 super.init(servletConfig); 216 instrumentation = Services.get().get(InstrumentationService.class).get(); 217 synchronized (JsonRestServlet.class) { 218 if (TOTAL_REQUESTS_SAMPLER_COUNTER == null) { 219 TOTAL_REQUESTS_SAMPLER_COUNTER = new AtomicLong(); 220 defineSampler(INSTR_TOTAL_REQUESTS_SAMPLER, TOTAL_REQUESTS_SAMPLER_COUNTER); 221 } 222 } 223 defineSampler(instrumentationName, samplerCounter); 224 } 225 226 /** 227 * Convenience method for instrumentation counters. 228 * 229 * @param name counter name. 230 * @param count count to increment the counter. 231 */ 232 protected void incrCounter(String name, int count) { 233 if (instrumentation != null) { 234 instrumentation.incr(INSTRUMENTATION_GROUP, name, count); 235 } 236 } 237 238 /** 239 * Logs audit information for write requests to the audit log. 240 * 241 * @param request the http request. 242 */ 243 private void logAuditInfo(HttpServletRequest request) { 244 if (request.getAttribute(AUDIT_OPERATION) != null) { 245 Integer httpStatusCode = (Integer) request.getAttribute(AUDIT_HTTP_STATUS_CODE); 246 httpStatusCode = (httpStatusCode != null) ? httpStatusCode : HttpServletResponse.SC_OK; 247 String status = (httpStatusCode == HttpServletResponse.SC_OK) ? "SUCCESS" : "FAILED"; 248 String operation = (String) request.getAttribute(AUDIT_OPERATION); 249 String param = (String) request.getAttribute(AUDIT_PARAM); 250 String user = XLog.Info.get().getParameter(XLogService.USER); 251 String group = XLog.Info.get().getParameter(XLogService.GROUP); 252 String jobId = getJobId(request); 253 String app = XLog.Info.get().getParameter(DagXLogInfoService.APP); 254 255 String errorCode = (String) request.getAttribute(AUDIT_ERROR_CODE); 256 String errorMessage = (String) request.getAttribute(AUDIT_ERROR_MESSAGE); 257 String hostDetail = request.getRemoteAddr(); 258 259 auditLog.info("IP [{0}], USER [{1}], GROUP [{2}], APP [{3}], " + DagXLogInfoService.AUDIT_JOBID 260 + " [{4}], OPERATION [{5}], PARAMETER [{6}], STATUS [{7}]," 261 + " HTTPCODE [{8}], ERRORCODE [{9}], ERRORMESSAGE [{10}]", hostDetail, user, group, app, jobId, 262 operation, param, status, httpStatusCode, errorCode, errorMessage); 263 } 264 } 265 266 private String getJobId(HttpServletRequest request) { 267 String jobId = XLog.Info.get().getParameter(DagXLogInfoService.JOB); 268 if (jobId == null) { 269 LOG.debug("JobId is not present in XLog.Info, getting it from HttpServletRequest" ); 270 jobId = getResourceName(request); 271 if (!(jobId.endsWith("-C") || jobId.endsWith("-B") || jobId.endsWith("-W") || jobId.contains("C@"))) { 272 jobId = null; 273 } 274 } 275 return jobId; 276 } 277 278 /** 279 * Dispatches to super after loginfo and intrumentation handling. In case of errors dispatches error response codes 280 * and does error logging. 281 */ 282 @SuppressWarnings("unchecked") 283 protected final void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, 284 IOException { 285 //if (Services.get().isSafeMode() && !request.getMethod().equals("GET") && !allowSafeModeChanges) { 286 if (Services.get().getSystemMode() != SYSTEM_MODE.NORMAL && !request.getMethod().equals("GET") && !allowSafeModeChanges) { 287 sendErrorResponse(response, HttpServletResponse.SC_SERVICE_UNAVAILABLE, ErrorCode.E0002.toString(), 288 ErrorCode.E0002.getTemplate()); 289 return; 290 } 291 Instrumentation.Cron cron = new Instrumentation.Cron(); 292 requestCron.set(cron); 293 try { 294 cron.start(); 295 validateRestUrl(request.getMethod(), getResourceName(request), request.getParameterMap()); 296 XLog.Info.get().clear(); 297 String user = getUser(request); 298 TOTAL_REQUESTS_SAMPLER_COUNTER.incrementAndGet(); 299 samplerCounter.incrementAndGet(); 300 //If trace is enabled then display the request headers 301 XLog log = XLog.getLog(getClass()); 302 if (log.isTraceEnabled()){ 303 logHeaderInfo(request); 304 } 305 super.service(request, response); 306 } 307 catch (XServletException ex) { 308 XLog log = XLog.getLog(getClass()); 309 log.warn("URL[{0} {1}] error[{2}], {3}", request.getMethod(), getRequestUrl(request), ex.getErrorCode(), ex 310 .getMessage(), ex); 311 request.setAttribute(AUDIT_ERROR_MESSAGE, ex.getMessage()); 312 request.setAttribute(AUDIT_ERROR_CODE, ex.getErrorCode().toString()); 313 request.setAttribute(AUDIT_HTTP_STATUS_CODE, ex.getHttpStatusCode()); 314 incrCounter(INSTR_TOTAL_FAILED_REQUESTS_COUNTER, 1); 315 sendErrorResponse(response, ex.getHttpStatusCode(), ex.getErrorCode().toString(), ex.getMessage()); 316 } 317 catch (AccessControlException ex) { 318 XLog log = XLog.getLog(getClass()); 319 log.error("URL[{0} {1}] error, {2}", request.getMethod(), getRequestUrl(request), ex.getMessage(), ex); 320 incrCounter(INSTR_TOTAL_FAILED_REQUESTS_COUNTER, 1); 321 sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, ErrorCode.E1400.toString(), 322 ex.getMessage()); 323 } 324 catch (IllegalArgumentException ex){ 325 XLog log = XLog.getLog(getClass()); 326 log.warn("URL[{0} {1}] user error, {2}", request.getMethod(), getRequestUrl(request), ex.getMessage(), ex); 327 incrCounter(INSTR_TOTAL_FAILED_REQUESTS_COUNTER, 1); 328 sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E1603.toString(), 329 ex.getMessage()); 330 } 331 catch (RuntimeException ex) { 332 XLog log = XLog.getLog(getClass()); 333 log.error("URL[{0} {1}] error, {2}", request.getMethod(), getRequestUrl(request), ex.getMessage(), ex); 334 incrCounter(INSTR_TOTAL_FAILED_REQUESTS_COUNTER, 1); 335 throw ex; 336 } 337 finally { 338 logAuditInfo(request); 339 TOTAL_REQUESTS_SAMPLER_COUNTER.decrementAndGet(); 340 incrCounter(INSTR_TOTAL_REQUESTS_COUNTER, 1); 341 samplerCounter.decrementAndGet(); 342 XLog.Info.remove(); 343 cron.stop(); 344 // TODO 345 incrCounter(instrumentationName, 1); 346 incrCounter(instrumentationName + "-" + request.getMethod(), 1); 347 addCron(instrumentationName, cron); 348 addCron(instrumentationName + "-" + request.getMethod(), cron); 349 requestCron.remove(); 350 } 351 } 352 353 private void logHeaderInfo(HttpServletRequest request){ 354 XLog log = XLog.getLog(getClass()); 355 StringBuilder traceInfo = new StringBuilder(4096); 356 //Display request URL and request.getHeaderNames(); 357 Enumeration<String> names = (Enumeration<String>) request.getHeaderNames(); 358 traceInfo.append("Request URL: ").append(getRequestUrl(request)).append("\nRequest Headers:\n"); 359 while (names.hasMoreElements()) { 360 String name = names.nextElement(); 361 String value = request.getHeader(name); 362 traceInfo.append(name).append(" : ").append(value).append("\n"); 363 } 364 log.trace(traceInfo); 365 } 366 367 private String getRequestUrl(HttpServletRequest request) { 368 StringBuffer url = request.getRequestURL(); 369 if (request.getQueryString() != null) { 370 url.append("?").append(request.getQueryString()); 371 } 372 return url.toString(); 373 } 374 375 /** 376 * Sends a JSON response. 377 * 378 * @param response servlet response. 379 * @param statusCode HTTP status code. 380 * @param bean bean to send as JSON response. 381 * @param timeZoneId time zone to use for dates in the JSON response. 382 * @throws java.io.IOException thrown if the bean could not be serialized to the response output stream. 383 */ 384 protected void sendJsonResponse(HttpServletResponse response, int statusCode, JsonBean bean, String timeZoneId) 385 throws IOException { 386 response.setStatus(statusCode); 387 JSONObject json = bean.toJSONObject(timeZoneId); 388 response.setContentType(JSON_UTF8); 389 json.writeJSONString(response.getWriter()); 390 } 391 392 /** 393 * Sends a error response. 394 * 395 * @param response servlet response. 396 * @param statusCode HTTP status code. 397 * @param error error code. 398 * @param message error message. 399 * @throws java.io.IOException thrown if the error response could not be set. 400 */ 401 protected void sendErrorResponse(HttpServletResponse response, int statusCode, String error, String message) 402 throws IOException { 403 response.setHeader(RestConstants.OOZIE_ERROR_CODE, error); 404 response.setHeader(RestConstants.OOZIE_ERROR_MESSAGE, message); 405 response.sendError(statusCode, StringEscapeUtils.escapeHtml(message)); 406 } 407 408 protected void sendJsonResponse(HttpServletResponse response, int statusCode, JSONStreamAware json) 409 throws IOException { 410 if (statusCode == HttpServletResponse.SC_OK || statusCode == HttpServletResponse.SC_CREATED) { 411 response.setStatus(statusCode); 412 } 413 else { 414 response.sendError(statusCode); 415 } 416 response.setStatus(statusCode); 417 response.setContentType(JSON_UTF8); 418 json.writeJSONString(response.getWriter()); 419 } 420 421 /** 422 * Validates REST URL using the ResourceInfos of the servlet. 423 * 424 * @param method HTTP method. 425 * @param resourceName resource name. 426 * @param queryStringParams query string parameters. 427 * @throws javax.servlet.ServletException thrown if the resource name or parameters are incorrect. 428 */ 429 @SuppressWarnings("unchecked") 430 protected void validateRestUrl(String method, String resourceName, Map<String, String[]> queryStringParams) 431 throws ServletException { 432 433 if (resourceName.contains("/")) { 434 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0301, resourceName); 435 } 436 437 boolean valid = false; 438 for (int i = 0; !valid && i < resourcesInfo.size(); i++) { 439 ResourceInfo resourceInfo = resourcesInfo.get(i); 440 if (resourceInfo.name.equals(resourceName) || resourceInfo.wildcard) { 441 if (!resourceInfo.methods.contains(method)) { 442 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0301, resourceName); 443 } 444 for (Map.Entry<String, String[]> entry : queryStringParams.entrySet()) { 445 String name = entry.getKey(); 446 ParameterInfo parameterInfo = resourceInfo.parameters.get(name); 447 if (parameterInfo != null) { 448 if (!parameterInfo.methods.contains(method)) { 449 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0302, name); 450 } 451 String value = entry.getValue()[0].trim(); 452 if (parameterInfo.type.equals(Boolean.class)) { 453 value = value.toLowerCase(); 454 if (!value.equals("true") && !value.equals("false")) { 455 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0304, name, 456 "boolean"); 457 } 458 } 459 if (parameterInfo.type.equals(Integer.class)) { 460 try { 461 Integer.parseInt(value); 462 } 463 catch (NumberFormatException ex) { 464 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0304, name, 465 "integer"); 466 } 467 } 468 } 469 } 470 for (ParameterInfo parameterInfo : resourceInfo.parameters.values()) { 471 if (parameterInfo.methods.contains(method) && parameterInfo.required 472 && queryStringParams.get(parameterInfo.name) == null) { 473 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0305, 474 parameterInfo.name); 475 } 476 } 477 valid = true; 478 } 479 } 480 if (!valid) { 481 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0301, resourceName); 482 } 483 } 484 485 /** 486 * Return the resource name of the request. <p> The resource name is the whole extra path. If the extra path starts 487 * with '/', the first '/' is trimmed. 488 * 489 * @param request request instance 490 * @return the resource name, <code>null</code> if none. 491 */ 492 protected String getResourceName(HttpServletRequest request) { 493 String requestPath = request.getPathInfo(); 494 if (requestPath != null) { 495 while (requestPath.startsWith("/")) { 496 requestPath = requestPath.substring(1); 497 } 498 requestPath = requestPath.trim(); 499 } 500 else { 501 requestPath = ""; 502 } 503 return requestPath; 504 } 505 506 /** 507 * Return the request content type, lowercase and without attributes. 508 * 509 * @param request servlet request. 510 * @return the request content type, <code>null</code> if none. 511 */ 512 protected String getContentType(HttpServletRequest request) { 513 String contentType = request.getContentType(); 514 if (contentType != null) { 515 int index = contentType.indexOf(";"); 516 if (index > -1) { 517 contentType = contentType.substring(0, index); 518 } 519 contentType = contentType.toLowerCase(); 520 } 521 return contentType; 522 } 523 524 /** 525 * Validate and return the content type of the request. 526 * 527 * @param request servlet request. 528 * @param expected expected contentType. 529 * @return the normalized content type (lowercase and without modifiers). 530 * @throws XServletException thrown if the content type is invalid. 531 */ 532 protected String validateContentType(HttpServletRequest request, String expected) throws XServletException { 533 String contentType = getContentType(request); 534 if (contentType == null || contentType.trim().length() == 0) { 535 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0300, contentType); 536 } 537 if (!contentType.equals(expected)) { 538 throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0300, contentType); 539 } 540 return contentType; 541 } 542 543 /** 544 * Request attribute constant for the authenticatio token. 545 */ 546 public static final String AUTH_TOKEN = "oozie.auth.token"; 547 548 /** 549 * Request attribute constant for the user name. 550 */ 551 public static final String USER_NAME = "oozie.user.name"; 552 553 protected static final String UNDEF = "?"; 554 555 /** 556 * Return the user name of the request if any. 557 * 558 * @param request request. 559 * @return the user name, <code>null</code> if there is none. 560 */ 561 protected String getUser(HttpServletRequest request) { 562 String userName = (String) request.getAttribute(USER_NAME); 563 564 String doAsUserName = request.getParameter(RestConstants.DO_AS_PARAM); 565 if (doAsUserName != null && !doAsUserName.equals(userName)) { 566 ProxyUserService proxyUser = Services.get().get(ProxyUserService.class); 567 try { 568 proxyUser.validate(userName, HostnameFilter.get(), doAsUserName); 569 } 570 catch (IOException ex) { 571 throw new RuntimeException(ex); 572 } 573 auditLog.info("Proxy user [{0}] DoAs user [{1}] Request [{2}]", userName, doAsUserName, 574 getRequestUrl(request)); 575 576 XLog.Info.get().setParameter(XLogService.USER, userName + " doAs " + doAsUserName); 577 578 userName = doAsUserName; 579 } 580 else { 581 XLog.Info.get().setParameter(XLogService.USER, userName); 582 } 583 return (userName != null) ? userName : UNDEF; 584 } 585 586 /** 587 * Gets proxy user. 588 * If there is any proxy user, then <code>HttpServletRequest</code> contains proxy user. 589 * Otherwise it is the normal user. 590 * @param request the request 591 * @return the username 592 */ 593 protected String getProxyUser(HttpServletRequest request) { 594 return (String) request.getAttribute(USER_NAME); 595 } 596 597 /** 598 * Set the thread local log info with the given information. 599 * 600 * @param actionid action ID. 601 */ 602 protected void setLogInfo(String actionid) { 603 LogUtils.setLogInfo(actionid); 604 } 605}