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    package org.apache.logging.log4j.core.config.plugins;
018    
019    import org.apache.logging.log4j.Logger;
020    import org.apache.logging.log4j.core.helpers.Loader;
021    import org.apache.logging.log4j.status.StatusLogger;
022    
023    import java.io.BufferedInputStream;
024    import java.io.BufferedOutputStream;
025    import java.io.DataInputStream;
026    import java.io.DataOutputStream;
027    import java.io.FileOutputStream;
028    import java.io.InputStream;
029    import java.text.DecimalFormat;
030    import java.util.HashMap;
031    import java.util.Map;
032    import java.util.concurrent.ConcurrentHashMap;
033    import java.util.concurrent.ConcurrentMap;
034    import java.util.concurrent.CopyOnWriteArrayList;
035    
036    /**
037     * Component that loads and manages all the plugins.
038     */
039    public class PluginManager {
040    
041        private static final long NANOS_PER_SECOND = 1000000000L;
042    
043        private static ConcurrentMap<String, ConcurrentMap<String, PluginType>> pluginTypeMap =
044            new ConcurrentHashMap<String, ConcurrentMap<String, PluginType>>();
045    
046        private static CopyOnWriteArrayList<String> packages = new CopyOnWriteArrayList<String>();
047        private static final String PATH = "org/apache/logging/log4j/core/config/plugins/";
048        private static final String FILENAME = "Log4j2Plugins.dat";
049        private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";
050    
051        private static final Logger LOGGER = StatusLogger.getLogger();
052    
053        private static String rootDir;
054    
055        private Map<String, PluginType> plugins = new HashMap<String, PluginType>();
056        private final String type;
057        private final Class clazz;
058    
059        /**
060         * Constructor that takes only a type name.
061         * @param type The type name.
062         */
063        public PluginManager(String type) {
064            this.type = type;
065            this.clazz = null;
066        }
067    
068        /**
069         * Constructor that takes a type name and a Class.
070         * @param type The type that must be matched.
071         * @param clazz The Class each match must be an instance of.
072         */
073        public PluginManager(String type, Class clazz) {
074            this.type = type;
075            this.clazz = clazz;
076        }
077    
078        public static void main(String[] args) throws Exception {
079            if (args == null || args.length < 1) {
080                System.err.println("A target directory must be specified");
081                System.exit(-1);
082            }
083            rootDir = args[0].endsWith("/") || args[0].endsWith("\\") ? args[0] : args[0] + "/";
084    
085            PluginManager manager = new PluginManager("Core");
086            manager.collectPlugins(false);
087            encode(pluginTypeMap);
088        }
089    
090        /**
091         * Adds a package name to be scanned for plugins. Must be invoked prior to plugins being collected.
092         * @param p The package name.
093         */
094        public static void addPackage(String p) {
095            packages.addIfAbsent(p);
096        }
097    
098        /**
099         * Returns the type of a specified plugin.
100         * @param name The name of the plugin.
101         * @return The plugin's type.
102         */
103        public PluginType getPluginType(String name) {
104            return plugins.get(name.toLowerCase());
105        }
106    
107        /**
108         * Returns all the matching plugins.
109         * @return A Map containing the name of the plugin and its type.
110         */
111        public Map<String, PluginType> getPlugins() {
112            return plugins;
113        }
114    
115        /**
116         * Locates all the plugins.
117         */
118        public void collectPlugins() {
119            collectPlugins(true);
120        }
121    
122        /**
123         * Collects plugins, optionally obtaining them from a preload map.
124         * @param preLoad if true, plugins will be obtained from the preload map.
125         */
126        public void collectPlugins(boolean preLoad) {
127            if (pluginTypeMap.containsKey(type)) {
128                plugins = pluginTypeMap.get(type);
129                preLoad = false;
130            }
131            long start = System.nanoTime();
132            ResolverUtil<?> r = new ResolverUtil();
133            ClassLoader loader = Loader.getClassLoader();
134            if (loader != null) {
135                r.setClassLoader(loader);
136            }
137            if (preLoad) {
138                ConcurrentMap<String, ConcurrentMap<String, PluginType>> map = decode(loader);
139                if (map != null) {
140                    pluginTypeMap = map;
141                    plugins = map.get(type);
142                } else {
143                    LOGGER.warn("Plugin preloads not available");
144                }
145            }
146            if (plugins.size() == 0) {
147                packages.add(LOG4J_PACKAGES);
148            }
149            ResolverUtil.Test test = new PluginTest(clazz);
150            for (String pkg : packages) {
151                r.findInPackage(test, pkg);
152            }
153            for (Class<?> item : r.getClasses()) {
154                Plugin p = item.getAnnotation(Plugin.class);
155                String pluginType = p.type();
156                if (!pluginTypeMap.containsKey(pluginType)) {
157                    pluginTypeMap.putIfAbsent(pluginType, new ConcurrentHashMap<String, PluginType>());
158                }
159                Map<String, PluginType> map = pluginTypeMap.get(pluginType);
160                String type = p.elementType().equals(Plugin.EMPTY) ? p.name() : p.elementType();
161                map.put(p.name().toLowerCase(), new PluginType(item, type, p.printObject(), p.deferChildren()));
162            }
163            long elapsed = System.nanoTime() - start;
164            plugins = pluginTypeMap.get(type);
165            StringBuilder sb = new StringBuilder("Generated plugins");
166            sb.append(" in ");
167            DecimalFormat numFormat = new DecimalFormat("#0");
168            long seconds = elapsed / NANOS_PER_SECOND;
169            elapsed %= NANOS_PER_SECOND;
170            sb.append(numFormat.format(seconds)).append(".");
171            numFormat = new DecimalFormat("000000000");
172            sb.append(numFormat.format(elapsed)).append(" seconds");
173            LOGGER.debug(sb.toString());
174        }
175    
176        private static ConcurrentMap<String, ConcurrentMap<String, PluginType>> decode(ClassLoader loader) {
177            String resource = PATH + FILENAME;
178            try {
179                InputStream is = loader.getResourceAsStream(resource);
180                BufferedInputStream bis = new BufferedInputStream(is);
181                DataInputStream dis = new DataInputStream(bis);
182                int count = dis.readInt();
183                ConcurrentMap<String, ConcurrentMap<String, PluginType>> map =
184                    new ConcurrentHashMap<String, ConcurrentMap<String, PluginType>>(count);
185                for (int j = 0; j < count; ++j) {
186                    String type = dis.readUTF();
187                    int entries = dis.readInt();
188                    ConcurrentMap<String, PluginType> types = new ConcurrentHashMap<String, PluginType>(count);
189                    for (int i = 0; i < entries; ++i) {
190                        String key = dis.readUTF();
191                        String className = dis.readUTF();
192                        String name = dis.readUTF();
193                        boolean printable = dis.readBoolean();
194                        boolean defer = dis.readBoolean();
195                        Class clazz = Class.forName(className);
196                        types.put(key, new PluginType(clazz, name, printable, defer));
197                    }
198                    map.putIfAbsent(type, types);
199                }
200                dis.close();
201                return map;
202            } catch (Exception ex) {
203                LOGGER.warn("Unable to preload plugins", ex);
204                return null;
205            }
206        }
207    
208        private static void encode(ConcurrentMap<String, ConcurrentMap<String, PluginType>> map) {
209            String fileName = rootDir + PATH + FILENAME;
210            try {
211                FileOutputStream fos = new FileOutputStream(fileName);
212                BufferedOutputStream bos = new BufferedOutputStream(fos);
213                DataOutputStream dos = new DataOutputStream(bos);
214                dos.writeInt(map.size());
215                for (Map.Entry<String, ConcurrentMap<String, PluginType>> outer : map.entrySet()) {
216                    dos.writeUTF(outer.getKey());
217                    dos.writeInt(outer.getValue().size());
218                    for (Map.Entry<String, PluginType> entry : outer.getValue().entrySet()) {
219                        dos.writeUTF(entry.getKey());
220                        PluginType pt = entry.getValue();
221                        dos.writeUTF(pt.getPluginClass().getName());
222                        dos.writeUTF(pt.getElementName());
223                        dos.writeBoolean(pt.isObjectPrintable());
224                        dos.writeBoolean(pt.isDeferChildren());
225                    }
226                }
227                dos.close();
228            } catch (Exception ex) {
229                ex.printStackTrace();
230            }
231        }
232    
233        /**
234         * A Test that checks to see if each class is annotated with a specific annotation. If it
235         * is, then the test returns true, otherwise false.
236         */
237        public static class PluginTest extends ResolverUtil.ClassTest {
238            private final Class isA;
239    
240            /**
241             * Constructs an AnnotatedWith test for the specified annotation type.
242             * @param isA The class to compare against.
243             */
244            public PluginTest(Class isA) {
245                this.isA = isA;
246            }
247    
248            /**
249             * Returns true if the type is annotated with the class provided to the constructor.
250             * @param type The type to check for.
251             * @return true if the Class is of the specified type.
252             */
253            public boolean matches(Class type) {
254                return type != null && type.isAnnotationPresent(Plugin.class) &&
255                    (isA == null || isA.isAssignableFrom(type));
256            }
257    
258            @Override
259            public String toString() {
260                StringBuilder msg = new StringBuilder("annotated with @" + Plugin.class.getSimpleName());
261                if (isA != null) {
262                    msg.append(" is assignable to " + isA.getSimpleName());
263                }
264                return msg.toString();
265            }
266        }
267    
268    }