This project has retired. For details please refer to its Attic page.
Source code
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}