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

net.jawr.web.resource.watcher.ResourceWatcher Maven / Gradle / Ivy

Go to download

Javascript/CSS bundling and compressing tool for java web apps. By using jawr resources are automatically bundled together and optionally minified and gzipped. Jawr provides tag libraries to reference a generated bundle either by id or by using the name of any of its members.

The newest version!
/**
 * Copyright 2015-2016 Ibrahim Chaehoi
 * 
 * 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 net.jawr.web.resource.watcher;

import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

import java.io.IOException;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

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

import net.jawr.web.exception.BundlingProcessException;
import net.jawr.web.resource.bundle.JoinableResourceBundle;
import net.jawr.web.resource.bundle.generator.GeneratorRegistry;
import net.jawr.web.resource.bundle.handler.ResourceBundlesHandler;
import net.jawr.web.resource.bundle.mappings.FilePathMapping;
import net.jawr.web.resource.bundle.mappings.PathMapping;
import net.jawr.web.resource.handler.reader.ResourceReaderHandler;

/**
 * This class defines the resource watcher
 * 
 * @author Ibrahim Chaehoi
 */
public class ResourceWatcher extends Thread {

	/** The logger */
	private static final Logger LOGGER = LoggerFactory.getLogger(ResourceWatcher.class);

	/** The watch service */
	private final WatchService watchService;

	/** The bundles handler */
	private ResourceBundlesHandler bundlesHandler;

	/** The bundles handler */
	private ResourceReaderHandler rsReader;

	/** The keys map */
	private final Map keys;

	/** The flag indicating if the watcher is stopped or not */
	private AtomicBoolean stopWatching = new AtomicBoolean(false);

	/** The map between path and resource bundle */
	private final Map> pathToResourceBundle = new ConcurrentHashMap<>();

	/** The Jawr watch event processor */
	private final JawrWatchEventProcessor jawrEvtProcessor;

	/** The watch events */
	private BlockingQueue watchEvents = new LinkedBlockingQueue<>();

	/**
	 * Constructor
	 * 
	 * @param bundlesHandler
	 *            the bundles handler
	 * @param rsReader
	 *            the resource reader handler
	 */
	public ResourceWatcher(ResourceBundlesHandler bundlesHandler, ResourceReaderHandler rsReader) {
		super(bundlesHandler.getResourceType() + " JawrResourceWatcher");
		this.bundlesHandler = bundlesHandler;
		this.rsReader = rsReader;
		this.keys = new HashMap<>();

		try {
			this.watchService = FileSystems.getDefault().newWatchService();
		} catch (IOException e) {
			throw new BundlingProcessException(e);
		}

		jawrEvtProcessor = new JawrWatchEventProcessor(this, watchEvents);
		jawrEvtProcessor.start();

		Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
			@Override
			public void run() {
				close();
			}
		}));
	}

	/**
	 * Returns the ResourceBundlesHandler
	 * 
	 * @return the ResourceBundlesHandler
	 */
	public ResourceBundlesHandler getBundlesHandler() {
		return bundlesHandler;
	}

	/**
	 * Returns the map which links java.nio.file.Path to a list of PathMapping
	 * 
	 * @return the map which links java.nio.file.Path to a list of PathMapping
	 */
	public Map> getPathToResourceBundle() {
		return pathToResourceBundle;
	}

	/**
	 * Sets the flag indicating if we must stop the resource watching
	 */
	public void stopWatching() {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Stopping resource watching");
		}

		this.stopWatching.set(true);
		jawrEvtProcessor.stopProcessing();
		jawrEvtProcessor.interrupt();
	}

	/**
	 * Initialize the map which links path to asset bundle
	 * 
	 * @param bundles
	 *            the list of bundles
	 * @throws IOException
	 *             if an {@link IOException} occurs
	 */
	public synchronized void initPathToResourceBundleMap(List bundles) throws IOException {

		for (JoinableResourceBundle bundle : bundles) {

			// Remove bundle reference from existing mapping if exists
			removePathMappingFromPathMap(bundle);

			List mappings = bundle.getMappings();
			for (PathMapping pathMapping : mappings) {
				register(pathMapping);
			}

			// Register file path mapping for linked resources
			List fMappings = bundle.getLinkedFilePathMappings();
			for (FilePathMapping fMapping : fMappings) {
				register(fMapping);
			}
		}
	}

	/**
	 * Register a path mapping
	 * 
	 * @param pathMapping
	 *            the path mapping to register
	 * @throws IOException
	 *             if an IOException occurs
	 */
	private void register(PathMapping pathMapping) throws IOException {

		GeneratorRegistry generatorRegistry = bundlesHandler.getConfig().getGeneratorRegistry();
		List mappings = new ArrayList<>();
		if (generatorRegistry.isPathGenerated(pathMapping.getPath())) {
			List genPathMappings = generatorRegistry.getGeneratedPathMappings(pathMapping.getBundle(),
					pathMapping.getPath(), rsReader);
			if (genPathMappings != null) {
				mappings.addAll(genPathMappings);
			} else {
				mappings.add(pathMapping);
			}
		} else {
			mappings.add(pathMapping);
		}

		for (PathMapping pMapping : mappings) {
			String filePath = rsReader.getFilePath(pMapping.getPath());
			registerPathMapping(pMapping, filePath);
		}
	}

	/**
	 * Register a path mapping
	 * 
	 * @param pathMapping
	 *            the path mapping to register
	 * @throws IOException
	 *             if an IOException occurs
	 */
	private void register(FilePathMapping pathMapping) throws IOException {
		registerPathMapping(pathMapping, pathMapping.getPath());
	}

	/**
	 * Register the path mapping
	 * 
	 * @param pathMapping
	 *            the path mapping
	 * @param filePath
	 *            the file path
	 * @throws IOException
	 *             if an IOException occurs
	 */
	private void registerPathMapping(PathMapping pathMapping, String filePath) throws IOException {
		if (filePath != null) {

			Path p = Paths.get(filePath);
			boolean isDir = Files.isDirectory(p);
			if (!isDir) {
				p = p.getParent();
			}

			if (pathMapping.isRecursive()) {
				registerAll(p, Arrays.asList(pathMapping));
			} else {
				register(p, Arrays.asList(pathMapping));
			}
		}
	}

	/**
	 * Removes the path mapping of the bundle given in parameter from map which
	 * links Path to resource bundle
	 * 
	 * @param bundle
	 *            the bundle whose the path mapping should be removed
	 */
	private void removePathMappingFromPathMap(JoinableResourceBundle bundle) {
		for (List pathMappings : pathToResourceBundle.values()) {
			for (Iterator iterator = pathMappings.iterator(); iterator.hasNext();) {
				PathMapping pathMapping = (PathMapping) iterator.next();
				if (pathMapping.getBundle().getName().equals(bundle.getName())) {
					iterator.remove();
				}
			}
		}
	}

	/**
	 * Register the given directory with the WatchService
	 * 
	 * @param dir
	 *            the directory to register
	 * @param pathMapping,
	 *            the list of path mapping associated
	 */
	private void register(Path dir, List pathMapping) throws IOException {

		WatchKey key = dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
		keys.put(key, dir);

		List m = pathToResourceBundle.get(dir);
		if (m == null) {
			m = new ArrayList<>();
			pathToResourceBundle.put(dir, m);
		}
		m.addAll(pathMapping);
	}

	/**
	 * Register the given directory, and all its sub-directories, with the
	 * WatchService.
	 * 
	 * @param pathMapping
	 */
	void registerAll(final Path start, final List pathMappings) throws IOException {
		// register directory and sub-directories
		Files.walkFileTree(start, new SimpleFileVisitor() {
			@Override
			public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
				register(dir, pathMappings);
				return FileVisitResult.CONTINUE;
			}
		});
	}

	/**
	 * Close the watch service
	 */
	private void close() {
		try {
			bundlesHandler = null;
			rsReader = null;
			watchService.close();
		} catch (IOException e) {
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		while (!stopWatching.get()) {
			WatchKey key;
			try {
				key = watchService.take();
			} catch (InterruptedException | ClosedWatchServiceException e) {
				close();
				return;
			}
			if (key != null) {
				Path dir = keys.get(key);
				if (dir == null) {
					LOGGER.warn("WatchKey not recognized!!");
					continue;
				}

				for (WatchEvent event : key.pollEvents()) {
					WatchEvent.Kind kind = event.kind();

					if (kind == OVERFLOW) {
						continue;
					}

					// Context for directory entry event is the file name of
					// entry
					Path path = (Path) event.context();
					Path resolvedPath = ((Path) key.watchable()).resolve(path);

					boolean isDir = Files.isDirectory(resolvedPath, NOFOLLOW_LINKS);
					Path dirPath = null;
					if (isDir) {
						dirPath = ((Path) key.watchable());
					} else {
						dirPath = resolvedPath.getParent();
					}

					JawrWatchEvent evt = new JawrWatchEvent(kind, resolvedPath, dirPath);
					watchEvents.add(evt);
				}

				// reset key and remove from set if directory no longer
				// accessible
				boolean valid = key.reset();
				if (!valid) {
					keys.remove(key);

					// all directories are inaccessible
					if (keys.isEmpty()) {
						break;
					}
				}
			}
		}
		close();
	}

	/**
	 * Returns true if there is no more event to process
	 * 
	 * @return true if there is no more event to process
	 */
	public boolean hasNoEventToProcess() {
		return jawrEvtProcessor.hasNoEventToProcess();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy