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

org.wicketstuff.mergedresources.resources.MergedResourceStream Maven / Gradle / Ivy

Go to download

Resource merging for Apache Wicket (http://wicket.apache.org), see http://talk-on-tech.blogspot.com/2008/08/wicket-interface-speed-up-merging.html

There is a newer version: 4.0.1
Show newest version
/**
 * Copyright 2010 Molindo GmbH
 *
 * 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 org.wicketstuff.mergedresources.resources;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import org.apache.wicket.Application;
import org.apache.wicket.IClusterable;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.util.listener.IChangeListener;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.apache.wicket.util.resource.locator.ResourceNameIterator;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.time.Time;
import org.apache.wicket.util.watch.IModificationWatcher;
import org.wicketstuff.mergedresources.ResourceSpec;
import org.wicketstuff.mergedresources.preprocess.IResourcePreProcessor;

import at.molindo.utils.io.StreamUtils;

public class MergedResourceStream implements IResourceStream {
	private static final long serialVersionUID = 1L;
	private static transient final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MergedResourceStream.class);

	private final ResourceSpec[] _specs;
	private Locale _locale;
	private final String _style;
	private LocalizedMergedResourceStream _localizedMergedResourceStream;
	private final IResourcePreProcessor _preProcessor;

	/**
	 * @deprecated use ResourceSpec[] instead of scopes[] and files[]
	 */
	@Deprecated
	public MergedResourceStream(final Class[] scopes, final String[] files, final Locale locale, final String style) {
		this(ResourceSpec.toResourceSpecs(scopes, files), locale, style, null);
	}

	public MergedResourceStream(final ResourceSpec[] specs, final Locale locale, final String style,
			IResourcePreProcessor preProcessor) {
		_specs = specs.clone();
		_locale = locale;
		_style = style;
		_preProcessor = preProcessor;
	}

	@Override
	public void close() throws IOException {
		// do nothing
	}

	@Override
	public String getContentType() {
		return getLocalizedMergedResourceStream().getContentType();
	}

	@Override
	public InputStream getInputStream() throws ResourceStreamNotFoundException {
		return getLocalizedMergedResourceStream().getInputStream();
	}

	@Override
	public Locale getLocale() {
		return _locale;
	}

	@Override
	public long length() {
		return getLocalizedMergedResourceStream().getContent().length;
	}

	@Override
	public void setLocale(final Locale locale) {
		_locale = locale;
	}

	@Override
	public Time lastModifiedTime() {
		return getLocalizedMergedResourceStream().getLastModifiedTime();
	}

	private LocalizedMergedResourceStream getLocalizedMergedResourceStream() {
		synchronized (this) {
			if (_localizedMergedResourceStream == null) {
				_localizedMergedResourceStream = new LocalizedMergedResourceStream();
			}
			return _localizedMergedResourceStream;
		}
	}

	private final class LocalizedMergedResourceStream implements IClusterable {
		private static final long serialVersionUID = 1L;
		private final byte[] _content;
		private final String _contentType;
		private final Time _lastModifiedTime;

		private LocalizedMergedResourceStream() {
			Time max = null;
			// final StringWriter w = new StringWriter(4096);
			ByteArrayOutputStream out = new ByteArrayOutputStream(4096);

			final ArrayList resourceStreams = new ArrayList(_specs.length);

			String contentType = null;
			for (int i = 0; i < _specs.length; i++) {
				final Class scope = _specs[i].getScope();
				final String fileName = _specs[i].getFile();

				final IResourceStream resourceStream = findResourceStream(scope, fileName);
				if (contentType != null) {
					if (resourceStream.getContentType() != null
							&& !contentType.equalsIgnoreCase(resourceStream.getContentType())) {
						log.warn("content types of merged resources don't match: '" + resourceStream.getContentType()
								+ "' and '" + contentType + "'");
					}
				} else {
					contentType = resourceStream.getContentType();
				}

				try {

					final Time lastModified = resourceStream.lastModifiedTime();
					if (max == null || lastModified != null && lastModified.after(max)) {
						max = lastModified;
					}
					if (i > 0) {
						writeFileSeparator(out);
					}
					// process content from single spec
					byte[] preprocessed = preProcess(_specs[i], StreamUtils.bytes(resourceStream.getInputStream()));
					writeContent(out, new ByteArrayInputStream(preprocessed));
					resourceStreams.add(resourceStream);
				} catch (final IOException e) {
					throw new WicketRuntimeException("failed to read from " + resourceStream, e);
				} catch (final ResourceStreamNotFoundException e) {
					throw new WicketRuntimeException("did not find resource", e);
				} finally {
					try {
						if (resourceStream != null) {
							resourceStream.close();
						}
					} catch (final IOException e) {
						log.warn("error while closing reader", e);
					}
				}

			}
			_contentType = contentType;

			_content = toContent(preProcess(null, out.toByteArray()));
			_lastModifiedTime = max == null ? Time.now() : max;
			watchForChanges(resourceStreams);
		}

		private IResourceStream findResourceStream(final Class scope, final String fileName) {
			// Create the base path
			final String path = Strings.beforeLast(scope.getName(), '.').replace('.', '/') + '/'
					+ Strings.beforeLast(fileName, '.');
			// Iterator over all the combinations
			final ResourceNameIterator iter = new ResourceNameIterator(path, _style, _locale, Strings.afterLast(
					fileName, '.'));

			IResourceStream resourceStream = null;
			while (resourceStream == null && iter.hasNext()) {
				final String resourceName = iter.next();
				resourceStream = Application.get().getResourceSettings().getResourceStreamLocator()
						.locate(scope, resourceName, _style, _locale, null);
			}

			if (resourceStream == null) {
				throw new WicketRuntimeException("did not find IResourceStream for "
						+ Arrays.asList(scope, fileName, _style, _locale));
			}

			return resourceStream;
		}

		private void writeContent(final OutputStream out, final InputStream resourceStream) throws IOException {
			StreamUtils.copy(resourceStream, out);
			out.flush();
		}

		private void writeFileSeparator(ByteArrayOutputStream out) throws IOException {
			out.write(getFileSeparator());
		}

		private byte[] getFileSeparator() {
			return isPlainText() ? "\n\n".getBytes() : new byte[0];
		}

		private void watchForChanges(final List resourceStreams) {
			// Watch file in the future
			final IModificationWatcher watcher = Application.get().getResourceSettings().getResourceWatcher(true);
			if (watcher != null) {
				final IChangeListener listener = new IChangeListener() {
					@Override
					public void onChange() {
						log.info("merged resource has changed");
						synchronized (MergedResourceStream.this) {
							for (final IResourceStream resourceStream : resourceStreams) {
								watcher.remove(resourceStream);
							}
							_localizedMergedResourceStream = null;
						}
					}
				};
				for (final IResourceStream resourceStream : resourceStreams) {
					watcher.add(resourceStream, listener);
				}
			}
		}

		public InputStream getInputStream() {
			return new ByteArrayInputStream(getContent());
		}

		public byte[] getContent() {
			return _content;
		}

		public Time getLastModifiedTime() {
			return _lastModifiedTime;
		}

		public String getContentType() {
			return _contentType;
		}
	}

	protected byte[] toContent(final byte[] content) {
		return content;
	}

	public boolean isPlainText() {
		// TODO maybe something smarter?
		return true;
	}

	public byte[] preProcess(ResourceSpec resourceSpec, byte[] content) {
		return _preProcessor != null ? _preProcessor.preProcess(resourceSpec, content) : content;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy