org.wicketstuff.mergedresources.resources.MergedResourceStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wicketstuff-merged-resources Show documentation
Show all versions of wicketstuff-merged-resources Show documentation
Resource merging for Apache Wicket (http://wicket.apache.org),
see http://talk-on-tech.blogspot.com/2008/08/wicket-interface-speed-up-merging.html
/**
* 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;
}
}