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

io.guise.framework.AbstractGuiseApplication Maven / Gradle / Ivy

There is a newer version: 0.5.3
Show newest version
/*
 * Copyright © 2005-2011 GlobalMentor, Inc. 
 *
 * 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 io.guise.framework;

import java.io.*;
import java.net.URI;
import java.net.URL;
import java.security.Principal;
import java.text.DateFormat;
import java.util.*;

import static java.nio.charset.StandardCharsets.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.Objects.*;

import java.util.concurrent.*;

import javax.mail.Message;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;

import static com.globalmentor.io.Filenames.*;
import static com.globalmentor.io.Files.*;
import static com.globalmentor.java.Threads.*;
import static com.globalmentor.model.Locales.*;
import static com.globalmentor.net.URIs.*;
import static com.globalmentor.time.Calendars.*;
import static io.guise.framework.Guise.*;
import static io.guise.framework.Resources.*;
import static io.guise.framework.theme.Theme.*;

import com.globalmentor.beans.BoundPropertyObject;
import com.globalmentor.collections.DecoratorReadWriteLockMap;
import com.globalmentor.collections.PurgeOnWriteSoftValueHashMap;
import com.globalmentor.io.*;
import com.globalmentor.java.Objects;
import com.globalmentor.log.*;
import com.globalmentor.mail.MailManager;
import com.globalmentor.model.ConfigurationException;
import com.globalmentor.net.URIPath;
import com.globalmentor.text.W3CDateFormat;
import com.globalmentor.util.*;
import com.globalmentor.xml.spec.XML;

import io.csar.*;
import io.guise.framework.component.*;
import io.guise.framework.platform.*;
import io.guise.framework.theme.Theme;
import io.urf.model.SimpleGraphUrfProcessor;
import io.urf.model.UrfObject;
import io.urf.turf.TurfParser;

/**
 * An abstract base class for a Guise application. This implementation only works with Guise containers that descend from {@link AbstractGuiseContainer}.
 * @author Garret Wilson
 */
public abstract class AbstractGuiseApplication extends BoundPropertyObject implements GuiseApplication {

	/** Whether this application is in debug mode. */
	private boolean debug = false;

	@Override
	public boolean isDebug() {
		return debug;
	}

	@Override
	public void setDebug(final boolean debug) {
		this.debug = debug;
	}

	/**
	 * I/O for loading resources.
	 * @implSpec This temporary implementation loads a map stored as the root resource in a TURF file.
	 * @implSpec This implementation does not support saving TURF resources.
	 */
	private static final IO> resourcesIO = new IO<>() {
		@SuppressWarnings("unchecked")
		@Override
		public Map read(final InputStream inputStream, final URI baseURI) throws IOException {
			return new TurfParser<>(new SimpleGraphUrfProcessor()).parseDocument(inputStream).stream().findFirst().filter(Map.class::isInstance).map(Map.class::cast)
					.orElseThrow(() -> new IOException("TURF resources did not contain map at root."));
		}

		@Override
		public void write(final OutputStream outputStream, final URI baseURI, final Map object) throws IOException {
			throw new UnsupportedOperationException();
		}
	};

	/** @return I/O for loading resources. */
	protected IO> getResourcesIO() {
		return resourcesIO;
	}

	/**
	 * I/O for loading themes.
	 * @implSpec This temporary implementation loads a legacy Guise theme and stores it as the theme description.
	 * @implSpec This implementation does not support saving themes resources.
	 */
	private static final IO themeIO = new IO<>() {
		@SuppressWarnings("unchecked")
		@Override
		public Theme read(final InputStream inputStream, final URI baseURI) throws IOException {
			final UrfObject themeDescription = new TurfParser<>(new SimpleGraphUrfProcessor()).parseDocument(inputStream).stream().findFirst()
					.filter(UrfObject.class::isInstance).map(UrfObject.class::cast).orElseThrow(() -> new IOException("Theme missing main theme description."));
			return new Theme(baseURI, themeDescription);
		}

		@Override
		public void write(final OutputStream outputStream, final URI baseURI, final Theme object) throws IOException {
			throw new UnsupportedOperationException();
		}
	};

	/** @return I/O for loading themes. */
	public IO getThemeIO() {
		return themeIO;
	}

	/** The manager of configurations for this session. */
	private final ConcernRegistry configurationManager = new DefaultConcernRegistry();

	/**
	 * Sets the given configurations, associating them with their respective classes.
	 * @param configurations The configurations to set.
	 */
	protected void setConfigurations(final Concern... configurations) {
		configurationManager.registerConcerns(configurations);
	}

	/**
	 * Sets the given configuration, associating it with its class.
	 * @param  The type of configuration being set.
	 * @param configuration The configuration to set.
	 * @return The configuration previously associated with the same class, or null if there was no previous configuration for that class.
	 * @throws NullPointerException if the given configuration is null.
	 */
	protected  Optional setConfiguration(final C configuration) {
		return configurationManager.registerConcern(configuration);
	}

	/**
	 * Sets the given configuration.
	 * @param  The type of configuration being set.
	 * @param configurationClass The class with which to associate the configuration.
	 * @param configuration The configuration to set.
	 * @return The configuration previously associated with the given class, or null if there was no previous configuration for that class.
	 */
	protected  Optional setConfiguration(final Class configurationClass, final C configuration) {
		return configurationManager.registerConcern(configurationClass, configuration);
	}

	@Override
	public  Optional findConcern(final Class configurationClass) {
		return configurationManager.findConcern(configurationClass);
	}

	/**
	 * Removes a configuration of the given type. If no configuration is associated with the specified type, no action occurs.
	 * @param  The type of configuration being removed.
	 * @param configurationClass The class with which the configuration is associated.
	 * @return The configuration previously associated with the given class, or null if there was no previous configuration for that class.
	 */
	protected  Optional removeConfiguration(final Class configurationClass) {
		return configurationManager.unregisterConcern(configurationClass);
	}

	/** The application identifier URI. */
	private URI uri;

	/**
	 * {@inheritDoc}
	 * 

* This URI may be but is not guaranteed to be the URI at which the application can be accessed. *

*/ @Override public URI getURI() { return uri; } /** The Guise container into which this application is installed, or null if the application is not yet installed. */ private AbstractGuiseContainer container = null; @Override public GuiseContainer getContainer() { return container; } /** The application local environment. */ private Environment environment; @Override public Environment getEnvironment() { return environment; } @Override public void setEnvironment(final Environment newEnvironment) { if(!Objects.equals(environment, newEnvironment)) { //if the value is really changing (compare their values, rather than identity) final Environment oldEnvironment = environment; //get the old value environment = requireNonNull(newEnvironment, "Guise session environment cannot be null."); //actually change the value firePropertyChange(ENVIRONMENT_PROPERTY, oldEnvironment, newEnvironment); //indicate that the value changed } } /** The properties of the mail manager, or null if the mail properties have not been configured. */ private Map mailProperties = null; @Override public Map getMailProperties() { if(isInstalled() && mailProperties == null) { //if the application has been installed but the repositories repository has not yet been set throw new ConfigurationException("Repositories repository has not been configured."); } return mailProperties; //return the repository for user repositories } @Override public void setMailProperties(final Map mailProperties) { checkNotInstalled(); //make sure the application has not been installed this.mailProperties = unmodifiableMap(requireNonNull(mailProperties, "Repository cannot be null.")); } /** The mail manager, or null if the application is not installed or there is no mail defined for this application. */ private MailManager mailManager = null; @Override public Session getMailSession() { checkInstalled(); //make sure the application has been installed (which will set the mail manager if configured) final MailManager mailManager = this.mailManager; //get the mail manager if(mailManager == null) { //if we don't have a mail manager, mail hasn't been configured throw new ConfigurationException("Mail has not been configured for the application."); } return mailManager.getSession(); //return the mail manager's session } @Override public Queue getMailSendQueue() { checkInstalled(); //make sure the application has been installed (which will set the mail manager if configured) final MailManager mailManager = this.mailManager; //get the mail manager if(mailManager == null) { //if we don't have a mail manager, mail hasn't been configured throw new ConfigurationException("Mail has not been configured for the application."); } return mailManager.getSendQueue(); //return the mail manager's send queue } /** Whether the application applies themes. */ private boolean themed = true; @Override public boolean isThemed() { return themed; } @Override public void setThemed(final boolean newThemed) { if(themed != newThemed) { //if the value is really changing final boolean oldThemed = themed; //get the current value themed = newThemed; //update the value firePropertyChange(THEMED_PROPERTY, Boolean.valueOf(oldThemed), Boolean.valueOf(newThemed)); } } /** * {@inheritDoc} *

* This version returns the relative path to the application unmodified. *

*/ @Override public URIPath getNavigationPath(final URI depictionURI) { return relativizeURI(depictionURI); //by default the navigation path and the depiction path are the same } /** * {@inheritDoc} *

* This implementation delegates to {@link #getDepictionURI(URI, URI)}. *

*/ @Override public final URI getDepictionURI(final URI depictionRootURI, final URIPath navigationPath) { return getDepictionURI(depictionRootURI, navigationPath.toURI()); } /** * {@inheritDoc} *

* This version resolves the navigation URI to the base path, but otherwise returns the navigation URI unmodified. *

*/ @Override public URI getDepictionURI(final URI depictionRootURI, final URI navigationURI) { return getBasePath().resolve(navigationURI); //by default the navigation URI and the depiction URI are the same, except that the depiction URI is resolved to the application base path } /** * {@inheritDoc} *

* This version creates and returns a default session. *

*/ @Override public GuiseSession createSession(final Platform platform) { return new DefaultGuiseSession(this, platform); //create a new default Guise session } /** The concurrent map of Guise session info keyed to Guise sessions. */ private final Map guiseSessionInfoMap = new ConcurrentHashMap(); /** The concurrent map of Guise sessions keyed to UUIDs. */ private final Map uuidGuiseSessionMap = new ConcurrentHashMap(new HashMap()); @Override public void registerSession(final GuiseSession guiseSession) { if(guiseSessionInfoMap.containsKey(guiseSession)) { //if we already have info for this session (there is a race condition here that would allow a session to be registered twice, but that would only prevent error-checking for conditions that logically shouldn't happen anyway, so it's not worth the synchronization overhead to prevent) throw new IllegalStateException("Guise session " + guiseSession + " already registered with Guise application " + this); } uuidGuiseSessionMap.put(guiseSession.getUUID(), guiseSession); //associate the Guise session with its UUID guiseSessionInfoMap.put(guiseSession, new GuiseSessionInfo(guiseSession)); //add new Guise session information } @Override public void unregisterSession(final GuiseSession guiseSession) { final GuiseSessionInfo guiseSessionInfo = guiseSessionInfoMap.remove(guiseSession); //remove the info for this Guise session if(guiseSessionInfo == null) { //if there was no Guise session registered throw new IllegalStateException("Guise session " + guiseSession + " not registered with Guise application " + this); } uuidGuiseSessionMap.remove(guiseSession.getUUID()); //remove the Guise session from the UUID map final List tempFileInfos = guiseSessionInfo.getTempFileInfos(); //get the temp files registered with this application synchronized(tempFileInfos) { //synchronize on the temp file infos for completeness (although no other threads should be accessing the list at this point for(final TempFileInfo tempFileInfo : tempFileInfos) { //for each temporary file final File tempFile = tempFileInfo.getTempFile(); //get the temporary file if(tempFile.exists()) { //if this file still exists if(!tempFile.delete()) { //delete the temporary file; if the file could not be deleted Log.warn("Could not delete temporary file " + tempFile + " associated with Guise session " + guiseSession); } } } } } @Override public GuiseSession getSession(final UUID uuid) { return uuidGuiseSessionMap.get(requireNonNull(uuid, "UUID cannot be null.")); //return the Guise session, if any, associted with the given UUID } /** * {@inheritDoc} *

* This implementation returns a default application frame. *

*/ public ApplicationFrame createApplicationFrame() { return new DefaultApplicationFrame(); //return an instance of the default application frame } /** * The base URI where the application is installed, or null if no application base URI has been specified and the application is not yet * installed. */ private URI baseURI = null; /** * Reports the base URI where the application is installed. The base URI is an absolute URI that ends with the base path, which ends with a slash ('/'). * @return The base URI representing the Guise application, or null if no application base URI has been specified and the application is not yet * installed. * @see #getBasePath() */ public URI getBaseURI() { return baseURI; } /** * Sets the base URI of the application. The base path is also set. * @param baseURI The base URI where the application is installed, which must be an absolute URI with an absolute collection path (e.g. * http://www.example.com/path/). * @throws NullPointerException if the given base URI is null. * @throws IllegalArgumentException if the given URI is not absolute or the path of which is not absolute or not a collection. * @throws IllegalStateException if the application is already installed. * @see #getBasePath() */ public void setBaseURI(final URI baseURI) { checkNotInstalled(); this.basePath = findURIPath(checkAbsolute(baseURI)).map(URIPath::checkAbsolute).map(URIPath::checkCollection) .orElseThrow(() -> new IllegalArgumentException(String.format("Base URI <%s> has no path.", baseURI))); this.baseURI = baseURI; } /** The base path of the application, or null if no application base URI has been specified and the application is not yet installed. */ private URIPath basePath = null; @Override public URIPath getBasePath() { return basePath; } /** The home directory shared by all sessions of this application. */ private File homeDirectory = null; @Override public File getHomeDirectory() { checkInstalled(); //make sure the application has been installed (which will set the home directory) assert homeDirectory != null : "Home directory is null even though application is installed."; return homeDirectory; //return the home directory; } /** The log directory shared by all sessions of this application. */ private File logDirectory = null; @Override public File getLogDirectory() { checkInstalled(); //make sure the application has been installed (which will set the log directory) assert logDirectory != null : "Log directory is null even though application is installed."; return logDirectory; //return the log directory; } /** The temporary directory shared by all sessions of this application. */ private File tempDirectory = null; @Override public File getTempDirectory() { checkInstalled(); //make sure the application has been installed (which will set the temporary directory) assert tempDirectory != null : "Temporary directory is null even though application is installed."; return tempDirectory; //return the temporary directory; } /** The synchronized map of log writer infos keyed to log base filenames. */ private final Map baseNameLogWriterInfoMap = synchronizedMap(new HashMap()); /** * Retrieves a writer suitable for recording log information for the application. This implementation returns an asynchronous writer that does not block for * information to be written when receiving information. The given base filename is appended with a representation of the current date. If a log writer for * the same date is available, it is returned; otherwise, a new log writer is created. If the current date is a different day than that used for the current * log writer for a given base filename, a new writer is created for the current date. * @param baseFilename The base filename (e.g. "base.log") that will be used in generating a log file for the current date (e.g. "base 2003-02-01.log"). * @param initializer The encapsulation of any initialization that should be performed on any new writer, or null if no initialization is * requested. * @param uninitializer The encapsulation of any uninitialization that should be performed on any new writer, or null if no uninitialization is * requested. * @see GuiseApplication#getLogDirectory() */ public Writer getLogWriter(final String baseFilename, /*TODO fix final CalendarResolution calendarResolution, */final IOOperation initializer, final IOOperation uninitializer) throws IOException { synchronized(baseNameLogWriterInfoMap) { //don't allow the map to be used while we look up a writer LogWriterInfo logWriterInfo = baseNameLogWriterInfoMap.get(baseFilename); //get the log writer info if(logWriterInfo == null || System.currentTimeMillis() >= logWriterInfo.getExpireTime()) { //if there is no log writer information, or this log writer has expired if(logWriterInfo != null && uninitializer != null) { //if we have an old log writer and something to uninitialize it with uninitializer.perform(logWriterInfo.getWriter()); //uninitialize the old log writer } final File logDirectory = getLogDirectory(); //get the application log directory final DateFormat logFilenameDateFormat = new W3CDateFormat(W3CDateFormat.Style.DATE); //create a formatter for the log filename final String logFilename = appendBase(baseFilename, "-" + logFilenameDateFormat.format(new Date())); //create a filename in the form "baseFilename-date.ext" final File logFile = new File(logDirectory, logFilename); //create a log file object //TODO add a way to let the initializer know if this is a new log file or just a new writer final boolean isNewLogFile=!logFile.exists(); //see if this is a new log file try { final Writer writer = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(logFile, true)), UTF_8); //create a buffered UTF-8 log writer, appending if the file already exists final ThreadGroup guiseSessionThreadGroup = Guise.getInstance().getGuiseSessionThreadGroup(Thread.currentThread()); //get the Guise session thread group assert guiseSessionThreadGroup != null : "Expected to be inside a Guise session thread group when application log writer was requested."; final AsynchronousWriterRunnable asynchronousWriterRunnable = new AsynchronousWriterRunnable(writer); //create a runnable for creating the new asynchronous writer call(guiseSessionThreadGroup.getParent(), asynchronousWriterRunnable); //create an asynchronous writer in the thread group above the Guise session thread group, because the asynchronous writer's thread will live past this session's thread group final Writer logWriter = asynchronousWriterRunnable.getWriter(); //get the asynchronous writer that was created assert logWriter != null : "Asynchronous writer runnable did not create asynchronous writer as expected."; //TODO del when works final Writer logWriter=new AsynchronousWriter(new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(logFile, true)), UTF_8)); //create an asynchronous, buffered UTF-8 log writer, appending if the file already exists final Calendar calendar = Calendar.getInstance(); //create a new default calendar for the current date and time calendar.add(Calendar.DAY_OF_YEAR, 1); //go to the next day to find out when this writer should expire clearTime(calendar); //clear the calendar's time, consentrating on just the date (i.e. set the writer to expire at midnight) //TODO del; testing logWriterInfo=new LogWriterInfo(logWriter, System.currentTimeMillis()+10000); logWriterInfo = new LogWriterInfo(logWriter, calendar.getTimeInMillis()); //encapsulate the writer and its expiration time baseNameLogWriterInfoMap.put(baseFilename, logWriterInfo); //replace the old writer with our new writer; the old writer will eventually be garbage collected and, under normal conditions, will close when it is finalized if(initializer != null) { //if we have something to initialize the new log writer with initializer.perform(logWriter); //initialize the new log writer } } catch(final UnsupportedEncodingException unsupportedEncodingException) { //we should always support UTF-8 throw new AssertionError(unsupportedEncodingException); } } return logWriterInfo.getWriter(); //return the writer } } /** * The runnable whose sole function is to create an asynchronous writer. * @author Garret Wilson */ private static class AsynchronousWriterRunnable implements Runnable //create a runnable for creating the new asynchronous writer { /** The writer to be decorated. */ private final Writer decoratedWriter; /** The writer that was created, or null if the writer has not yet been created. */ private Writer writer = null; /** @return The writer that was created, or null if the writer has not yet been created. */ public Writer getWriter() { return writer; } /** * Constructs the class with a writer to decorate. * @param decoratedWriter The writer to decorate with an asynchronous writer. * @throws NullPointerException if the given writer is null. */ public AsynchronousWriterRunnable(final Writer decoratedWriter) { this.decoratedWriter = requireNonNull(decoratedWriter, "Decorated writer cannot be null."); } @Override public void run() { writer = new AsynchronousWriter(decoratedWriter); //create an asynchronous writer based upon the decorated writer } }; /** The hash code, which we'll update after installation. The value is only used after installation, so the initial value is irrelevant. */ //TODO del if not needed private int hashCode=-1; @Override public boolean isInstalled() { return getContainer() != null && getBasePath() != null; } @Override public void checkInstalled() { if(!isInstalled()) { //if the application is not installed throw new IllegalStateException("Application not installed."); } } /** * Checks to ensure that this application is not installed. * @throws IllegalStateException if the application is installed. * @see #isInstalled() */ public void checkNotInstalled() { if(isInstalled()) { //if the application is installed throw new IllegalStateException("Application already installed."); } } /** * {@inheritDoc} *

* This implementation configures logging. Mail is enabled if mail properties have been configured using {@link #setMailProperties(Map)}. *

*/ @Override public void install(final AbstractGuiseContainer container, final URI baseURI, final File homeDirectory, final File logDirectory, final File tempDirectory) { requireNonNull(container, "Container cannot be null"); checkNotInstalled(); Log.info("Installing application", this, "at URI", baseURI); setBaseURI(baseURI); //set the base URI this.container = container; //store the container this.homeDirectory = requireNonNull(homeDirectory, "Home directory cannot be null."); this.logDirectory = requireNonNull(logDirectory, "Log directory cannot be null."); this.tempDirectory = requireNonNull(tempDirectory, "Temporary directory cannot be null."); final DateFormat logFilenameDateFormat = new W3CDateFormat(W3CDateFormat.Style.DATE); //create a formatter for the log filename final String logFilename = addExtension("application-" + logFilenameDateFormat.format(new Date()), Log.FILENAME_EXTENSION); //create a filename in the form "application-YYYY-MM-DD.log" final File logFile = new File(logDirectory, logFilename); //determine the log file for this application TODO create a custom log configuration that will use rolling log files logConfiguration.setFile(logFile); //set the log file for our log configuration logConfiguration.setStandardOutput(isDebug()); //if we are debugging, turn on logging to the standard output setConfiguration(LogConfiguration.class, logConfiguration); //configure logging for this application logConfiguration.getLogger().info("Installing application", this); //inform the application-specific log if(mailProperties != null) { //if mail properties have been configured try { mailManager = new MailManager(mailProperties); //create a new mail manager from the properties } catch(final NoSuchProviderException noSuchProviderException) { //if a provider couldn't be found throw new ConfigurationException(noSuchProviderException); //indicate that there was a problem configuring the application TODO use a better error; create a ConfigurationStateException } } else { //if there are no mail properties Log.warn("Mail properties not configured."); //warn that mail isn't configured } } @Override public void uninstall(final GuiseContainer container) { Log.info("Uninstalling application", this, "from URI", baseURI); logConfiguration.getLogger().info("Uninstalling application", this); //inform the application-specific log if(this.container == null) { //if we don't have a container throw new IllegalStateException("Application not installed."); } if(this.container != container) { //if we're installed into a different container throw new IllegalStateException("Application installed into different container."); } mailManager.getSendThread().interrupt(); //interrupt the mail manager's send thread mailManager = null; //release the mail manager synchronized(baseNameLogWriterInfoMap) { //don't allow the map to be used while we look up a writer for(final LogWriterInfo logWriterInfo : baseNameLogWriterInfoMap.values()) { //for each log writer info try { logWriterInfo.getWriter().close(); //close this writer } catch(final IOException ioException) { //if there is an error closing the writer Log.warn(ioException); //log the warning and continue } } baseNameLogWriterInfoMap.clear(); //remove all log writer information } logConfiguration.dispose(); //dispose of our main application log configuration, closing files as necessary this.container = null; //release the container this.basePath = null; //remove the base path } /** The identifier for logging to a Data Collection System such as WebTrends, or null if no DCS ID is known. */ private String dcsID = null; @Override public String getDCSID() { return dcsID; } @Override public void setDCSID(final String dcsID) { this.dcsID = dcsID; } /** * The read-only non-empty list of locales supported by the application, with the first locale the default used if a new session cannot determine the users's * preferred locale. */ private List locales; @Override public List getLocales() { return locales; } @Override public void setLocales(final List newLocales) { requireNonNull(newLocales, "Guise application locales cannot be null."); //make sure the list is not null if(newLocales.isEmpty()) { //if there are no locales given throw new IllegalArgumentException("Guise application must support at least one locale."); } if(!locales.equals(newLocales)) { //if the value is really changing final List oldLocales = locales; //get the old value locales = unmodifiableList(new ArrayList(newLocales)); //create an unmodifiable copy of the locales firePropertyChange(LOCALES_PROPERTY, oldLocales, locales); //indicate that the value changed } } /** The thread-safe set of locales supported by this application. */ private final Set supportedLocales = new CopyOnWriteArraySet(); /** @return The thread-safe set of locales supported by this application. */ public Set getSupportedLocales() { return supportedLocales; } /** The base name of the resource bundle to use for this application, or null if no custom resource bundle is specified for this application. */ private String resourceBundleBaseName = null; @Override public String getResourceBundleBaseName() { return resourceBundleBaseName; } @Override public void setResourceBundleBaseName(final String newResourceBundleBaseName) { if(!Objects.equals(resourceBundleBaseName, newResourceBundleBaseName)) { //if the value is really changing final String oldResourceBundleBaseName = resourceBundleBaseName; //get the old value resourceBundleBaseName = newResourceBundleBaseName; //actually change the value firePropertyChange(RESOURCE_BUNDLE_BASE_NAME_PROPERTY, oldResourceBundleBaseName, newResourceBundleBaseName); //indicate that the value changed } } /** The absolute or application-relative URI of the application style, or null if the default style should be used. */ private URI styleURI; /** @return The absolute or application-relative URI of the application style, or null if the default style should be used. */ public URI getStyleURI() { return styleURI; } @Override public void setStyleURI(final URI newStyle) { if(!Objects.equals(styleURI, newStyle)) { //if the value is really changing (compare their values, rather than identity) final URI oldStyle = styleURI; //get the old value styleURI = newStyle; //actually change the value firePropertyChange(STYLE_URI_PROPERTY, oldStyle, newStyle); //indicate that the value changed } } /** The URI of the application theme, to be resolved against the application base path. */ private URI themeURI = GUISE_BASIC_THEME_PATH.toURI(); /** @return The URI of the application theme, to be resolved against the application base path. */ public URI getThemeURI() { return themeURI; } @Override public void setThemeURI(final URI newThemeURI) { if(!Objects.equals(themeURI, newThemeURI)) { //if the value is really changing final URI oldThemeURI = themeURI; //get the old value themeURI = requireNonNull(newThemeURI, "Theme URI cannot be null."); //actually change the value firePropertyChange(THEME_URI_PROPERTY, oldThemeURI, newThemeURI); //indicate that the value changed } } /** The log configuration for this application. */ private final DefaultLogConfiguration logConfiguration; @Override public void setLogLevel(final Log.Level level) { logConfiguration.setLevel(level); } /** * URI constructor. The URI identifier may or may not be the URI at which the application can be accessed This implementation sets the locale to the JVM * default. * @param uri The URI for the application, or null if there is no identifier is not known. */ public AbstractGuiseApplication(final URI uri) { this.uri = uri; //set the URI locales = unmodifiableList(asList(Locale.getDefault())); //create an unmodifiable list of locales including only the default locale of the JVM this.environment = new DefaultEnvironment(); //create a default environment this.logConfiguration = new DefaultLogConfiguration(); //create a default log configuration, which we'll initialize with a log file later this.logConfiguration.setStandardOutput(false); //turn off logging to the standard output; we may turn it on later } /** The concurrent list of destinations which have path patterns specified. */ private final List pathPatternDestinations = new CopyOnWriteArrayList(); /** The concurrent map of destinations associated with application context-relative paths. */ private final Map pathDestinationMap = new ConcurrentHashMap(); @Override public void addDestination(final Destination destination) { addDestination(destination, false); //add this destination with subordinate priority } @Override public void addDestination(final Destination destination, final boolean priority) { final URIPath path = destination.getPath(); //get the destination's path, if there is one if(path != null) { //if this destination has a path pathDestinationMap.put(path, destination); //associate the destination with the path } else { //if the destination has no path assert destination.getPathPattern() != null : "Destination should have had either a path or a path pattern."; if(priority) { //if this destination has priority pathPatternDestinations.add(0, destination); //add this destination to the beginning of the list of destinations with path patterns } else { //if this destination should not have priority pathPatternDestinations.add(destination); //add this destination to the list of destinations with path patterns } } } @Override public void setDestinations(final List destinations) { pathDestinationMap.clear(); //clear the map of path/destination associations pathPatternDestinations.clear(); //clear the list of path pattern destinations for(final Destination destination : destinations) { //for each destination addDestination(destination); //add this destination } } @Override public Optional getDestination(final URIPath path) { path.checkRelative(); //make sure the path is relative Destination destination = pathDestinationMap.get(path); //get the destination associated with this path, if any if(destination == null) { //if there is no destination for this exact path for(final Destination pathPatternDestination : pathPatternDestinations) { //look at all the destinations with path patterns if(pathPatternDestination.getPathPattern().matcher(path.toString()).matches()) { //if this destination's pattern matches the given path destination = pathPatternDestination; //use this destination break; //stop looking at destinations with path patterns } } } return Optional.ofNullable(destination); //return the destination we found, if any } @Override public Iterable getDestinations() { final List destinations = new ArrayList(pathDestinationMap.size() + pathPatternDestinations.size()); //create a list large enough to hold all the path-mapped destinations and all the destinations with path patterns destinations.addAll(pathDestinationMap.values()); //add the path destinations destinations.addAll(pathPatternDestinations); //add the path pattern destinations return destinations; //return our constructed list of all available destinations } @Override public boolean hasDestination(final URIPath path) { path.checkRelative(); //make sure the path is relative if(pathDestinationMap.containsKey(path)) { //see if there is a destination associated with this navigation path return true; //show that we found an exact match } for(final Destination pathPatternDestination : pathPatternDestinations) { //look at all the destinations with path patterns if(pathPatternDestination.getPathPattern().matcher(path.toString()).matches()) { //if this destination's pattern matches the given path return true; //show that we found a pattern match } } return false; //indicate we couldn't find an exact match or a pattern match } @Override public URIPath resolvePath(final URIPath path) { return getBasePath().resolve(requireNonNull(path, "Path cannot be null.")); //resolve the given path against the base path } @Override public URI resolveURI(URI uri) { if(PATH_SCHEME.equals(uri.getScheme())) { //if this ia a path: URI uri = getPathURIPath(uri).toURI(); //get the URI form of the raw path of the path URI } return getBasePath().resolve(requireNonNull(uri, "URI cannot be null.")); //resolve the given URI against the base path } @Override public URIPath relativizePath(final URIPath path) { return getBasePath().relativizeChildPath(path); //get the path relative to the application path } @Override public URIPath relativizeURI(final URI uri) { return relativizePath(URIPath.of(uri.getRawPath())); //relativize the path of the URI TODO make sure the URI is from the correct domain } @Override public String getLocaleResourcePath(final String resourceBasePath, final Locale locale) { /*TODO refactor into common method final String relativeApplicationPath=relativizePath(getContainer().getBasePath(), getBasePath()); //get the application path relative to the container path final String contextRelativeResourcebasePath=relativeApplicationPath+resourceBasePath; //get the base path relative to the container */ for(int depth = 3; depth >= 0; --depth) { //try different locales, starting with the most specific final String resourceCandidatePath = getLocaleCandidatePath(resourceBasePath, locale, depth); //get a candidate path for the resource at this locale depth if(resourceCandidatePath != null && hasResource(resourceCandidatePath)) { //if we can generate a candidate path for the locale at this depth, and we have that resource return resourceCandidatePath; //return this candidate path } } return null; //indicate that we were unable to find a resource path for the given locale } /** * {@inheritDoc} *

* This implementation uses package access to delegate to {@link AbstractGuiseContainer#hasResource(String)}. *

*/ @Override public boolean hasResource(final String resourcePath) { checkInstalled(); //make sure we're installed final URIPath relativeApplicationPath = getContainer().getBasePath().relativizeChildPath(getBasePath()); //get the application path relative to the container path return container.hasResource(relativeApplicationPath.toString() + resourcePath); //delegate to the container } /** * {@inheritDoc} *

* This implementation uses package access to delegate to {@link AbstractGuiseContainer#getResourceInputStream(String)}. *

*/ @Override public InputStream getResourceInputStream(final String resourcePath) { checkInstalled(); //make sure we're installed final URIPath relativeApplicationPath = getContainer().getBasePath().relativizeChildPath(getBasePath()); //get the application path relative to the container path return container.getResourceInputStream(relativeApplicationPath.toString() + resourcePath); //delegate to the container } @Override public InputStream getInputStream(final URI uri) throws IOException { //TODO check for the resource: URI scheme /*TODO decide if we really want this If this is a resource: URI representing a private resource, this method delegates to {@link #getResourceInputStream(String)}. if(RESOURCE_SCHEME.equals(uri.getScheme())) { //if this is a resource reference URI return getResourceInputStream(uri.get) } */ //TODO del Log.trace("getting input stream to URI", uri); final GuiseContainer container = getContainer(); //get the container final URI resolvedURI = resolveURI(uri); //resolve the URI to the application //TODO del Log.trace("resolved URI:", resolvedURI); final URI absoluteResolvedURI = resolve(container.getBaseURI(), resolvedURI); //resolve the URI against the container base URI //TODO del Log.trace("absolute resolved URI:", absoluteResolvedURI); //check for Guise public resources final URI publicResourcesBaseURI = resolve(container.getBaseURI(), getBasePath().resolve(GUISE_ASSETS_BASE_PATH).toURI()); //get the base URI of Guise public resources // TODO del Log.trace("publicResourcesBaseURI:", publicResourcesBaseURI); final URI publicResourceRelativeURI = publicResourcesBaseURI.relativize(absoluteResolvedURI); //see if the absolute URI is in the application public path // TODO del Log.trace("resourceURI:", resourceURI); if(!publicResourceRelativeURI.isAbsolute()) { //if the URI is relative to the application's public resources return Guise.getInstance().getAssetInputStream(GUISE_ASSETS_BASE_KEY + publicResourceRelativeURI.getPath()); //return an input stream to the resource directly, rather than going through the server } //check for Guise public temp resources final URI publicTempBaseURI = resolve(container.getBaseURI(), getBasePath().resolve(GUISE_ASSETS_TEMP_BASE_PATH).toURI()); //get the base URI of Guise public temporary resources // TODO del Log.trace("publicResourcesBaseURI:", publicResourcesBaseURI); final URI publicTempRelativeURI = publicTempBaseURI.relativize(absoluteResolvedURI); //see if the absolute URI is in the application public temporary path // TODO del Log.trace("resourceURI:", resourceURI); if(!publicTempRelativeURI.isAbsolute()) { //if the URI is relative to the application's public temp resources final String filename = publicTempRelativeURI.getRawPath(); //get the filename of the temp file final TempFileInfo tempFileInfo = filenameTempFileInfoMap.get(filename); //get the info for this temp file if(tempFileInfo != null) { //if we found the temporary file final File tempFile = tempFileInfo.getTempFile(); //get the temp file if(tempFile.exists()) { //if the temp file exists final GuiseSession restrictionSession = tempFileInfo.getRestrictionSession(); //get the restriction session, if any if(restrictionSession != null) { //if this file is restricted to a Guise session if(!restrictionSession.equals(Guise.getInstance().getGuiseSession())) { //compare the restricted session with the current Guise session, throwing an exception if there is Guise session throw new IllegalStateException("Guise public temporary resource " + uri + " cannot be accessed from the current Guise session."); } } return new FileInputStream(tempFileInfo.getTempFile()); //create an input stream to the temp file } } return null; //if there is no such temp file info, or the temp file does not exist, indicate that the temporary file does not exist } return container.getInputStream(resolvedURI); //resolve the URI to the application and delegate to the container } @Override public InputStream getInputStream(final URIPath path) throws IOException { return getInputStream(path.toURI()); //create a URI, verifying that it is a path, and return an input stream to the URI /*TODO fix; reverse delegation final URIPath basePath=getBasePath(); //get the application base path final URIPath resolvedPath=basePath.resolve(path); //resolve the path against the application base URI final URIPath publicTempBasePath=basePath.resolve(GUISE_TEMP_BASE_PATH); //get the base path of the Guise temp resources final URIPath publicTempRelativePath=publicTempBasePath.relativize(resolvedPath); //see if the resolved path is in the application public path if(!publicTempRelativePath.isAbsolute()) { //if the path is relative to the application's temp resources final String filename=publicTempRelativePath.toString(); //get the filename of the temp file; all temporary files are stored in the same directory, so if this is not a plain filename no file will be found final TempFileInfo tempFileInfo=filenameTempFileInfoMap.get(filename); //get the info for this temp file if(tempFileInfo!=null) { //if we found the temporary file final GuiseSession restrictionSession=tempFileInfo.getRestrictionSession(); //get the restriction session, if any if(restrictionSession!=null) { //if this file is restricted to a Guise session if(!restrictionSession.equals(Guise.getInstance().getGuiseSession())) { //compare the restricted session with the current Guise session, throwing an exception if there is Guise session throw new IllegalStateException("Guise public temporary resource "+path+" cannot be accessed from the current Guise session."); } } return new FileOutputStream(tempFileInfo.getTempFile()); //create an output stream to the temp file } else { //if there is no such temp file throw new FileNotFoundException("No such Guise public temp file: "+filename); } } else { //if the URI is not an application-relative public temporary resource URI throw new UnsupportedOperationException("Access to non-temporary resource URI "+path+" is unsupported."); //TODO fix } */ } @Override public OutputStream getOutputStream(final URI uri) throws IOException { //TODO del Log.trace("getting input stream to URI", uri); final GuiseContainer container = getContainer(); //get the container final URI resolvedURI = resolveURI(uri); //resolve the URI to the application // TODO del Log.trace("resolved URI:", resolvedURI); final URI absoluteResolvedURI = resolve(container.getBaseURI(), resolvedURI); //resolve the URI against the container base URI // TODO del Log.trace("absolute resolved URI:", absoluteResolvedURI); final URI publicTempBaseURI = resolve(container.getBaseURI(), getBasePath().resolve(GUISE_ASSETS_TEMP_BASE_PATH).toURI()); //get the base URI of the Guise temp resources // TODO del Log.trace("publicResourcesBaseURI:", publicResourcesBaseURI); final URI publicTempRelativeURI = publicTempBaseURI.relativize(absoluteResolvedURI); //see if the absolute URI is in the application public path // TODO del Log.trace("resourceURI:", resourceURI); if(!publicTempRelativeURI.isAbsolute()) { //if the URI is relative to the application's temp resources final String filename = publicTempRelativeURI.getRawPath(); //get the filename of the temp file final TempFileInfo tempFileInfo = filenameTempFileInfoMap.get(filename); //get the info for this temp file if(tempFileInfo != null) { //if we found the temporary file final GuiseSession restrictionSession = tempFileInfo.getRestrictionSession(); //get the restriction session, if any if(restrictionSession != null) { //if this file is restricted to a Guise session if(!restrictionSession.equals(Guise.getInstance().getGuiseSession())) { //compare the restricted session with the current Guise session, throwing an exception if there is Guise session throw new IllegalStateException("Guise public temporary resource " + uri + " cannot be accessed from the current Guise session."); } } return new FileOutputStream(tempFileInfo.getTempFile()); //create an output stream to the temp file } else { //if there is no such temp file throw new FileNotFoundException("No such Guise public temp file: " + filename); } } else { //if the URI is not an application-relative public temporary resource URI throw new UnsupportedOperationException("Access to non-temporary resource URI " + uri + " is unsupported."); //TODO fix } } @Override public OutputStream getOutputStream(final URIPath path) throws IOException { return getOutputStream(path.toURI()); //create a URI, verifying that it is a path, and return an output stream to the URI } /** * The map of temp file info objects keyed to temporary filenames (not paths). Because all temporary files are created in the same directory, there should be * no filename conflicts. */ private final Map filenameTempFileInfoMap = new ConcurrentHashMap(); @Override public URIPath createTempAsset(String baseName, final String extension, final GuiseSession restrictionSession) throws IOException { final File tempFile = createTempFile(baseName, requireNonNull(extension, "Extension cannot be null."), getTempDirectory(), true); //create a temporary file in the application's temporary directory, specifying that it should be deleted on JVM exit final String filename = tempFile.getName(); //get the name of the file assert filename.length() > 0 : "Name of generated temporary file is missing."; final TempFileInfo tempFileInfo = new TempFileInfo(tempFile, restrictionSession); //create an object to keep track of the file filenameTempFileInfoMap.put(filename, tempFileInfo); //map the filename to the temp file info if(restrictionSession != null) { //if file access should be restricted to a session final GuiseSessionInfo guiseSessionInfo = guiseSessionInfoMap.get(restrictionSession); //get info for this session if(guiseSessionInfo == null) { //if this Guise session isn't registered with this application throw new IllegalStateException("Guise restriction session " + restrictionSession + " not registered with Guise application " + this); } guiseSessionInfo.getTempFileInfos().add(tempFileInfo); //indicate that this temp file is associated with the given session } return GUISE_ASSETS_TEMP_BASE_PATH.resolve(filename); //create and return a path for the temp asset under the Guise temp path } //TODO rename all these to XXXAsset() and check both for normal and temp assets, delegating to the Guise class for non-temp assets /** The string form of the assets base path. */ private static final String GUISE_ASSETS_BASE_PATH_STRING = GUISE_ASSETS_BASE_PATH.toString(); /** The string form of the temp assets base path. */ private static final String GUISE_ASSETS_TEMP_BASE_PATH_STRING = GUISE_ASSETS_TEMP_BASE_PATH.toString(); @Override public boolean hasAsset(final URIPath path) throws IOException { final String pathString = path.normalize().toString(); //get the string form of the normalized path if(pathString.startsWith(GUISE_ASSETS_BASE_PATH_STRING)) { //if the path is in the Guise asset tree if(pathString.startsWith(GUISE_ASSETS_TEMP_BASE_PATH_STRING)) { //if the path is in the Guise temporary asset tree final String filename = pathString.substring(GUISE_ASSETS_TEMP_BASE_PATH_STRING.length()); //determine the filename final TempFileInfo tempFileInfo = filenameTempFileInfoMap.get(filename); //get the info for this temp file return tempFileInfo != null && tempFileInfo.getTempFile().exists(); //return whether there is a temporary file that exists } else { //if the path is for a normal Guise asset final String guiseAssetKey = GUISE_ASSETS_BASE_KEY + pathString.substring(GUISE_ASSETS_BASE_PATH_STRING.length()); //determine the Guise asset key return Guise.getInstance().hasAsset(guiseAssetKey); //see whether the Guise asset exists } } //TODO add support for normal application assets return false; //indicate we couldn't find an asset at the given path } @Override public URL getAssetURL(final URIPath path, final GuiseSession guiseSession) throws IOException { final String pathString = path.normalize().toString(); //get the string form of the normalized path if(pathString.startsWith(GUISE_ASSETS_BASE_PATH_STRING)) { //if the path is in the Guise asset tree if(pathString.startsWith(GUISE_ASSETS_TEMP_BASE_PATH_STRING)) { //if the path is in the Guise temporary asset tree final String filename = pathString.substring(GUISE_ASSETS_TEMP_BASE_PATH_STRING.length()); //determine the filename final TempFileInfo tempFileInfo = filenameTempFileInfoMap.get(filename); //get the info for this temp file if(tempFileInfo == null) { //if there is no temporary file return null; //there is no temporary asset } final GuiseSession restrictionSession = tempFileInfo.getRestrictionSession(); //get the restriction session, if any if(restrictionSession != null) { //if this file is restricted to a Guise session if(!restrictionSession.equals(guiseSession)) { //compare the restricted session with the given Guise session throw new IllegalStateException("Guise temporary asset " + path + " cannot be accessed from the current Guise session."); } } return tempFileInfo.getTempFile().toURI().toURL(); //return a URL to the given temporary asset } else { //if the path is for a normal Guise asset final String guiseAssetKey = GUISE_ASSETS_BASE_KEY + pathString.substring(GUISE_ASSETS_BASE_PATH_STRING.length()); //determine the Guise asset key return Guise.getInstance().getAssetURL(guiseAssetKey); //return a URL to the Guise asset } } //TODO add support for normal application assets return null; //indicate we couldn't find an asset at the given path } @Override public ResourceBundle loadResourceBundle(final Theme theme, final Locale locale) throws IOException { final ClassLoader loader = getClass().getClassLoader(); //get our class loader //default resources ResourceBundle resourceBundle = ResourceBundles.getResourceBundle(DEFAULT_RESOURCE_BUNDLE_BASE_NAME, locale, loader, null, resourcesIO); //load the default resource bundle //theme resources resourceBundle = loadResourceBundle(theme, locale, resourceBundle); //load any resources for this theme and resolving parents //application resources final String resourceBundleBaseName = getResourceBundleBaseName(); //get the specified resource bundle base name //TODO del Log.trace("ready to load application resources; resource bundle base name:", resourceBundleBaseName); if(resourceBundleBaseName != null && !resourceBundleBaseName.equals(DEFAULT_RESOURCE_BUNDLE_BASE_NAME)) { //if a distinct resource bundle base name was specified resourceBundle = ResourceBundles.getResourceBundle(resourceBundleBaseName, locale, loader, resourceBundle, resourcesIO); //load the new resource bundle, specifying the current resource bundle as the parent } return resourceBundle; //return the resource bundle } /** * Retrieves a resource bundle from this theme and its resolving parents, if any. If multiple resource bundles are specified in this theme, they will be * chained in no particular order. For each resource that provides both a reference URI and local definitions, the resources at the reference URI will be used * as the resolving parent of the local definitions. If the theme does not specify a resource bundle, the given parent resource bundle will be returned. * @param theme The theme for which to load resources. * @param locale The locale for which resources should be retrieved. * @param parentResourceBundle The resource bundle to serve as the parent, or null if there is no parent resource bundle. * @return The resource bundle for the theme, with parent resource bundles loaded, or the parent resource bundle if the theme specifies no resources. * @throws IOException if there was an error loading a resource bundle. */ protected ResourceBundle loadResourceBundle(final Theme theme, final Locale locale, final ResourceBundle parentResourceBundle) throws IOException { ResourceBundle resourceBundle = parentResourceBundle; //at the end of the chain will be the parent resource bundle final Theme parentTheme = theme.getParent(); //get the parent theme if(parentTheme != null) { //if there is a parent theme resourceBundle = loadResourceBundle(parentTheme, locale, parentResourceBundle); //get the parent resource bundle first and use that as the parent } for(final Object resources : theme.getResourceResources(locale)) { //for each resources object in the theme if(resources instanceof UrfObject && ((UrfObject)resources).getTag().isPresent()) { final URIPath resourcesURIPath = URIPath.asPathURIPath(((UrfObject)resources).getTag().orElseThrow(AssertionError::new)); assert resourcesURIPath != null : "Transitional themes must use path URIs until URF support relative references."; final URI resourcesURI = theme.getURI().resolve(resourcesURIPath.toURI().getRawPath()); resourceBundle = loadResourceBundle(resourcesURI, resourceBundle); //load the resources and insert it into the chain } else if(resources instanceof Map) { //if this is embedded resources @SuppressWarnings("unchecked") final Map resourceMap = (Map)resources; //TODO cache this if possible if(!resourceMap.isEmpty()) { //if any resources are defined locally resourceBundle = new HashMapResourceBundle(resourceMap, resourceBundle); //create a new hash map resource bundle with resources and the given parent and insert it into the chain } } } return resourceBundle; //return the end of the resource bundle chain } /** A thread-safe cache of softly-referenced resource maps keyed to resource bundle URIs. */ private static final Map> cachedResourceMapMap = new DecoratorReadWriteLockMap>( //TODO restrict this to string keys again new PurgeOnWriteSoftValueHashMap>()); /** * Loads a resource bundle from the given URI. * @param resourceBundleURI The URI of the resource bundle to load. * @param parentResourceBundle The resource bundle to serve as the parent, or null if there is no parent resource bundle. * @return The loaded resource bundle. * @throws IOException if there was an error loading the resource bundle. */ protected ResourceBundle loadResourceBundle(final URI resourceBundleURI, ResourceBundle parentResourceBundle) throws IOException { Map resourceMap = cachedResourceMapMap.get(resourceBundleURI); //see if we already have a map representing the resources in the bundle TODO first check to see if the file has changed if(resourceMap == null) { //if there is no cached resource map; don't worry about the benign race condition, which at worst will cause the resource bundle to be loaded more than once; blocking would be less efficient //TODO del Debug.info("resource bundle cache miss for", resourceBundleURI); //TODO make sure this is a TURF file; if not, load the properties from the properties file try (final InputStream resourcesInputStream = new BufferedInputStream(getInputStream(resourceBundleURI))) { //get a buffered input stream to the resources resourceMap = getResourcesIO().read(resourcesInputStream, resourceBundleURI); //load the resources cachedResourceMapMap.put(resourceBundleURI, resourceMap); //cache the map for later } catch(final IOException ioException) { //if there was an error loading the resource bundle throw new IOException("Error loading resource bundle (" + resourceBundleURI + "): " + ioException.getMessage(), ioException); } } /*TODO del else { //TODO del Debug.info("resource bundle cache hit for", resourceBundleURI); } */ return new HashMapResourceBundle(resourceMap, parentResourceBundle); //create a new hash map resource bundle with resources and the given parent and return it } @Override public Theme loadTheme(final URI themeURI) throws IOException { final URI resolvedThemeURI = resolveURI(themeURI); //resolve the theme URI against the application path; getInputStream() will do this to, but we will need this resolved URI later in this method final InputStream themeInputStream = getInputStream(resolvedThemeURI); //ask the application to get the input stream, so that the resource can be loaded directly if possible if(themeInputStream == null) { //if there is no such theme throw new FileNotFoundException("Missing theme resource: " + resolvedThemeURI); //indicate that the theme cannot be found } final Theme theme; try (final InputStream bufferedThemeInputStream = new BufferedInputStream(themeInputStream)) { //get a buffered input stream to the theme theme = getThemeIO().read(bufferedThemeInputStream, resolvedThemeURI); //read this theme } catch(final IOException ioException) { //if there was an error loading the theme throw new IOException("Error loading theme (" + resolvedThemeURI + "): " + ioException.getMessage(), ioException); } final URI rootThemeURI = GUISE_ROOT_THEME_PATH.toURI(); //get the application-relative URI to the root theme final URI resolvedRootThemeURI = resolveURI(rootThemeURI); //get the resolved path URI to the root theme if(!resolvedThemeURI.equals(resolvedRootThemeURI)) { //if this is not the root theme, load the parent theme URI parentURI = theme.getParentURI(); //get the parent designation, if any TODO detect circular references if(parentURI == null) { //if no parent was designated parentURI = rootThemeURI; //use the root theme for the parent theme } final Theme parentTheme = loadTheme(parentURI); //load the parent theme theme.setParent(parentTheme); //set the parent theme } try { theme.updateRules(); //update the theme rules } catch(final ClassNotFoundException classNotFoundException) { //if a class specified by a rule selector cannot be found throw new IOException("Error loading theme (" + resolvedThemeURI + "): " + classNotFoundException.getMessage(), classNotFoundException); } return theme; //return the theme } @Override public Properties loadProperties(final String propertiesPath) throws IOException { final File propertiesFile = new File(getHomeDirectory(), requireNonNull(propertiesPath, "Properties path cannot be null.")); //create the properties file object final String extension = findNameExtension(propertiesFile).orElse(null); //get the extension of the properties file final boolean isXML; //see if this is an XML file if(XML.FILENAME_EXTENSION.equals(extension)) { //if this is an XML file isXML = true; //indicate that we should load XML } else if(PropertiesUtilities.PROPERTIES_NAME_EXTENSION.equals(extension)) { //if this is a properties file isXML = false; //indicate that we should load normal properties } else { //if this is neither an XML file nor a traditional properties file throw new IllegalArgumentException("Unrecognized properties file type: " + propertiesPath); } final Properties properties = new Properties(); //create a properties file try (final InputStream inputStream = new BufferedInputStream(new FileInputStream(propertiesFile))) { //get an input stream to the file if(isXML) { //if we're loading XML properties.loadFromXML(inputStream); //load the properties file from the XML } else { //if we're loading a traditional properties file properties.load(inputStream); //load the traditional properties file } return properties; //return the properties we loaded } } /** * Looks up a principal from the given ID. This version returns null. * @param id The ID of the principal. * @return The principal corresponding to the given ID, or null if no principal could be determined. */ protected Principal getPrincipal(final String id) { return null; //the abstract Guise application doesn't know any principals } /** * Looks up the corresponding password for the given principal. This version returns null. * @param principal The principal for which a password should be returned. * @return The password associated with the given principal, or null if no password is associated with the given principal. */ protected char[] getPassword(final Principal principal) { return null; //the abstract Guise application doesn't know any passwords } /** * Determines the realm applicable for the resource indicated by the given application path. This version returns the application base path as the realm for * all application paths. * @param applicationPath The relative path of the resource requested. * @return The realm appropriate for the resource, or null if the given resource is not in a known realm. */ protected String getRealm(final URIPath applicationPath) { return getBasePath().toString(); //return the application base path as the realm for all resources } /** * Checks whether the given principal is authorized to access the resource at the given application path. This version authorized any principal accessing any * application path. * @param applicationPath The relative path of the resource requested. * @param principal The principal requesting authentication, or null if the principal is not known. * @param realm The realm with which the resource is associated, or null if the realm is not known. * @return true if the given principal is authorized to access the resource represented by the given application path. */ protected boolean isAuthorized(final URIPath applicationPath, final Principal principal, final String realm) { return true; //default to authorizing access } /** * {@inheritDoc} *

* This version returns an empty set. *

*/ @Override public Set getFacebookAdminIDs() { return emptySet(); //TODO fix to allow storage in the application } /** * {@inheritDoc} *

* This version delegates to {@link #getFacebookAdminIDs()}. *

*/ @Override public Set getFacebookAdminIDs(final URIPath navigationPath) { requireNonNull(navigationPath); return getFacebookAdminIDs(); } /** * {@inheritDoc} *

* This version returns null. *

*/ @Override public String getFacebookAppID() { return null; //TODO fix to allow storage in the application } /** * {@inheritDoc} *

* This version delegates to {@link #getFacebookAppID()}. *

*/ @Override public String getFacebookAppID(final URIPath navigationPath) { requireNonNull(navigationPath); return getFacebookAppID(); } /** * Information about a log writer. * @author Garret Wilson */ private static class LogWriterInfo { /** The writer. */ private final Writer writer; /** @return The writer. */ public Writer getWriter() { return writer; } /** The time at which the writer expires. */ private long expireTime; /** @return The time at which the writer expires. */ public long getExpireTime() { return expireTime; } /** * Writer and expire time constructor. * @param writer The writer. * @param expireTime The time at which the writer expires. * @throws NullPointerException if the given writer is null. */ public LogWriterInfo(final Writer writer, final long expireTime) { this.writer = requireNonNull(writer, "Writer cannot be null."); this.expireTime = expireTime; } } /** * Information about a temporary file. * @author Garret Wilson */ private static class TempFileInfo { /** The file object representing the actual temporary file in the file system. */ private final File tempFile; /** @return The file object representing the actual temporary file in the file system. */ public File getTempFile() { return tempFile; } /** The session to which this temporary file is restricted, or null if this temporary file is not restricted to a session. */ private final GuiseSession restrictionSession; /** @return The session to which this temporary file is restricted, or null if this temporary file is not restricted to a session. */ public GuiseSession getRestrictionSession() { return restrictionSession; } /** * Temporary file and restriction session constructor. * @param tempFile The file object representing the actual temporary file in the file system. * @param restrictionSession The session to which this temporary file is restricted, or null if this temporary file is not restricted to a * session. * @throws NullPointerException if the given temporary file is null. */ public TempFileInfo(final File tempFile, final GuiseSession restrictionSession) { this.tempFile = requireNonNull(tempFile, "Temporary file object cannot be null."); this.restrictionSession = restrictionSession; //save the session, if there is one } @Override public int hashCode() { return getTempFile().hashCode(); //return the file's hash code } /** {@inheritDoc} @return true if the other object is another info object for the same temporary file */ @Override public boolean equals(final Object object) { return object instanceof TempFileInfo && getTempFile().equals(((TempFileInfo)object).getTempFile()); //see if the other object is a temp file info object for the same file } @Override public String toString() { return getTempFile().toString(); } } /** * Application-related information about a Guise session associated with this application. * @author Garret Wilson */ private static class GuiseSessionInfo { /** The Guise session to which the information relates. */ private final GuiseSession guiseSession; /** @return The Guise session to which the information relates. */ public GuiseSession getGuiseSession() { return guiseSession; } /** The synchronized list of information for all files associated with this session. */ private final List tempFileInfos = synchronizedList(new ArrayList()); /** @return The synchronized list of information for all files associated with this session. */ public List getTempFileInfos() { return tempFileInfos; } /** * Guise session constructor. * @param guiseSession The Guise session to which the information relates. * @throws NullPointerException if the given Guise session is null. */ public GuiseSessionInfo(final GuiseSession guiseSession) { this.guiseSession = requireNonNull(guiseSession, "Guise session cannot be null."); } @Override public int hashCode() { return getGuiseSession().hashCode(); //return the session's hash code } /** * {inheritDoc} *

* This version considers two object equal if they are both {@link GuiseSessionInfo} objects with the same session. *

* @see GuiseSessionInfo#getGuiseSession() */ @Override public boolean equals(final Object object) { return object instanceof GuiseSessionInfo && getGuiseSession().equals(((GuiseSessionInfo)object).getGuiseSession()); //see if the other object is a Guise session info object for the same Guise session } @Override public String toString() { return "Guise Session info: " + getGuiseSession().toString(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy