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 019 package org.apache.hadoop.fs.http.server; 020 021 import org.apache.hadoop.classification.InterfaceAudience; 022 import org.apache.hadoop.conf.Configuration; 023 import org.apache.hadoop.fs.FileSystem; 024 import org.apache.hadoop.fs.http.client.HttpFSFileSystem; 025 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.AccessTimeParam; 026 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.BlockSizeParam; 027 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.DataParam; 028 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.DestinationParam; 029 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.DoAsParam; 030 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.FilterParam; 031 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.GroupParam; 032 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.LenParam; 033 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.ModifiedTimeParam; 034 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OffsetParam; 035 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OperationParam; 036 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OverwriteParam; 037 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.OwnerParam; 038 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.PermissionParam; 039 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.RecursiveParam; 040 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.ReplicationParam; 041 import org.apache.hadoop.fs.http.server.HttpFSParametersProvider.SourcesParam; 042 import org.apache.hadoop.lib.service.FileSystemAccess; 043 import org.apache.hadoop.lib.service.FileSystemAccessException; 044 import org.apache.hadoop.lib.service.Groups; 045 import org.apache.hadoop.lib.service.Instrumentation; 046 import org.apache.hadoop.lib.service.ProxyUser; 047 import org.apache.hadoop.lib.servlet.FileSystemReleaseFilter; 048 import org.apache.hadoop.lib.servlet.HostnameFilter; 049 import org.apache.hadoop.lib.wsrs.InputStreamEntity; 050 import org.apache.hadoop.lib.wsrs.Parameters; 051 import org.apache.hadoop.security.authentication.server.AuthenticationToken; 052 import org.json.simple.JSONObject; 053 import org.slf4j.Logger; 054 import org.slf4j.LoggerFactory; 055 import org.slf4j.MDC; 056 057 import javax.ws.rs.Consumes; 058 import javax.ws.rs.DELETE; 059 import javax.ws.rs.GET; 060 import javax.ws.rs.POST; 061 import javax.ws.rs.PUT; 062 import javax.ws.rs.Path; 063 import javax.ws.rs.PathParam; 064 import javax.ws.rs.Produces; 065 import javax.ws.rs.QueryParam; 066 import javax.ws.rs.core.Context; 067 import javax.ws.rs.core.MediaType; 068 import javax.ws.rs.core.Response; 069 import javax.ws.rs.core.UriBuilder; 070 import javax.ws.rs.core.UriInfo; 071 import java.io.IOException; 072 import java.io.InputStream; 073 import java.net.URI; 074 import java.security.AccessControlException; 075 import java.security.Principal; 076 import java.text.MessageFormat; 077 import java.util.List; 078 import java.util.Map; 079 080 /** 081 * Main class of HttpFSServer server. 082 * <p/> 083 * The <code>HttpFSServer</code> class uses Jersey JAX-RS to binds HTTP requests to the 084 * different operations. 085 */ 086 @Path(HttpFSFileSystem.SERVICE_VERSION) 087 @InterfaceAudience.Private 088 public class HttpFSServer { 089 private static Logger AUDIT_LOG = LoggerFactory.getLogger("httpfsaudit"); 090 091 /** 092 * Resolves the effective user that will be used to request a FileSystemAccess filesystem. 093 * <p/> 094 * If the doAs-user is NULL or the same as the user, it returns the user. 095 * <p/> 096 * Otherwise it uses proxyuser rules (see {@link ProxyUser} to determine if the 097 * current user can impersonate the doAs-user. 098 * <p/> 099 * If the current user cannot impersonate the doAs-user an 100 * <code>AccessControlException</code> will be thrown. 101 * 102 * @param user principal for whom the filesystem instance is. 103 * @param doAs do-as user, if any. 104 * 105 * @return the effective user. 106 * 107 * @throws IOException thrown if an IO error occurrs. 108 * @throws AccessControlException thrown if the current user cannot impersonate 109 * the doAs-user. 110 */ 111 private String getEffectiveUser(Principal user, String doAs) throws IOException { 112 String effectiveUser = user.getName(); 113 if (doAs != null && !doAs.equals(user.getName())) { 114 ProxyUser proxyUser = HttpFSServerWebApp.get().get(ProxyUser.class); 115 String proxyUserName; 116 if (user instanceof AuthenticationToken) { 117 proxyUserName = ((AuthenticationToken)user).getUserName(); 118 } else { 119 proxyUserName = user.getName(); 120 } 121 proxyUser.validate(proxyUserName, HostnameFilter.get(), doAs); 122 effectiveUser = doAs; 123 AUDIT_LOG.info("Proxy user [{}] DoAs user [{}]", proxyUserName, doAs); 124 } 125 return effectiveUser; 126 } 127 128 /** 129 * Executes a {@link FileSystemAccess.FileSystemExecutor} using a filesystem for the effective 130 * user. 131 * 132 * @param user principal making the request. 133 * @param doAs do-as user, if any. 134 * @param executor FileSystemExecutor to execute. 135 * 136 * @return FileSystemExecutor response 137 * 138 * @throws IOException thrown if an IO error occurrs. 139 * @throws FileSystemAccessException thrown if a FileSystemAccess releated error occurred. Thrown 140 * exceptions are handled by {@link HttpFSExceptionProvider}. 141 */ 142 private <T> T fsExecute(Principal user, String doAs, FileSystemAccess.FileSystemExecutor<T> executor) 143 throws IOException, FileSystemAccessException { 144 String hadoopUser = getEffectiveUser(user, doAs); 145 FileSystemAccess fsAccess = HttpFSServerWebApp.get().get(FileSystemAccess.class); 146 Configuration conf = HttpFSServerWebApp.get().get(FileSystemAccess.class).getFileSystemConfiguration(); 147 return fsAccess.execute(hadoopUser, conf, executor); 148 } 149 150 /** 151 * Returns a filesystem instance. The fileystem instance is wired for release at the completion of 152 * the current Servlet request via the {@link FileSystemReleaseFilter}. 153 * <p/> 154 * If a do-as user is specified, the current user must be a valid proxyuser, otherwise an 155 * <code>AccessControlException</code> will be thrown. 156 * 157 * @param user principal for whom the filesystem instance is. 158 * @param doAs do-as user, if any. 159 * 160 * @return a filesystem for the specified user or do-as user. 161 * 162 * @throws IOException thrown if an IO error occurred. Thrown exceptions are 163 * handled by {@link HttpFSExceptionProvider}. 164 * @throws FileSystemAccessException thrown if a FileSystemAccess releated error occurred. Thrown 165 * exceptions are handled by {@link HttpFSExceptionProvider}. 166 */ 167 private FileSystem createFileSystem(Principal user, String doAs) throws IOException, FileSystemAccessException { 168 String hadoopUser = getEffectiveUser(user, doAs); 169 FileSystemAccess fsAccess = HttpFSServerWebApp.get().get(FileSystemAccess.class); 170 Configuration conf = HttpFSServerWebApp.get().get(FileSystemAccess.class).getFileSystemConfiguration(); 171 FileSystem fs = fsAccess.createFileSystem(hadoopUser, conf); 172 FileSystemReleaseFilter.setFileSystem(fs); 173 return fs; 174 } 175 176 private void enforceRootPath(HttpFSFileSystem.Operation op, String path) { 177 if (!path.equals("/")) { 178 throw new UnsupportedOperationException( 179 MessageFormat.format("Operation [{0}], invalid path [{1}], must be '/'", 180 op, path)); 181 } 182 } 183 184 /** 185 * Special binding for '/' as it is not handled by the wildcard binding. 186 * 187 * @param user the principal of the user making the request. 188 * @param op the HttpFS operation of the request. 189 * @param params the HttpFS parameters of the request. 190 * 191 * @return the request response. 192 * 193 * @throws IOException thrown if an IO error occurred. Thrown exceptions are 194 * handled by {@link HttpFSExceptionProvider}. 195 * @throws FileSystemAccessException thrown if a FileSystemAccess releated 196 * error occurred. Thrown exceptions are handled by 197 * {@link HttpFSExceptionProvider}. 198 */ 199 @GET 200 @Produces(MediaType.APPLICATION_JSON) 201 public Response getRoot(@Context Principal user, 202 @QueryParam(OperationParam.NAME) OperationParam op, 203 @Context Parameters params) 204 throws IOException, FileSystemAccessException { 205 return get(user, "", op, params); 206 } 207 208 private String makeAbsolute(String path) { 209 return "/" + ((path != null) ? path : ""); 210 } 211 212 /** 213 * Binding to handle GET requests, supported operations are 214 * 215 * @param user the principal of the user making the request. 216 * @param path the path for operation. 217 * @param op the HttpFS operation of the request. 218 * @param params the HttpFS parameters of the request. 219 * 220 * @return the request response. 221 * 222 * @throws IOException thrown if an IO error occurred. Thrown exceptions are 223 * handled by {@link HttpFSExceptionProvider}. 224 * @throws FileSystemAccessException thrown if a FileSystemAccess releated 225 * error occurred. Thrown exceptions are handled by 226 * {@link HttpFSExceptionProvider}. 227 */ 228 @GET 229 @Path("{path:.*}") 230 @Produces({MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON}) 231 public Response get(@Context Principal user, 232 @PathParam("path") String path, 233 @QueryParam(OperationParam.NAME) OperationParam op, 234 @Context Parameters params) 235 throws IOException, FileSystemAccessException { 236 Response response; 237 path = makeAbsolute(path); 238 MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name()); 239 String doAs = params.get(DoAsParam.NAME, DoAsParam.class); 240 switch (op.value()) { 241 case OPEN: { 242 //Invoking the command directly using an unmanaged FileSystem that is 243 // released by the FileSystemReleaseFilter 244 FSOperations.FSOpen command = new FSOperations.FSOpen(path); 245 FileSystem fs = createFileSystem(user, doAs); 246 InputStream is = command.execute(fs); 247 Long offset = params.get(OffsetParam.NAME, OffsetParam.class); 248 Long len = params.get(LenParam.NAME, LenParam.class); 249 AUDIT_LOG.info("[{}] offset [{}] len [{}]", 250 new Object[]{path, offset, len}); 251 InputStreamEntity entity = new InputStreamEntity(is, offset, len); 252 response = 253 Response.ok(entity).type(MediaType.APPLICATION_OCTET_STREAM).build(); 254 break; 255 } 256 case GETFILESTATUS: { 257 FSOperations.FSFileStatus command = 258 new FSOperations.FSFileStatus(path); 259 Map json = fsExecute(user, doAs, command); 260 AUDIT_LOG.info("[{}]", path); 261 response = Response.ok(json).type(MediaType.APPLICATION_JSON).build(); 262 break; 263 } 264 case LISTSTATUS: { 265 String filter = params.get(FilterParam.NAME, FilterParam.class); 266 FSOperations.FSListStatus command = new FSOperations.FSListStatus( 267 path, filter); 268 Map json = fsExecute(user, doAs, command); 269 AUDIT_LOG.info("[{}] filter [{}]", path, 270 (filter != null) ? filter : "-"); 271 response = Response.ok(json).type(MediaType.APPLICATION_JSON).build(); 272 break; 273 } 274 case GETHOMEDIRECTORY: { 275 enforceRootPath(op.value(), path); 276 FSOperations.FSHomeDir command = new FSOperations.FSHomeDir(); 277 JSONObject json = fsExecute(user, doAs, command); 278 AUDIT_LOG.info(""); 279 response = Response.ok(json).type(MediaType.APPLICATION_JSON).build(); 280 break; 281 } 282 case INSTRUMENTATION: { 283 enforceRootPath(op.value(), path); 284 Groups groups = HttpFSServerWebApp.get().get(Groups.class); 285 List<String> userGroups = groups.getGroups(user.getName()); 286 if (!userGroups.contains(HttpFSServerWebApp.get().getAdminGroup())) { 287 throw new AccessControlException( 288 "User not in HttpFSServer admin group"); 289 } 290 Instrumentation instrumentation = 291 HttpFSServerWebApp.get().get(Instrumentation.class); 292 Map snapshot = instrumentation.getSnapshot(); 293 response = Response.ok(snapshot).build(); 294 break; 295 } 296 case GETCONTENTSUMMARY: { 297 FSOperations.FSContentSummary command = 298 new FSOperations.FSContentSummary(path); 299 Map json = fsExecute(user, doAs, command); 300 AUDIT_LOG.info("[{}]", path); 301 response = Response.ok(json).type(MediaType.APPLICATION_JSON).build(); 302 break; 303 } 304 case GETFILECHECKSUM: { 305 FSOperations.FSFileChecksum command = 306 new FSOperations.FSFileChecksum(path); 307 Map json = fsExecute(user, doAs, command); 308 AUDIT_LOG.info("[{}]", path); 309 response = Response.ok(json).type(MediaType.APPLICATION_JSON).build(); 310 break; 311 } 312 case GETFILEBLOCKLOCATIONS: { 313 response = Response.status(Response.Status.BAD_REQUEST).build(); 314 break; 315 } 316 default: { 317 throw new IOException( 318 MessageFormat.format("Invalid HTTP GET operation [{0}]", 319 op.value())); 320 } 321 } 322 return response; 323 } 324 325 326 /** 327 * Binding to handle DELETE requests. 328 * 329 * @param user the principal of the user making the request. 330 * @param path the path for operation. 331 * @param op the HttpFS operation of the request. 332 * @param params the HttpFS parameters of the request. 333 * 334 * @return the request response. 335 * 336 * @throws IOException thrown if an IO error occurred. Thrown exceptions are 337 * handled by {@link HttpFSExceptionProvider}. 338 * @throws FileSystemAccessException thrown if a FileSystemAccess releated 339 * error occurred. Thrown exceptions are handled by 340 * {@link HttpFSExceptionProvider}. 341 */ 342 @DELETE 343 @Path("{path:.*}") 344 @Produces(MediaType.APPLICATION_JSON) 345 public Response delete(@Context Principal user, 346 @PathParam("path") String path, 347 @QueryParam(OperationParam.NAME) OperationParam op, 348 @Context Parameters params) 349 throws IOException, FileSystemAccessException { 350 Response response; 351 path = makeAbsolute(path); 352 MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name()); 353 String doAs = params.get(DoAsParam.NAME, DoAsParam.class); 354 switch (op.value()) { 355 case DELETE: { 356 Boolean recursive = 357 params.get(RecursiveParam.NAME, RecursiveParam.class); 358 AUDIT_LOG.info("[{}] recursive [{}]", path, recursive); 359 FSOperations.FSDelete command = 360 new FSOperations.FSDelete(path, recursive); 361 JSONObject json = fsExecute(user, doAs, command); 362 response = Response.ok(json).type(MediaType.APPLICATION_JSON).build(); 363 break; 364 } 365 default: { 366 throw new IOException( 367 MessageFormat.format("Invalid HTTP DELETE operation [{0}]", 368 op.value())); 369 } 370 } 371 return response; 372 } 373 374 /** 375 * Binding to handle POST requests. 376 * 377 * @param is the inputstream for the request payload. 378 * @param user the principal of the user making the request. 379 * @param uriInfo the of the request. 380 * @param path the path for operation. 381 * @param op the HttpFS operation of the request. 382 * @param params the HttpFS parameters of the request. 383 * 384 * @return the request response. 385 * 386 * @throws IOException thrown if an IO error occurred. Thrown exceptions are 387 * handled by {@link HttpFSExceptionProvider}. 388 * @throws FileSystemAccessException thrown if a FileSystemAccess releated 389 * error occurred. Thrown exceptions are handled by 390 * {@link HttpFSExceptionProvider}. 391 */ 392 @POST 393 @Path("{path:.*}") 394 @Consumes({"*/*"}) 395 @Produces({MediaType.APPLICATION_JSON}) 396 public Response post(InputStream is, 397 @Context Principal user, 398 @Context UriInfo uriInfo, 399 @PathParam("path") String path, 400 @QueryParam(OperationParam.NAME) OperationParam op, 401 @Context Parameters params) 402 throws IOException, FileSystemAccessException { 403 Response response; 404 path = makeAbsolute(path); 405 MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name()); 406 switch (op.value()) { 407 case APPEND: { 408 String doAs = params.get(DoAsParam.NAME, DoAsParam.class); 409 Boolean hasData = params.get(DataParam.NAME, DataParam.class); 410 if (!hasData) { 411 response = Response.temporaryRedirect( 412 createUploadRedirectionURL(uriInfo, 413 HttpFSFileSystem.Operation.APPEND)).build(); 414 } else { 415 FSOperations.FSAppend command = 416 new FSOperations.FSAppend(is, path); 417 fsExecute(user, doAs, command); 418 AUDIT_LOG.info("[{}]", path); 419 response = Response.ok().type(MediaType.APPLICATION_JSON).build(); 420 } 421 break; 422 } 423 case CONCAT: { 424 System.out.println("HTTPFS SERVER CONCAT"); 425 String sources = params.get(SourcesParam.NAME, SourcesParam.class); 426 427 FSOperations.FSConcat command = 428 new FSOperations.FSConcat(path, sources.split(",")); 429 fsExecute(user, null, command); 430 AUDIT_LOG.info("[{}]", path); 431 System.out.println("SENT RESPONSE"); 432 response = Response.ok().build(); 433 break; 434 } 435 default: { 436 throw new IOException( 437 MessageFormat.format("Invalid HTTP POST operation [{0}]", 438 op.value())); 439 } 440 } 441 return response; 442 } 443 444 /** 445 * Creates the URL for an upload operation (create or append). 446 * 447 * @param uriInfo uri info of the request. 448 * @param uploadOperation operation for the upload URL. 449 * 450 * @return the URI for uploading data. 451 */ 452 protected URI createUploadRedirectionURL(UriInfo uriInfo, Enum<?> uploadOperation) { 453 UriBuilder uriBuilder = uriInfo.getRequestUriBuilder(); 454 uriBuilder = uriBuilder.replaceQueryParam(OperationParam.NAME, uploadOperation). 455 queryParam(DataParam.NAME, Boolean.TRUE); 456 return uriBuilder.build(null); 457 } 458 459 460 /** 461 * Binding to handle PUT requests. 462 * 463 * @param is the inputstream for the request payload. 464 * @param user the principal of the user making the request. 465 * @param uriInfo the of the request. 466 * @param path the path for operation. 467 * @param op the HttpFS operation of the request. 468 * @param params the HttpFS parameters of the request. 469 * 470 * @return the request response. 471 * 472 * @throws IOException thrown if an IO error occurred. Thrown exceptions are 473 * handled by {@link HttpFSExceptionProvider}. 474 * @throws FileSystemAccessException thrown if a FileSystemAccess releated 475 * error occurred. Thrown exceptions are handled by 476 * {@link HttpFSExceptionProvider}. 477 */ 478 @PUT 479 @Path("{path:.*}") 480 @Consumes({"*/*"}) 481 @Produces({MediaType.APPLICATION_JSON}) 482 public Response put(InputStream is, 483 @Context Principal user, 484 @Context UriInfo uriInfo, 485 @PathParam("path") String path, 486 @QueryParam(OperationParam.NAME) OperationParam op, 487 @Context Parameters params) 488 throws IOException, FileSystemAccessException { 489 Response response; 490 path = makeAbsolute(path); 491 MDC.put(HttpFSFileSystem.OP_PARAM, op.value().name()); 492 String doAs = params.get(DoAsParam.NAME, DoAsParam.class); 493 switch (op.value()) { 494 case CREATE: { 495 Boolean hasData = params.get(DataParam.NAME, DataParam.class); 496 if (!hasData) { 497 response = Response.temporaryRedirect( 498 createUploadRedirectionURL(uriInfo, 499 HttpFSFileSystem.Operation.CREATE)).build(); 500 } else { 501 Short permission = params.get(PermissionParam.NAME, 502 PermissionParam.class); 503 Boolean override = params.get(OverwriteParam.NAME, 504 OverwriteParam.class); 505 Short replication = params.get(ReplicationParam.NAME, 506 ReplicationParam.class); 507 Long blockSize = params.get(BlockSizeParam.NAME, 508 BlockSizeParam.class); 509 FSOperations.FSCreate command = 510 new FSOperations.FSCreate(is, path, permission, override, 511 replication, blockSize); 512 fsExecute(user, doAs, command); 513 AUDIT_LOG.info( 514 "[{}] permission [{}] override [{}] replication [{}] blockSize [{}]", 515 new Object[]{path, permission, override, replication, blockSize}); 516 response = Response.status(Response.Status.CREATED).build(); 517 } 518 break; 519 } 520 case MKDIRS: { 521 Short permission = params.get(PermissionParam.NAME, 522 PermissionParam.class); 523 FSOperations.FSMkdirs command = 524 new FSOperations.FSMkdirs(path, permission); 525 JSONObject json = fsExecute(user, doAs, command); 526 AUDIT_LOG.info("[{}] permission [{}]", path, permission); 527 response = Response.ok(json).type(MediaType.APPLICATION_JSON).build(); 528 break; 529 } 530 case RENAME: { 531 String toPath = params.get(DestinationParam.NAME, DestinationParam.class); 532 FSOperations.FSRename command = 533 new FSOperations.FSRename(path, toPath); 534 JSONObject json = fsExecute(user, doAs, command); 535 AUDIT_LOG.info("[{}] to [{}]", path, toPath); 536 response = Response.ok(json).type(MediaType.APPLICATION_JSON).build(); 537 break; 538 } 539 case SETOWNER: { 540 String owner = params.get(OwnerParam.NAME, OwnerParam.class); 541 String group = params.get(GroupParam.NAME, GroupParam.class); 542 FSOperations.FSSetOwner command = 543 new FSOperations.FSSetOwner(path, owner, group); 544 fsExecute(user, doAs, command); 545 AUDIT_LOG.info("[{}] to (O/G)[{}]", path, owner + ":" + group); 546 response = Response.ok().build(); 547 break; 548 } 549 case SETPERMISSION: { 550 Short permission = params.get(PermissionParam.NAME, 551 PermissionParam.class); 552 FSOperations.FSSetPermission command = 553 new FSOperations.FSSetPermission(path, permission); 554 fsExecute(user, doAs, command); 555 AUDIT_LOG.info("[{}] to [{}]", path, permission); 556 response = Response.ok().build(); 557 break; 558 } 559 case SETREPLICATION: { 560 Short replication = params.get(ReplicationParam.NAME, 561 ReplicationParam.class); 562 FSOperations.FSSetReplication command = 563 new FSOperations.FSSetReplication(path, replication); 564 JSONObject json = fsExecute(user, doAs, command); 565 AUDIT_LOG.info("[{}] to [{}]", path, replication); 566 response = Response.ok(json).build(); 567 break; 568 } 569 case SETTIMES: { 570 Long modifiedTime = params.get(ModifiedTimeParam.NAME, 571 ModifiedTimeParam.class); 572 Long accessTime = params.get(AccessTimeParam.NAME, 573 AccessTimeParam.class); 574 FSOperations.FSSetTimes command = 575 new FSOperations.FSSetTimes(path, modifiedTime, accessTime); 576 fsExecute(user, doAs, command); 577 AUDIT_LOG.info("[{}] to (M/A)[{}]", path, 578 modifiedTime + ":" + accessTime); 579 response = Response.ok().build(); 580 break; 581 } 582 default: { 583 throw new IOException( 584 MessageFormat.format("Invalid HTTP PUT operation [{0}]", 585 op.value())); 586 } 587 } 588 return response; 589 } 590 591 }