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