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

de.wicketbuch.extensions.classpathpreloader.ClassPathPreloader Maven / Gradle / Ivy

/**
 * Copyright (C) 2018 Carl-Eric Menzel ,
 * Antonia Schmalstieg ,
 * and possibly other classpathpreloader contributors.
 *
 * Licensed 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 de.wicketbuch.extensions.classpathpreloader;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnel;
import com.google.common.hash.PrimitiveSink;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.matchprocessor.FilenameMatchProcessor;
import org.apache.wicket.Application;
import org.apache.wicket.core.util.lang.WicketObjects;
import org.apache.wicket.core.util.resource.ClassPathResourceFinder;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.util.file.IResourceFinder;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.nio.charset.Charset;
import java.util.ListIterator;

/**
 * Use this if your filesystem or classloading is unusually slow and Wicket's resource loading strategy of trying many different paths, e.g. with locales and
 * variations, is taking too much time. The symptom of this is that the first request with a previously unusued locale takes an unacceptably long time. This
 * can happen e.g. on some IBM systems. Do not use this if your production system is not affected! Also, to keep development turnaround times low,
 * only use it in {@link org.apache.wicket.RuntimeConfigurationType#DEPLOYMENT DEPLOYMENT} mode.
 * 

*

* To use {@code ClassPathPreloader} in your application, do this in {@link Application#init()}: *

 *     ClassPathPreloader.configure(this);
 * 
* Note: If your application uses custom {@link IResourceFinder}s, initialize them before {@code ClassPathPreloader}. *

* {@code ClassPathPreloader} scans the entire classpath to have a list of available resources. All {@link ClassPathResourceFinder}s in the given * {@link Application} are wrapped to check against this list before trying to load a resource. This way non-existing paths are not actually accessed, saving * a lot of time on systems affected by this. Essentially you will be trading application start time and a constant amount of memory for first-request-served * time. *

* To conserve memory and still be fast, we use a {@link BloomFilter} instead of a List of strings to store which paths exist on the classpath. A bloom filter * is set up to expect a number of elements and provide an acceptable false positive rate up to that limit. This implementation is currently set to expect up * to 1,000,000 classpath entries with a false positive probability of 0.1. This leads to memory consumption of roughly 600KB, regardless of how many entries * are stored. A false positive here means that a non-existing path is mistakenly thought to exist and therefore the underlying * {@link ClassPathResourceFinder} will try to load it. Bloom filters do not produce false negatives, so no actually existing path will ever be denied. */ @SuppressWarnings("UnstableApiUsage") public class ClassPathPreloader { private static final Logger log = LoggerFactory.getLogger(ClassPathPreloader.class); private static final long BLOOM_SIZE = 1_000_000L; private static final Funnel FUNNEL = new Funnel() { @Override public void funnel(String s, PrimitiveSink primitiveSink) { primitiveSink.putString(s, Charset.defaultCharset()); } }; private BloomFilter knownPaths = BloomFilter.create(FUNNEL, BLOOM_SIZE, 0.1); private int numberOfFoundPaths = 0; /** * Create a ClassPathPreloader and configure the {@link Application} to use it. Call this after initializing any {@link IResourceFinder}s. Depending on * your system and the size of your classpath, this might take some time! It is strongly recommended to only use this on systems that need it, and only * in {@link org.apache.wicket.RuntimeConfigurationType#DEPLOYMENT DEPLOYMENT} mode. */ public static ClassPathPreloader configure(Application application) { return new ClassPathPreloader(application); } private ClassPathPreloader(Application application) { // find all ClassPathResourceFinders and wrap them final ListIterator it = application.getResourceSettings().getResourceFinders().listIterator(); while (it.hasNext()) { final IResourceFinder finder = it.next(); if (finder instanceof ClassPathResourceFinder) { it.set(new PrescanningClasspathResourceFinder(((ClassPathResourceFinder) finder))); } } log.info("scanning classpath"); long startTime = System.currentTimeMillis(); // this gives us all files found in all classpath elements, including all dependency jars. new FastClasspathScanner().matchFilenamePattern(".*", new FilenameMatchProcessor() { @Override public void processMatch(File classpathElt, String relativePath) { log.trace("found on classpath: {}", relativePath); knownPaths.put(relativePath); numberOfFoundPaths++; } }).scan(); log.info("scanning complete after {} ms", System.currentTimeMillis() - startTime); log.info("found a total of {} files", numberOfFoundPaths); log.info("bloom filter using a total of {} bytes", WicketObjects.sizeof(knownPaths)); } /** * This wraps a ClassPathResourceFinder and only lets it load existing paths. */ class PrescanningClasspathResourceFinder implements IResourceFinder { private final ClassPathResourceFinder delegate; private final String prefix; PrescanningClasspathResourceFinder(ClassPathResourceFinder delegate) { this.delegate = delegate; // This is evil: PropertyModel handles the reflection for us to access the private // field "prefix" in ClassPathResourceFinder. Remove this once Wicket adds // a getter. See find() on why we need this. String delegatePrefix = new PropertyModel(delegate, "prefix").getObject(); if (!Strings.isEmpty(delegatePrefix) && !delegatePrefix.endsWith("/")) { delegatePrefix += "/"; } this.prefix = delegatePrefix; } @Override public IResourceStream find(Class clazz, String pathname) { // ClassPathResourceFinder can have a prefix so that looking for path "foo/bar" does not start at the classpath root, but rather in the // directory "/prefix/". If the delegate has such a prefix, we need to take it into account. String prefixedPath = prefix + pathname; if (knownPaths.mightContain(prefixedPath)) { log.debug("path '{}' probably exists, delegating", prefixedPath); // must pass non-prefixed path here because the delegate will also apply the prefix: return delegate.find(clazz, pathname); } else { log.debug("path '{}' does not exist", prefixedPath); return null; } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy