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 */ 018package org.apache.oozie.service; 019 020import java.io.File; 021import java.io.IOException; 022import java.net.URI; 023import java.net.URL; 024import java.net.URLDecoder; 025import java.text.ParseException; 026import java.text.SimpleDateFormat; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.Calendar; 030import java.util.Comparator; 031import java.util.Date; 032import java.util.Enumeration; 033import java.util.HashMap; 034import java.util.HashSet; 035import java.util.List; 036import java.util.Map; 037import java.util.Properties; 038import java.util.Set; 039import java.util.TimeZone; 040 041import org.apache.commons.lang.StringUtils; 042import org.apache.hadoop.fs.FileStatus; 043import org.apache.hadoop.fs.FileSystem; 044import org.apache.hadoop.fs.Path; 045import org.apache.hadoop.fs.PathFilter; 046import org.apache.hadoop.fs.permission.FsPermission; 047import org.apache.oozie.action.ActionExecutor; 048import org.apache.oozie.action.hadoop.JavaActionExecutor; 049import org.apache.oozie.client.rest.JsonUtils; 050import org.apache.oozie.util.Instrumentable; 051import org.apache.oozie.util.Instrumentation; 052import org.apache.oozie.util.XLog; 053 054import com.google.common.annotations.VisibleForTesting; 055 056import org.apache.oozie.ErrorCode; 057 058public class ShareLibService implements Service, Instrumentable { 059 060 public static final String LAUNCHERJAR_LIB_RETENTION = CONF_PREFIX + "ShareLibService.temp.sharelib.retention.days"; 061 062 public static final String SHARELIB_MAPPING_FILE = CONF_PREFIX + "ShareLibService.mapping.file"; 063 064 public static final String SHIP_LAUNCHER_JAR = "oozie.action.ship.launcher.jar"; 065 066 public static final String PURGE_INTERVAL = CONF_PREFIX + "ShareLibService.purge.interval"; 067 068 public static final String FAIL_FAST_ON_STARTUP = CONF_PREFIX + "ShareLibService.fail.fast.on.startup"; 069 070 private static final String PERMISSION_STRING = "-rwxr-xr-x"; 071 072 public static final String LAUNCHER_PREFIX = "launcher_"; 073 074 public static final String SHARED_LIB_PREFIX = "lib_"; 075 076 public static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); 077 078 private Services services; 079 080 private Map<String, List<Path>> shareLibMap = new HashMap<String, List<Path>>(); 081 082 private Map<String, List<Path>> launcherLibMap = new HashMap<String, List<Path>>(); 083 084 private static XLog LOG = XLog.getLog(ShareLibService.class); 085 086 private String sharelibMappingFile; 087 088 private boolean isShipLauncherEnabled = false; 089 090 public static String SHARE_LIB_CONF_PREFIX = "oozie"; 091 092 private boolean shareLibLoadAttempted = false; 093 094 private String sharelibMetaFileOldTimeStamp; 095 096 private String sharelibDirOld; 097 098 FileSystem fs; 099 100 final long retentionTime = 1000 * 60 * 60 * 24 * Services.get().getConf().getInt(LAUNCHERJAR_LIB_RETENTION, 7); 101 102 @Override 103 public void init(Services services) throws ServiceException { 104 this.services = services; 105 sharelibMappingFile = services.getConf().get(SHARELIB_MAPPING_FILE, ""); 106 isShipLauncherEnabled = services.getConf().getBoolean(SHIP_LAUNCHER_JAR, false); 107 boolean failOnfailure = services.getConf().getBoolean(FAIL_FAST_ON_STARTUP, false); 108 Path launcherlibPath = getLauncherlibPath(); 109 HadoopAccessorService has = Services.get().get(HadoopAccessorService.class); 110 URI uri = launcherlibPath.toUri(); 111 try { 112 fs = FileSystem.get(has.createJobConf(uri.getAuthority())); 113 updateLauncherLib(); 114 updateShareLib(); 115 } catch(Throwable e) { 116 if (failOnfailure) { 117 LOG.error("Sharelib initialization fails", e); 118 throw new ServiceException(ErrorCode.E0104, getClass().getName(), "Sharelib initialization fails. ", e); 119 } 120 else { 121 // We don't want to actually fail init by throwing an Exception, so only create the ServiceException and 122 // log it 123 ServiceException se = new ServiceException(ErrorCode.E0104, getClass().getName(), 124 "Not able to cache sharelib. An Admin needs to install the sharelib with oozie-setup.sh and issue the " 125 + "'oozie admin' CLI command to update the sharelib", e); 126 LOG.error(se); 127 } 128 } 129 Runnable purgeLibsRunnable = new Runnable() { 130 @Override 131 public void run() { 132 System.out.flush(); 133 try { 134 //Only one server should purge sharelib 135 if (Services.get().get(JobsConcurrencyService.class).isLeader()) { 136 final Date current = Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime(); 137 purgeLibs(fs, LAUNCHER_PREFIX, current); 138 purgeLibs(fs, SHARED_LIB_PREFIX, current); 139 } 140 } 141 catch (IOException e) { 142 LOG.error("There was an issue purging the sharelib", e); 143 } 144 } 145 }; 146 services.get(SchedulerService.class).schedule(purgeLibsRunnable, 10, 147 services.getConf().getInt(PURGE_INTERVAL, 1) * 60 * 60 * 24, SchedulerService.Unit.SEC); 148 } 149 150 /** 151 * Recursively change permissions. 152 * 153 * @throws IOException Signals that an I/O exception has occurred. 154 * @throws ClassNotFoundException the class not found exception 155 */ 156 157 private void updateLauncherLib() throws IOException { 158 if (isShipLauncherEnabled) { 159 if (fs == null) { 160 Path launcherlibPath = getLauncherlibPath(); 161 HadoopAccessorService has = Services.get().get(HadoopAccessorService.class); 162 URI uri = launcherlibPath.toUri(); 163 fs = FileSystem.get(has.createJobConf(uri.getAuthority())); 164 } 165 Path launcherlibPath = getLauncherlibPath(); 166 setupLauncherLibPath(fs, launcherlibPath); 167 recursiveChangePermissions(fs, launcherlibPath, FsPermission.valueOf(PERMISSION_STRING)); 168 } 169 170 } 171 172 /** 173 * Copy launcher jars to Temp directory 174 * 175 * @param fs the FileSystem 176 * @param tmpShareLibPath destination path 177 * @throws IOException Signals that an I/O exception has occurred. 178 * @throws ClassNotFoundException the class not found exception 179 */ 180 private void setupLauncherLibPath(FileSystem fs, Path tmpLauncherLibPath) throws IOException { 181 182 ActionService actionService = Services.get().get(ActionService.class); 183 List<Class> classes = JavaActionExecutor.getCommonLauncherClasses(); 184 Path baseDir = new Path(tmpLauncherLibPath, JavaActionExecutor.OOZIE_COMMON_LIBDIR); 185 copyJarContainingClasses(classes, fs, baseDir, JavaActionExecutor.OOZIE_COMMON_LIBDIR); 186 Set<String> actionTypes = actionService.getActionTypes(); 187 for (String key : actionTypes) { 188 ActionExecutor executor = actionService.getExecutor(key); 189 if (executor instanceof JavaActionExecutor) { 190 JavaActionExecutor jexecutor = (JavaActionExecutor) executor; 191 classes = jexecutor.getLauncherClasses(); 192 if (classes != null) { 193 String type = executor.getType(); 194 Path executorDir = new Path(tmpLauncherLibPath, type); 195 copyJarContainingClasses(classes, fs, executorDir, type); 196 } 197 } 198 } 199 } 200 201 /** 202 * Recursive change permissions. 203 * 204 * @param fs the FileSystem 205 * @param path the Path 206 * @param perm is permission 207 * @throws IOException Signals that an I/O exception has occurred. 208 */ 209 private void recursiveChangePermissions(FileSystem fs, Path path, FsPermission fsPerm) throws IOException { 210 fs.setPermission(path, fsPerm); 211 FileStatus[] filesStatus = fs.listStatus(path); 212 for (int i = 0; i < filesStatus.length; i++) { 213 Path p = filesStatus[i].getPath(); 214 if (filesStatus[i].isDir()) { 215 recursiveChangePermissions(fs, p, fsPerm); 216 } 217 else { 218 fs.setPermission(p, fsPerm); 219 } 220 } 221 } 222 223 /** 224 * Copy jar containing classes. 225 * 226 * @param classes the classes 227 * @param fs the FileSystem 228 * @param executorDir is Path 229 * @param type is actionKey 230 * @throws IOException Signals that an I/O exception has occurred. 231 */ 232 private void copyJarContainingClasses(List<Class> classes, FileSystem fs, Path executorDir, String type) 233 throws IOException { 234 fs.mkdirs(executorDir); 235 Set<String> localJarSet = new HashSet<String>(); 236 for (Class c : classes) { 237 String localJar = findContainingJar(c); 238 if (localJar != null) { 239 localJarSet.add(localJar); 240 } 241 else { 242 throw new IOException("No jar containing " + c + " found"); 243 } 244 } 245 List<Path> listOfPaths = new ArrayList<Path>(); 246 for (String localJarStr : localJarSet) { 247 File localJar = new File(localJarStr); 248 fs.copyFromLocalFile(new Path(localJar.getPath()), executorDir); 249 Path path = new Path(executorDir, localJar.getName()); 250 listOfPaths.add(path); 251 LOG.info(localJar.getName() + " uploaded to " + executorDir.toString()); 252 } 253 launcherLibMap.put(type, listOfPaths); 254 255 } 256 257 /** 258 * Gets the path recursively. 259 * 260 * @param fs the FileSystem 261 * @param rootDir the root directory 262 * @param listOfPaths the list of paths 263 * @return the path recursively 264 * @throws IOException Signals that an I/O exception has occurred. 265 */ 266 private void getPathRecursively(FileSystem fs, Path rootDir, List<Path> listOfPaths) throws IOException { 267 if (rootDir == null) { 268 return; 269 } 270 271 FileStatus[] status = fs.listStatus(rootDir); 272 if (status == null) { 273 LOG.info("Shared lib " + rootDir + " doesn't exist, not adding to cache"); 274 return; 275 } 276 277 for (FileStatus file : status) { 278 if (file.isDir()) { 279 getPathRecursively(fs, file.getPath(), listOfPaths); 280 } 281 else { 282 listOfPaths.add(file.getPath()); 283 } 284 } 285 } 286 287 public Map<String, List<Path>> getShareLib() { 288 return shareLibMap; 289 } 290 291 /** 292 * Gets the action system lib common jars. 293 * 294 * @param actionKey the action key 295 * @return List of paths 296 * @throws IOException 297 */ 298 public List<Path> getShareLibJars(String actionKey) throws IOException { 299 // Sharelib map is empty means that on previous or startup attempt of 300 // caching sharelib has failed.Trying to reload 301 if (shareLibMap.isEmpty() && !shareLibLoadAttempted) { 302 synchronized (ShareLibService.class) { 303 if (shareLibMap.isEmpty()) { 304 updateShareLib(); 305 shareLibLoadAttempted = true; 306 } 307 } 308 } 309 return shareLibMap.get(actionKey); 310 } 311 312 /** 313 * Gets the launcher jars. 314 * 315 * @param actionKey the action key 316 * @return launcher jars paths 317 * @throws ClassNotFoundException 318 * @throws IOException 319 */ 320 public List<Path> getSystemLibJars(String actionKey) throws IOException { 321 List<Path> returnList = new ArrayList<Path>(); 322 // Sharelib map is empty means that on previous or startup attempt of 323 // caching launcher jars has failed.Trying to reload 324 if (isShipLauncherEnabled) { 325 if (launcherLibMap.isEmpty()) { 326 synchronized (ShareLibService.class) { 327 if (launcherLibMap.isEmpty()) { 328 updateLauncherLib(); 329 } 330 } 331 } 332 if (launcherLibMap.get(actionKey) != null) { 333 returnList.addAll(launcherLibMap.get(actionKey)); 334 } 335 } 336 if (actionKey.equals(JavaActionExecutor.OOZIE_COMMON_LIBDIR)) { 337 List<Path> sharelibList = getShareLibJars(actionKey); 338 if (sharelibList != null) { 339 returnList.addAll(sharelibList); 340 } 341 } 342 return returnList; 343 } 344 345 /** 346 * Find containing jar containing. 347 * 348 * @param clazz the clazz 349 * @return the string 350 */ 351 @VisibleForTesting 352 protected String findContainingJar(Class clazz) { 353 ClassLoader loader = clazz.getClassLoader(); 354 String classFile = clazz.getName().replaceAll("\\.", "/") + ".class"; 355 try { 356 for (Enumeration itr = loader.getResources(classFile); itr.hasMoreElements();) { 357 URL url = (URL) itr.nextElement(); 358 if ("jar".equals(url.getProtocol())) { 359 String toReturn = url.getPath(); 360 if (toReturn.startsWith("file:")) { 361 toReturn = toReturn.substring("file:".length()); 362 // URLDecoder is a misnamed class, since it actually 363 // decodes 364 // x-www-form-urlencoded MIME type rather than actual 365 // URL encoding (which the file path has). Therefore it 366 // would 367 // decode +s to ' 's which is incorrect (spaces are 368 // actually 369 // either unencoded or encoded as "%20"). Replace +s 370 // first, so 371 // that they are kept sacred during the decoding 372 // process. 373 toReturn = toReturn.replaceAll("\\+", "%2B"); 374 toReturn = URLDecoder.decode(toReturn, "UTF-8"); 375 toReturn = toReturn.replaceAll("!.*$", ""); 376 return toReturn; 377 } 378 } 379 } 380 } 381 catch (IOException ioe) { 382 throw new RuntimeException(ioe); 383 } 384 return null; 385 } 386 387 /** 388 * Purge libs. 389 * 390 * @param fs the fs 391 * @param prefix the prefix 392 * @throws IOException Signals that an I/O exception has occurred. 393 */ 394 private void purgeLibs(FileSystem fs, final String prefix, final Date current) throws IOException { 395 Path executorLibBasePath = services.get(WorkflowAppService.class).getSystemLibPath(); 396 PathFilter directoryFilter = new PathFilter() { 397 @Override 398 public boolean accept(Path path) { 399 if (path.getName().startsWith(prefix)) { 400 String name = path.getName(); 401 String time = name.substring(prefix.length()); 402 Date d = null; 403 try { 404 d = dateFormat.parse(time); 405 } 406 catch (ParseException e) { 407 return false; 408 } 409 return (current.getTime() - d.getTime()) > retentionTime; 410 } 411 else { 412 return false; 413 } 414 } 415 }; 416 FileStatus[] dirList = fs.listStatus(executorLibBasePath, directoryFilter); 417 Arrays.sort(dirList, new Comparator<FileStatus>() { 418 // sort in desc order 419 @Override 420 public int compare(FileStatus o1, FileStatus o2) { 421 return o2.getPath().getName().compareTo(o1.getPath().getName()); 422 } 423 }); 424 425 //Logic is to keep all share-lib between current timestamp and 7days old + 1 latest sharelib older than 7 days. 426 // refer OOZIE-1761 427 for (int i = 1; i < dirList.length; i++) { 428 Path dirPath = dirList[i].getPath(); 429 fs.delete(dirPath, true); 430 LOG.info("Deleted old launcher jar lib directory {0}", dirPath.getName()); 431 } 432 } 433 434 @Override 435 public void destroy() { 436 shareLibMap.clear(); 437 launcherLibMap.clear(); 438 439 } 440 441 @Override 442 public Class<? extends Service> getInterface() { 443 return ShareLibService.class; 444 } 445 446 /** 447 * Update share lib cache. 448 * 449 * @return the map 450 * @throws IOException Signals that an I/O exception has occurred. 451 */ 452 public Map<String, String> updateShareLib() throws IOException { 453 Map<String, String> status = new HashMap<String, String>(); 454 455 if (fs == null) { 456 Path launcherlibPath = getLauncherlibPath(); 457 HadoopAccessorService has = Services.get().get(HadoopAccessorService.class); 458 URI uri = launcherlibPath.toUri(); 459 fs = FileSystem.get(has.createJobConf(uri.getAuthority())); 460 } 461 462 Map<String, List<Path>> tempShareLibMap = new HashMap<String, List<Path>>(); 463 464 if (!StringUtils.isEmpty(sharelibMappingFile)) { 465 String sharelibMetaFileNewTimeStamp = JsonUtils.formatDateRfc822(new Date(fs.getFileStatus( 466 new Path(sharelibMappingFile)).getModificationTime()),"GMT"); 467 loadShareLibMetaFile(tempShareLibMap, sharelibMappingFile); 468 status.put("sharelibMetaFile", sharelibMappingFile); 469 status.put("sharelibMetaFileNewTimeStamp", sharelibMetaFileNewTimeStamp); 470 status.put("sharelibMetaFileOldTimeStamp", sharelibMetaFileOldTimeStamp); 471 sharelibMetaFileOldTimeStamp = sharelibMetaFileNewTimeStamp; 472 } 473 else { 474 Path shareLibpath = getLatestLibPath(services.get(WorkflowAppService.class).getSystemLibPath(), 475 SHARED_LIB_PREFIX); 476 loadShareLibfromDFS(tempShareLibMap, shareLibpath); 477 478 if (shareLibpath != null) { 479 status.put("sharelibDirNew", shareLibpath.toString()); 480 status.put("sharelibDirOld", sharelibDirOld); 481 sharelibDirOld = shareLibpath.toString(); 482 } 483 484 } 485 shareLibMap = tempShareLibMap; 486 return status; 487 } 488 489 /** 490 * Update share lib cache. Parse the share lib directory and each sub 491 * directory is a action key 492 * 493 * @param shareLibMap the share lib jar map 494 * @param shareLibpath the share libpath 495 * @throws IOException Signals that an I/O exception has occurred. 496 */ 497 private void loadShareLibfromDFS(Map<String, List<Path>> shareLibMap, Path shareLibpath) throws IOException { 498 499 if (shareLibpath == null) { 500 LOG.info("No share lib directory found"); 501 return; 502 503 } 504 505 FileStatus[] dirList = fs.listStatus(shareLibpath); 506 507 if (dirList == null) { 508 return; 509 } 510 511 for (FileStatus dir : dirList) { 512 if (!dir.isDir()) { 513 continue; 514 } 515 List<Path> listOfPaths = new ArrayList<Path>(); 516 getPathRecursively(fs, dir.getPath(), listOfPaths); 517 shareLibMap.put(dir.getPath().getName(), listOfPaths); 518 LOG.info("Share lib for " + dir.getPath().getName() + ":" + listOfPaths); 519 520 } 521 522 } 523 524 /** 525 * Load share lib text file. Sharelib mapping files contains list of 526 * key=value. where key is the action key and value is the DFS location of 527 * sharelib files. 528 * 529 * @param shareLibMap the share lib jar map 530 * @param sharelipFileMapping the sharelip file mapping 531 * @throws IOException Signals that an I/O exception has occurred. 532 */ 533 @SuppressWarnings("unchecked") 534 private void loadShareLibMetaFile(Map<String, List<Path>> shareLibMap, String sharelibFileMapping) 535 throws IOException { 536 537 Path shareFileMappingPath = new Path(sharelibFileMapping); 538 HadoopAccessorService has = Services.get().get(HadoopAccessorService.class); 539 FileSystem filesystem = FileSystem.get(has.createJobConf(shareFileMappingPath.toUri().getAuthority())); 540 Properties prop = new Properties(); 541 prop.load(filesystem.open(new Path(sharelibFileMapping))); 542 543 for (Object keyObject : prop.keySet()) { 544 String key = (String) keyObject; 545 if (key.toLowerCase().startsWith(SHARE_LIB_CONF_PREFIX)) { 546 String mapKey = key.substring(SHARE_LIB_CONF_PREFIX.length() + 1); 547 String pathList[] = ((String) prop.get(key)).split(","); 548 List<Path> listOfPaths = new ArrayList<Path>(); 549 for (String dfsPath : pathList) { 550 getPathRecursively(fs, new Path(dfsPath), listOfPaths); 551 } 552 shareLibMap.put(mapKey, listOfPaths); 553 LOG.info("Share lib for " + mapKey + ":" + listOfPaths); 554 555 } 556 else { 557 LOG.info(" Not adding " + key + " to sharelib, not prefix with " + SHARE_LIB_CONF_PREFIX); 558 } 559 560 } 561 } 562 563 /** 564 * Gets the launcherlib path. 565 * 566 * @return the launcherlib path 567 */ 568 private Path getLauncherlibPath() { 569 String formattedDate = dateFormat.format(Calendar.getInstance(TimeZone.getTimeZone("GMT")).getTime()); 570 Path tmpLauncherLibPath = new Path(services.get(WorkflowAppService.class).getSystemLibPath(), LAUNCHER_PREFIX 571 + formattedDate); 572 return tmpLauncherLibPath; 573 } 574 575 /** 576 * Gets the Latest lib path. 577 * 578 * @param rootDir the root dir 579 * @param prefix the prefix 580 * @return latest lib path 581 * @throws IOException Signals that an I/O exception has occurred. 582 */ 583 public Path getLatestLibPath(Path rootDir, final String prefix) throws IOException { 584 Date max = new Date(0L); 585 Path path = null; 586 PathFilter directoryFilter = new PathFilter() { 587 @Override 588 public boolean accept(Path path) { 589 return path.getName().startsWith(prefix); 590 } 591 }; 592 593 FileStatus[] files = fs.listStatus(rootDir, directoryFilter); 594 for (FileStatus file : files) { 595 String name = file.getPath().getName().toString(); 596 String time = name.substring(prefix.length()); 597 Date d = null; 598 try { 599 d = dateFormat.parse(time); 600 } 601 catch (ParseException e) { 602 continue; 603 } 604 if (d.compareTo(max) > 0) { 605 path = file.getPath(); 606 max = d; 607 } 608 } 609 //If there are no timestamped directories, fall back to root directory 610 if (path == null) { 611 path = rootDir; 612 } 613 return path; 614 } 615 616 /** 617 * Instruments the log service. 618 * <p/> 619 * It sets instrumentation variables indicating the location of the sharelib and launcherlib 620 * 621 * @param instr instrumentation to use. 622 */ 623 @Override 624 public void instrument(Instrumentation instr) { 625 instr.addVariable("libs", "sharelib.source", new Instrumentation.Variable<String>() { 626 @Override 627 public String getValue() { 628 if (!StringUtils.isEmpty(sharelibMappingFile)) { 629 return SHARELIB_MAPPING_FILE; 630 } 631 return WorkflowAppService.SYSTEM_LIB_PATH; 632 } 633 }); 634 instr.addVariable("libs", "sharelib.mapping.file", new Instrumentation.Variable<String>() { 635 @Override 636 public String getValue() { 637 if (!StringUtils.isEmpty(sharelibMappingFile)) { 638 return sharelibMappingFile; 639 } 640 return "(none)"; 641 } 642 }); 643 instr.addVariable("libs", "sharelib.system.libpath", new Instrumentation.Variable<String>() { 644 @Override 645 public String getValue() { 646 String sharelibPath = "(unavailable)"; 647 try { 648 Path libPath = getLatestLibPath(services.get(WorkflowAppService.class).getSystemLibPath(), 649 SHARED_LIB_PREFIX); 650 if (libPath != null) { 651 sharelibPath = libPath.toUri().toString(); 652 } 653 } 654 catch (IOException ioe) { 655 // ignore exception because we're just doing instrumentation 656 } 657 return sharelibPath; 658 } 659 }); 660 instr.addVariable("libs", "sharelib.mapping.file.timestamp", new Instrumentation.Variable<String>() { 661 @Override 662 public String getValue() { 663 if (!StringUtils.isEmpty(sharelibMetaFileOldTimeStamp)) { 664 return sharelibMetaFileOldTimeStamp; 665 } 666 return "(none)"; 667 } 668 }); 669 instr.addVariable("libs", "sharelib.keys", new Instrumentation.Variable<String>() { 670 @Override 671 public String getValue() { 672 Map<String, List<Path>> shareLib = getShareLib(); 673 if (shareLib != null && !shareLib.isEmpty()) { 674 Set<String> keySet = shareLib.keySet(); 675 return keySet.toString(); 676 } 677 return "(unavailable)"; 678 } 679 }); 680 instr.addVariable("libs", "launcherlib.system.libpath", new Instrumentation.Variable<String>() { 681 @Override 682 public String getValue() { 683 return getLauncherlibPath().toUri().toString(); 684 } 685 }); 686 } 687 688 /** 689 * Returns file system for shared libraries. 690 * <p/> 691 * If WorkflowAppService#getSystemLibPath doesn't have authority then a default one assumed 692 * 693 * @return file system for shared libraries 694 */ 695 public FileSystem getFileSystem() { 696 return fs; 697 } 698}