All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.tika.config.ServiceLoader Maven / Gradle / Ivy

/*
 * 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.config;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Internal utility class that Tika uses to look up service providers.
 *
 * @since Apache Tika 0.9
 */
public class ServiceLoader {

    /**
     * The default context class loader to use for all threads, or
     * null to automatically select the context class loader.
     */
    private static volatile ClassLoader contextClassLoader = null;

    private static class RankedService implements Comparable {
        private Object service;
        private int rank;

        public RankedService(Object service, int rank) {
            this.service = service;
            this.rank = rank;
        }

        public boolean isInstanceOf(Class iface) {
            return iface.isAssignableFrom(service.getClass());
        }

        public int compareTo(RankedService that) {
            return that.rank - rank; // highest number first
        }

    }

    /**
     * The dynamic set of services available in an OSGi environment.
     * Managed by the {@link TikaActivator} class and used as an additional
     * source of service instances in the {@link #loadServiceProviders(Class)}
     * method.
     */
    private static final Map services =
            new HashMap();

    /**
     * Returns the context class loader of the current thread. If such
     * a class loader is not available, then the loader of this class or
     * finally the system class loader is returned.
     *
     * @see TIKA-441
     * @return context class loader, or null if no loader
     *         is available
     */
    static ClassLoader getContextClassLoader() {
        ClassLoader loader = contextClassLoader;
        if (loader == null) {
            loader = ServiceLoader.class.getClassLoader();
        }
        if (loader == null) {
            loader = ClassLoader.getSystemClassLoader();
        }
        return loader;
    }

    /**
     * Sets the context class loader to use for all threads that access
     * this class. Used for example in an OSGi environment to avoid problems
     * with the default context class loader.
     *
     * @param loader default context class loader,
     *               or null to automatically pick the loader
     */
    public static void setContextClassLoader(ClassLoader loader) {
        contextClassLoader = loader;
    }

    static void addService(Object reference, Object service, int rank) {
        synchronized (services) {
            services.put(reference, new RankedService(service, rank));
        }
    }

    static Object removeService(Object reference) {
        synchronized (services) {
            return services.remove(reference);
        }
    }

    private final ClassLoader loader;

    private final LoadErrorHandler handler;

    private final boolean dynamic;
    
    public ServiceLoader(
            ClassLoader loader, LoadErrorHandler handler, boolean dynamic) {
        this.loader = loader;
        this.handler = handler;
        this.dynamic = dynamic;
    }

    public ServiceLoader(ClassLoader loader, LoadErrorHandler handler) {
        this(loader, handler, false);
    }

    public ServiceLoader(ClassLoader loader) {
    	this(loader, Boolean.getBoolean("org.apache.tika.service.error.warn") 
    			? LoadErrorHandler.WARN:LoadErrorHandler.IGNORE);
    }

    public ServiceLoader() {
    	this(getContextClassLoader(), Boolean.getBoolean("org.apache.tika.service.error.warn") 
    			? LoadErrorHandler.WARN:LoadErrorHandler.IGNORE, true);
    }
    
    /**
     * Returns if the service loader is static or dynamic
     * 
     * @return dynamic or static loading
     * @since Apache Tika 1.10
     */
    public boolean isDynamic() {
        return dynamic;
    }

    /**
     * Returns the load error handler used by this loader.
     *
     * @return load error handler
     * @since Apache Tika 1.3
     */
    public LoadErrorHandler getLoadErrorHandler() {
        return handler;
    }

    /**
     * Returns an input stream for reading the specified resource from the
     * configured class loader.
     *
     * @param name resource name
     * @return input stream, or null if the resource was not found
     * @see ClassLoader#getResourceAsStream(String)
     * @since Apache Tika 1.1
     */
    public InputStream getResourceAsStream(String name) {
        if (loader != null) {
            return loader.getResourceAsStream(name);
        } else {
            return null;
        }
    }

    /**
     * Loads and returns the named service class that's expected to implement
     * the given interface.
     * 
     * Note that this class does not use the {@link LoadErrorHandler}, a
     *  {@link ClassNotFoundException} is always returned for unknown
     *  classes or classes of the wrong type
     *
     * @param iface service interface
     * @param name service class name
     * @return service class
     * @throws ClassNotFoundException if the service class can not be found
     *                                or does not implement the given interface
     * @see Class#forName(String, boolean, ClassLoader)
     * @since Apache Tika 1.1
     */
    @SuppressWarnings("unchecked")
    public  Class getServiceClass(Class iface, String name)
            throws ClassNotFoundException {
        if (loader == null) {
            throw new ClassNotFoundException(
                    "Service class " + name + " is not available");
        }
        Class klass = Class.forName(name, true, loader);
        if (klass.isInterface()) {
            throw new ClassNotFoundException(
                    "Service class " + name + " is an interface");
        } else if (!iface.isAssignableFrom(klass)) {
            throw new ClassNotFoundException(
                    "Service class " + name
                    + " does not implement " + iface.getName());
        } else {
            return (Class) klass;
        }
    }

    /**
     * Returns all the available service resources matching the
     *  given pattern, such as all instances of tika-mimetypes.xml 
     *  on the classpath, or all org.apache.tika.parser.Parser 
     *  service files.
     */
    public Enumeration findServiceResources(String filePattern) {
       try {    	  
          Enumeration resources = loader.getResources(filePattern);
          return resources;
       } catch (IOException ignore) {
          // We couldn't get the list of service resource files
          List empty = Collections.emptyList();
          return Collections.enumeration( empty );
      }
    }

    /**
     * Returns all the available service providers of the given type.
     *
     * @param iface service provider interface
     * @return available service providers
     */
    public  List loadServiceProviders(Class iface) {
        List providers = new ArrayList();
        providers.addAll(loadDynamicServiceProviders(iface));
        providers.addAll(loadStaticServiceProviders(iface));
        return providers;
    }

    /**
     * Returns the available dynamic service providers of the given type.
     * The returned list is newly allocated and may be freely modified
     * by the caller.
     *
     * @since Apache Tika 1.2
     * @param iface service provider interface
     * @return dynamic service providers
     */
    @SuppressWarnings("unchecked")
    public  List loadDynamicServiceProviders(Class iface) {
        if (dynamic) {
            synchronized (services) {
                List list =
                        new ArrayList(services.values());
                Collections.sort(list);

                List providers = new ArrayList(list.size());
                for (RankedService service : list) {
                    if (service.isInstanceOf(iface)) {
                        providers.add((T) service.service);
                    }
                }
                return providers;
            }
        } else {
            return new ArrayList(0);
        }
    }

    /**
     * Returns the defined static service providers of the given type, without
     * attempting to load them.
     * The providers are loaded using the service provider mechanism using
     * the configured class loader (if any).
     *
     * @since Apache Tika 1.6
     * @param iface service provider interface
     * @return static list of uninitialised service providers
     */
    protected  List identifyStaticServiceProviders(Class iface) {
        List names = new ArrayList();

        if (loader != null) {
            String serviceName = iface.getName();
            Enumeration resources =
                    findServiceResources("META-INF/services/" + serviceName);
            for (URL resource : Collections.list(resources)) {
                try {
                    collectServiceClassNames(resource, names);
                } catch (IOException e) {
                    handler.handleLoadError(serviceName, e);
                }
            }
        }
        
        return names;
    }

    /**
     * Returns the available static service providers of the given type.
     * The providers are loaded using the service provider mechanism using
     * the configured class loader (if any). The returned list is newly
     * allocated and may be freely modified by the caller.
     *
     * @since Apache Tika 1.2
     * @param iface service provider interface
     * @return static service providers
     */
    @SuppressWarnings("unchecked")
    public  List loadStaticServiceProviders(Class iface) {
        List providers = new ArrayList();

        if (loader != null) {
            List names = identifyStaticServiceProviders(iface);

            for (String name : names) {
                try {
                    Class klass = loader.loadClass(name);
                    if (iface.isAssignableFrom(klass)) {
                        providers.add((T) klass.newInstance());
                    }
                } catch (Throwable t) {
                    handler.handleLoadError(name, t);
                }
            }
        }
        return providers;
    }

    private static final Pattern COMMENT = Pattern.compile("#.*");

    private static final Pattern WHITESPACE = Pattern.compile("\\s+");

    private void collectServiceClassNames(URL resource, Collection names)
            throws IOException {
        try (InputStream stream = resource.openStream()) {
            BufferedReader reader =
                    new BufferedReader(new InputStreamReader(stream, UTF_8));
            String line = reader.readLine();
            while (line != null) {
                line = COMMENT.matcher(line).replaceFirst("");
                line = WHITESPACE.matcher(line).replaceAll("");
                if (line.length() > 0) {
                    names.add(line);
                }
                line = reader.readLine();
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy