DefaultPluginsDir.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.tika.pipes.core.config;

import java.nio.file.Files;
import java.nio.file.Path;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Resolves the default {@code plugins} directory when {@code plugin-roots}
 * is not configured (TIKA-4864). The probe order matches the install
 * layouts Tika ships:
 * <ol>
 *   <li>next to the jar the anchor class was loaded from (a flat install,
 *       tika-grpc's docker image);</li>
 *   <li>next to that directory's parent (the unpacked distributions and the
 *       tika-server docker image load classes from {@code lib/}, the plugins
 *       sit beside {@code lib/});</li>
 *   <li>a {@code plugins} directory in the current working directory.</li>
 * </ol>
 * The result is always absolute: the forked pipes server resolves the
 * configured value against its own working directory, which need not be the
 * parent's, so a relative default would make the two processes disagree.
 */
public final class DefaultPluginsDir {

    private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginsDir.class);

    /**
     * The directory name probed in each location.
     */
    public static final String PLUGINS_DIR_NAME = "plugins";

    private DefaultPluginsDir() {
    }

    /**
     * Resolves the default plugins directory for the install layout of the
     * given class.
     *
     * @param anchor the class whose code source anchors the probe, usually
     *               the caller
     * @return the absolute path of the first {@code plugins} directory found,
     * or the absolute path of {@code plugins} in the working directory if
     * none exists yet
     */
    public static String resolve(Class<?> anchor) {
        Path codeSourceDir = null;
        try {
            codeSourceDir = Path.of(anchor.getProtectionDomain().getCodeSource().getLocation()
                    .toURI()).getParent();
        } catch (Exception e) {
            //no code source (e.g. a repacked classloader): probe the working
            //directory only
        }
        return resolve(codeSourceDir, Path.of("")).toString();
    }

    /**
     * The probe itself, separated from the code-source lookup for testing.
     *
     * @param codeSourceDir the directory holding the anchor's jar, or null
     * @param cwd           the working directory to fall back to
     * @return the absolute path of the resolved directory
     */
    public static Path resolve(Path codeSourceDir, Path cwd) {
        if (codeSourceDir != null) {
            Path nextToJar = codeSourceDir.resolve(PLUGINS_DIR_NAME);
            if (Files.isDirectory(nextToJar)) {
                return nextToJar.toAbsolutePath();
            }
            Path parent = codeSourceDir.getParent();
            if (parent != null) {
                Path nextToParent = parent.resolve(PLUGINS_DIR_NAME);
                if (Files.isDirectory(nextToParent)) {
                    return nextToParent.toAbsolutePath();
                }
            }
        }
        Path fallback = cwd.resolve(PLUGINS_DIR_NAME).toAbsolutePath();
        if (!Files.isDirectory(fallback)) {
            LOG.warn("no plugins directory found in the install layout or at {}; "
                    + "pipes plugins will not load unless plugin-roots is configured", fallback);
        }
        return fallback;
    }
}