001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017
018package org.apache.logging.log4j.util;
019
020import java.lang.invoke.CallSite;
021import java.lang.invoke.LambdaMetafactory;
022import java.lang.invoke.MethodHandle;
023import java.lang.invoke.MethodHandles.Lookup;
024import java.lang.invoke.MethodType;
025import java.security.AccessController;
026import java.security.PrivilegedAction;
027import java.util.Collections;
028import java.util.HashSet;
029import java.util.Iterator;
030import java.util.ServiceConfigurationError;
031import java.util.ServiceLoader;
032import java.util.Set;
033import java.util.Spliterator;
034import java.util.function.Consumer;
035import java.util.stream.Stream;
036import java.util.stream.StreamSupport;
037
038import org.apache.logging.log4j.Logger;
039import org.apache.logging.log4j.status.StatusLogger;
040
041/**
042 * This class should be considered internal.
043 */
044public final class ServiceLoaderUtil {
045
046    private ServiceLoaderUtil() {
047    }
048
049    /**
050     * Retrieves the available services from the caller's classloader.
051     * 
052     * Broken services will be ignored.
053     * 
054     * @param <T>         The service type.
055     * @param serviceType The class of the service.
056     * @param lookup      The calling class data.
057     * @return A stream of service instances.
058     */
059    public static <T> Stream<T> loadServices(final Class<T> serviceType, Lookup lookup) {
060        return loadServices(serviceType, lookup, false);
061    }
062
063    /**
064     * Retrieves the available services from the caller's classloader and possibly
065     * the thread context classloader.
066     * 
067     * Broken services will be ignored.
068     * 
069     * @param <T>         The service type.
070     * @param serviceType The class of the service.
071     * @param lookup      The calling class data.
072     * @param useTccl     If true the thread context classloader will also be used.
073     * @return A stream of service instances.
074     */
075    public static <T> Stream<T> loadServices(final Class<T> serviceType, Lookup lookup, boolean useTccl) {
076        return loadServices(serviceType, lookup, useTccl, true);
077    }
078
079    static <T> Stream<T> loadServices(final Class<T> serviceType, final Lookup lookup, final boolean useTccl,
080            final boolean verbose) {
081        final ClassLoader classLoader = lookup.lookupClass().getClassLoader();
082        Stream<T> services = loadClassloaderServices(serviceType, lookup, classLoader, verbose);
083        if (useTccl) {
084            final ClassLoader contextClassLoader = LoaderUtil.getThreadContextClassLoader();
085            if (contextClassLoader != classLoader) {
086                services = Stream.concat(services,
087                        loadClassloaderServices(serviceType, lookup, contextClassLoader, verbose));
088            }
089        }
090        if (OsgiServiceLocator.isAvailable()) {
091            services = Stream.concat(services, OsgiServiceLocator.loadServices(serviceType, lookup, verbose));
092        }
093        final Set<Class<?>> classes = new HashSet<>();
094        // only the first occurrence of a class
095        return services.filter(service -> classes.add(service.getClass()));
096    }
097
098    static <T> Stream<T> loadClassloaderServices(final Class<T> serviceType, final Lookup lookup,
099            final ClassLoader classLoader, final boolean verbose) {
100        return StreamSupport.stream(new ServiceLoaderSpliterator<T>(serviceType, lookup, classLoader, verbose), false);
101    }
102
103    static <T> Iterable<T> callServiceLoader(Lookup lookup, Class<T> serviceType, ClassLoader classLoader,
104            boolean verbose) {
105        try {
106            // Creates a lambda in the caller's domain that calls `ServiceLoader`
107            final MethodHandle loadHandle = lookup.findStatic(ServiceLoader.class, "load",
108                    MethodType.methodType(ServiceLoader.class, Class.class, ClassLoader.class));
109            final CallSite callSite = LambdaMetafactory.metafactory(lookup,
110                    "run",
111                    MethodType.methodType(PrivilegedAction.class, Class.class, ClassLoader.class),
112                    MethodType.methodType(Object.class),
113                    loadHandle,
114                    MethodType.methodType(ServiceLoader.class));
115            final PrivilegedAction<ServiceLoader<T>> action = (PrivilegedAction<ServiceLoader<T>>) callSite
116                    .getTarget()//
117                    .bindTo(serviceType)
118                    .bindTo(classLoader)
119                    .invoke();
120            final ServiceLoader<T> serviceLoader;
121            if (System.getSecurityManager() == null) {
122                serviceLoader = action.run();
123            } else {
124                final MethodHandle privilegedHandle = lookup.findStatic(AccessController.class, "doPrivileged",
125                        MethodType.methodType(Object.class, PrivilegedAction.class));
126                serviceLoader = (ServiceLoader<T>) privilegedHandle.invoke(action);
127            }
128            return serviceLoader;
129        } catch (Throwable e) {
130            if (verbose) {
131                StatusLogger.getLogger().error("Unable to load services for service {}", serviceType, e);
132            }
133        }
134        return Collections.emptyList();
135    }
136
137    private static class ServiceLoaderSpliterator<S> implements Spliterator<S> {
138
139        private final Iterator<S> serviceIterator;
140        private final Logger logger;
141        private final String serviceName;
142
143        public ServiceLoaderSpliterator(final Class<S> serviceType, final Lookup lookup, final ClassLoader classLoader,
144                final boolean verbose) {
145            this.serviceIterator = callServiceLoader(lookup, serviceType, classLoader, verbose).iterator();
146            this.logger = verbose ? StatusLogger.getLogger() : null;
147            this.serviceName = serviceType.toString();
148        }
149
150        @Override
151        public boolean tryAdvance(Consumer<? super S> action) {
152            while (serviceIterator.hasNext()) {
153                try {
154                    action.accept(serviceIterator.next());
155                    return true;
156                } catch (ServiceConfigurationError e) {
157                    if (logger != null) {
158                        logger.warn("Unable to load service class for service {}", serviceName, e);
159                    }
160                }
161            }
162            return false;
163        }
164
165        @Override
166        public Spliterator<S> trySplit() {
167            return null;
168        }
169
170        @Override
171        public long estimateSize() {
172            return Long.MAX_VALUE;
173        }
174
175        @Override
176        public int characteristics() {
177            return NONNULL | IMMUTABLE;
178        }
179
180    }
181}