/* * Copyright 2012 Jason Miller * * 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 jj.resource; import java.io.IOException; import java.net.URI; import java.nio.charset.Charset; import java.time.Clock; import java.util.Collection; import java.util.Collections; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; import javax.inject.Singleton; import jj.logging.LoggedEvent; import org.mozilla.javascript.Scriptable; import jj.event.Listener; import jj.event.Publisher; import jj.event.Subscriber; import org.slf4j.Logger; /** * <p> * basic {@link Resource} behavior. ALL RESOURCES MUST EXTEND THIS. the binders will not * bind classes that do not. * * <p> * Provides all of the interesting hooks into the system, as well * as default implementations of most of the core {@link Resource} * interface, excluding {@link Resource#sha1()} * * @author jason * */ @Subscriber public abstract class AbstractResource<T> implements Resource<T> { @Singleton public static class AbstractResourceDependencies { protected final Clock clock; protected final ResourceConfiguration resourceConfiguration; protected final AbstractResourceEventDemuxer demuxer; protected final Publisher publisher; protected final ResourceFinder resourceFinder; @Inject protected AbstractResourceDependencies( Clock clock, ResourceConfiguration resourceConfiguration, AbstractResourceEventDemuxer demuxer, Publisher publisher, ResourceFinder resourceFinder ) { this.clock = clock; this.resourceConfiguration = resourceConfiguration; this.demuxer = demuxer; this.publisher = publisher; this.resourceFinder = resourceFinder; } } /** * technique used to bundle up all of the dependencies for the base * class, to avoid changing the constructor signature for descendants * all the time. public so it can be overridden, as the script system * does. * * @author jason * */ public static class Dependencies { protected final AbstractResourceDependencies abstractResourceDependencies; protected final ResourceIdentifier<?, ?> identifier; @Inject protected Dependencies( AbstractResourceDependencies abstractResourceDependencies, ResourceIdentifier<?, ?> identifier ) { this.abstractResourceDependencies = abstractResourceDependencies; this.identifier = identifier; } } /** * The key that identifies this resource in the cache */ protected final ResourceIdentifier<? extends Resource<T>, T> identifier; /** * When this resource was created according to the system {@link Clock} */ protected final long creationTime; protected final Publisher publisher; protected final ResourceFinder resourceFinder; /** * The configuration of the resource system */ protected final ResourceConfiguration resourceConfiguration; /** * The resolved settings for this resource */ protected final ResourceSettings settings; private final ConcurrentHashMap<ResourceIdentifier<?, ?>, AbstractResource<?>> dependents = new ConcurrentHashMap<>(2, 0.75f, 2); private final AtomicBoolean alive = new AtomicBoolean(true); @SuppressWarnings("unchecked") private ResourceIdentifier<? extends Resource<T>, T> cast(ResourceIdentifier<?, ?> identifier) { return (ResourceIdentifier<? extends Resource<T>, T>)identifier; } protected AbstractResource(Dependencies dependencies) { this.identifier = cast(dependencies.identifier); this.creationTime = dependencies.abstractResourceDependencies.clock.millis(); this.publisher = dependencies.abstractResourceDependencies.publisher; this.resourceFinder = dependencies.abstractResourceDependencies.resourceFinder; this.resourceConfiguration = dependencies.abstractResourceDependencies.resourceConfiguration; if ((this instanceof FileSystemResource) && this.base().parentInDirectory()) { dependencies.abstractResourceDependencies.demuxer.awaitInitialization(this); } ResourceSettings baseSettings = resourceConfiguration.fileTypeSettings().get(extension()); if (baseSettings == null) { baseSettings = resourceConfiguration.defaultSettings(); } // and specific based on the type/base/name? need to figure out how to represent that tuple. maybe just name? settings = baseSettings; } void resourceLoaded() { // little bit of internal magic here - post initialization of either a // FileResource or a DirectoryResource rooted in Base should be added to // the directory structure. int index = name().lastIndexOf('/'); String parentName = index == -1 ? (name().equals("") ? null : "") : name().substring(0, index); if (parentName != null) { DirectoryResource parent = resourceFinder.findResource(DirectoryResource.class, base(), parentName); // possibly the best bet in this case is to make the missing directory, // which really oughta go into another thread? cause this could get all deadlocky if (parent == null) { throw new AssertionError( "no parent directory " + parentName + " for " + this + ", looked in " + base() + " for " + parentName ); } parent.addDependent(this); } } /** * handles cleaning up dependency tracking when resources are killed. since that only happens from the watch thread, * and only happens when there are modifications to resources, which are both rare events in a sense (and things * that should never matter in a production setting) then having this listener live in every resource in the system * seems like a reasonable thing. Note that the loaded event is demuxed in a separate component because that event * is going to be thrown around like crazy and will cause resource creation to slow as more resources are created. * * @param event the event */ @Listener void on(ResourceKilled event) { dependents.remove(event.identifier()); } /** * Override this implementation to provide a specific extension, used to determine base settings. * AbstractFileResource provides an implementation that suffices in most cases. */ protected String extension() { return ""; } /** * A resource-specific test to indicate if the given resource should be replaced when the * watch system becomes aware of it. any sort of check is allowed at this point. this method is * only used if the resource is still considered "alive" so if the resource has been killed by * some other method (such as dependency propagation) this has no effect * @return true to be replaced * @throws IOException */ @ResourceThread public abstract boolean needsReplacing() throws IOException; /** * return true from this method to be removed instead of reloaded on watch notifications, * or return false to be reloaded in the background and replaced. default is true */ protected boolean removeOnReload() { return true; } @Override public void addDependent(Resource<?> dependent) { assert alive.get() : "cannot accept dependents, i am dead " + toString(); assert dependent != null : "can not depend on null"; assert dependent != this : "can not depend on myself"; assert dependent.alive() : "can not depend on dead resources"; publisher.publish(new DependentAdded(toString(), dependent.toString())); dependents.put(dependent.identifier(), (AbstractResource<?>)dependent); } @ResourceLogger private static class DependentAdded extends LoggedEvent { private final String parentString; private final String dependentString; DependentAdded(String parentString, String dependentString) { this.parentString = parentString; this.dependentString = dependentString; } @Override public void describeTo(Logger logger) { logger.debug("{} is depending on {}", dependentString, parentString); } } /** * retrieve an unmodifiable collection of this resource's dependents */ Collection<AbstractResource<?>> dependents() { return Collections.unmodifiableCollection(dependents.values()); } /** * DO NOT LIKE THIS HERE! * @param to the scriptable to fill with a description */ void describe(Scriptable to) { // put the basics in place, then start calling // downstream to add more? sure // certain stuff can be done here anyway? // ugh. lee. // don't forget to convert to strings if needed // need an id! use the resource key? to.put("id", to, identifier.toString()); to.put("type", to, getClass().getName()); to.put("name", to, name()); to.put("base", to, base().toString()); to.put("uri", to, uri()); to.put("sha1", to, sha1()); to.put("creationTime", to, creationTime); } /** * flag tracking if this resource is considered alive. when this method begins returning * false, the resource has been taken out of service. */ public boolean alive() { return alive.get(); } /** * kills this resource. */ void kill() { if (alive.getAndSet(false)) { publisher.publish(new ResourceKilled(this)); died(); } } /** * internal notification that this resource has died, so some descendant class can * make something of the information. the base implementation does nothing */ protected void died() { // mainly to allow descendants to publish their own death event } @Override public Charset charset() { return null; // by default, resources don't have one at all. might not be text! } @Override public ResourceIdentifier<? extends Resource<T>, T> identifier() { return identifier; } @Override @SuppressWarnings("unchecked") public Class<? extends Resource<T>> type() { return (Class<? extends Resource<T>>)getClass(); } @Override public Location base() { return identifier.base; } @Override public String name() { return identifier.name; } @Override public T creationArg() { return identifier.argument; } @Override public final URI uri() { return null; } public String toString() { return identifier.toString(); } // equals and hashCode are left as Object impls deliberately }