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