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

asset.pipeline.AbstractAssetFile.groovy Maven / Gradle / Ivy

Go to download

JVM Asset Pipeline library for serving static web assets, bundling, minifying, and extensibility for transpiling.

The newest version!
/*
* Copyright 2014 the original author or authors.
*
* 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 asset.pipeline

import asset.pipeline.fs.AssetResolver
import asset.pipeline.processors.JsRequireProcessor
import groovy.transform.CompileStatic
import java.util.regex.Pattern
import java.security.MessageDigest
import java.security.DigestInputStream


/**
* This is the base Asset File specification class. An AssetFile object should extend this abstract base class.
* The AssetFile specification provides information on what processors need to be run on a file.
* A file is matched to an AssetFile specification based on its content type and extension.
* If a file is not matched to a processable AssetFile entity, see the {@link asset.pipeline.GenericAssetFile}.
*
* @author David Estes
*/
// @CompileStatic
abstract class AbstractAssetFile implements AssetFile {

	String path

    /**
     * If this file was loaded as a sub dependency of a parent file this property will contain that baseFile reference
     * This is useful for recalculating base path relative uri patterns within the file.
     */
	AssetFile baseFile
    /**
     * The relevant {@link asset.pipeline.fs.AssetResolver} that was used to find the instance of this file
     */
	AssetResolver sourceResolver

    /**
     * The encoding of the file, typically injected by the {@link DirectiveProcessor}
     * If unspecified, the OS default is assumed
     */
	String encoding


	Pattern directivePattern = null
	Closure inputStreamSource = {} //Implemented by AssetResolver
	byte[] byteCache
	List matchedDirectives = []
	DigestInputStream digestStream
	MessageDigest digest
	private String digestString

    /**
     * Executes the inputStreamSource() closure to fetch a new inputStream object
     * In the case of a standard asset file this result is cached into a Byte Array and also wrapped
     * in a DigestInputStream for efficient md5 digest generation
     * @return InputStream object of files contents (before processing)
     */
	InputStream getInputStream() {
		if(byteCache == null) {
			digest = MessageDigest.getInstance("MD5")
			digestStream = new DigestInputStream((InputStream)inputStreamSource(),digest)
			byteCache = digestStream.getBytes()
		}
		return new ByteArrayInputStream(byteCache)
	}

    /**
     * Returns a HEX encoded byte digest of the file contents (preprocessed)
     * This leverages the DigestStream wrapping the files inputStream for efficient calculation
     * If the stream is not fully read yet it will consume the rest of the stream
     * @return String hexDigest
     */
	public String getByteDigest() {
		if(digestString != null) {
			return digestString
		}
		if(!digestStream || !digest) {
			getInputStream()
		}

		try {
			byte[] buffer = new byte[1024]
			int nRead
			while((nRead = digestStream.read(buffer, 0, buffer.length)) != -1) {
				// noop (just to complete the stream)
			}
		} catch(IOException ioe) {
			// Its ok if the stream is already closed so ignore error
		} finally {
			try { digestStream?.close() } catch(Exception ex) { /*ignore if already closed this reduces open file handles*/ }
		}
		digestString = digest.digest().encodeHex().toString()
		return digestString
	}

    /**
     * Returns the canonicalPath in the context of the AssetResolver file path structure.
     * (this is not the real filesystem canonical path)
     * @return absolute path representation within an AssetResolver context
     */
    String getCanonicalPath() {
        return path
    }

    /**
     * Gets the parent path of the file
     * Behaves similarly to a File.getParent() method
     * @return
     */
	public String getParentPath() {
		String[] pathArgs = path.split("/")
		if(pathArgs.size() == 1) {
			return null
		}
		return (Arrays.copyOfRange(pathArgs,0,pathArgs.size() - 1) as String[]).join("/")
	}

    /**
     * Returns the name of the file without the path elements
     * @return Name of the file
     */
	public String getName() {
		if(path) {
			path.split("/")[-1]	
		}
	}

    /**
     * Returns a Processed String of the files contents
     * This includes a run through of all Processors in the {@link #processors} List.
     * The precompiler object is passed to all processors as well as determines the behavior of the runtime cache
     * manager.
     * @param precompiler reference to the active compiler being used (If NULL development mode is assumed)
     * @param skipCaching defaults to false. Optional flag for forcing a cache skip.
     * @return the final processed contents of the file
     */
	String processedStream(AssetCompiler precompiler, Boolean skipCaching = false) {
		String fileText
		Boolean skipCache = skipCaching ?: precompiler ?: (!processors || processors.size() == 0)
		String cacheKey
		InputStream sourceStream = getInputStream()
		try {
			if(baseFile?.encoding || encoding) {
				fileText = sourceStream?.getText(baseFile?.encoding ? baseFile.encoding : encoding)
			} else {
				fileText = sourceStream?.getText("UTF-8")
			}

			String md5 = null
			if(!skipCache) {
				md5 = getByteDigest()
				Map cache = CacheManager.findCache(path, md5, baseFile?.path)
				if(cache) {
					Map moduleMaps = JsRequireProcessor.commonJsModules.get()
					if(moduleMaps != null) {
						if(cache.requireModules) {
							moduleMaps = cache.requireModules + moduleMaps
							JsRequireProcessor.commonJsModules.set(moduleMaps)
						}
					}
					return cache.processedFileText
				}
			}

			if(processors != null) {
				for(Class processor in processors) {
					Processor processInstance = processor.newInstance(precompiler) as Processor
					fileText = processInstance.process(fileText, this)
				}	
			}
		    

			if(!skipCache) {
				CacheManager.createCache(path, md5, fileText, baseFile?.path)
			}
		} finally {
			try { sourceStream?.close() } catch(Exception ex) { /*doesnt matter this just ensures it closes at the end*/}
		}

		return fileText
	}

    /**
     * String representation of the object defaults to the full path of the file
     * @return
     */
	public String toString() {
		return path
	}

    /**
     * Returns the directive pattern used to perform bundling in the comments of the file
     * it is possible for the pattern to be NULL if this file type does not support it
     * @return multi-line regex Pattern for finding //=require like directives
     */
	public Pattern getDirectivePattern() {
		return this.directivePattern
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy