/*
* Copyright 2013 eXo Platform SAS
*
* 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 juzu.impl.bridge.spi.web;
import juzu.PropertyType;
import juzu.Response;
import juzu.asset.AssetLocation;
import juzu.impl.asset.Asset;
import juzu.impl.asset.AssetManager;
import juzu.impl.common.Tools;
import juzu.io.Chunk;
import juzu.io.Stream;
import org.w3c.dom.Element;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/** @author Julien Viet */
public abstract class WebStream implements AsyncStream {
/** . */
private static final int BUFFERING = 0;
/** . */
private static final int STREAMING = 1;
/** . */
private static final int CLOSED = 2;
/** . */
private static final int FAILED = 3;
/** . */
private final HttpStream stream;
/** . */
private int status = BUFFERING;
/** . */
private final AssetManager assetManager;
/** The current document being assembled. */
private final Page page;
/** . */
private final boolean minifyAssets;
public WebStream(HttpStream stream, AssetManager assetManager, boolean minifyAssets) {
this.stream = stream;
this.assetManager = assetManager;
this.page = new Page();
this.minifyAssets = minifyAssets;
}
public void provide(Chunk chunk) {
//
if (status == BUFFERING) {
if (chunk instanceof Chunk.Property<?>) {
Chunk.Property<?> property = (Chunk.Property<?>)chunk;
if (property.type == PropertyType.TITLE) {
page.title = (String)property.value;
} else if (property.type == PropertyType.META_TAG) {
page.metaTags.add(((Map.Entry<String, String>)property.value));
} else if (property.type == PropertyType.META_HTTP_EQUIV) {
page.metaHttpEquivs.add(((Map.Entry<String, String>)property.value));
} else if (property.type == PropertyType.ASSET) {
page.assets.add(((String)property.value));
} else if (property.type == PropertyType.HEADER_TAG) {
page.headerTags.add(((Element)property.value));
} else {
stream.provide(property);
}
} else if (chunk instanceof Chunk.Data) {
try {
if (page.assets.size() > 0 && assetManager != null) {
Iterable<Asset> resolvedAssets = assetManager.resolveAssets(page.assets);
Tools.addAll(page.resolvedAssets, resolvedAssets);
}
status = STREAMING;
page.sendHeader(stream);
}
catch (IllegalArgumentException e) {
// Handle that better...
e.printStackTrace();
//
status = FAILED;
stream.setStatusCode(500);
page.clear();
page.sendHeader(stream);
Response.error(e).asStatus(true).streamable().send(new Stream() {
public void provide(Chunk chunk) {
stream.provide(chunk);
}
public void close(Thread.UncaughtExceptionHandler errorHandler) {
// Do nothing
// perhaps we should have a send(stream, boolean close)
// that would avoid to do that and help to nest stuff
}
});
page.sendFooter(stream);
return;
}
}
}
//
if (status == STREAMING) {
stream.provide(chunk);
}
}
public abstract String renderAssetURL(AssetLocation location, String uri);
public void close(Thread.UncaughtExceptionHandler errorHandler) {
if (status != CLOSED) {
try {
if (status == BUFFERING) {
provide(Chunk.create(""));
}
if (status == STREAMING) {
page.sendFooter(stream);
}
}
finally {
status = CLOSED;
stream.close(errorHandler);
}
}
}
public void end() {
stream.end();
}
private class Page {
/** . */
private String title = null;
/** . */
private final LinkedList<Map.Entry<String, String>> metaTags = new LinkedList<Map.Entry<String, String>>();
/** . */
private final LinkedList<Map.Entry<String, String>> metaHttpEquivs = new LinkedList<Map.Entry<String, String>>();
/** . */
private final LinkedList<String> assets = new LinkedList<String>();
/** . */
private final LinkedList<String> scripts = new LinkedList<String>();
/** . */
private final LinkedList<Element> headerTags = new LinkedList<Element>();
/** . */
private final LinkedList<Asset> resolvedAssets = new LinkedList<Asset>();
void clear() {
this.title = null;
this.metaTags.clear();
this.assets.clear();
this.headerTags.clear();
}
void sendHeader(HttpStream stream) {
stream.provide(Chunk.create(
"<!DOCTYPE html>\n" +
"<html>\n" +
"<head>\n"));
if (title != null) {
stream.provide(Chunk.create("<title>\n"));
stream.provide(Chunk.create(title));
stream.provide(Chunk.create("</title>\n"));
}
if (stream.mimeType != null) {
stream.provide(Chunk.create("<meta http-equiv=\"content-type\" content=\""));
stream.provide(Chunk.create(stream.mimeType));
stream.provide(Chunk.create("; charset="));
stream.provide(Chunk.create(stream.encoding.name()));
stream.provide(Chunk.create("\">\n"));
}
for (Map.Entry<String, String> metaTag : metaHttpEquivs) {
stream.provide(Chunk.create("<meta http-equiv=\""));
stream.provide(Chunk.create(metaTag.getKey()));
stream.provide(Chunk.create("\" content=\""));
stream.provide(Chunk.create(metaTag.getValue()));
stream.provide(Chunk.create("\">\n"));
}
for (Map.Entry<String, String> metaTag : metaTags) {
stream.provide(Chunk.create("<meta name=\""));
stream.provide(Chunk.create(metaTag.getKey()));
stream.provide(Chunk.create("\" content=\""));
stream.provide(Chunk.create(metaTag.getValue()));
stream.provide(Chunk.create("\">\n"));
}
for (Asset asset : resolvedAssets) {
if (asset.isStylesheet()) {
String uri = asset.resolveURI(minifyAssets);
int pos = uri.lastIndexOf('.');
String ext = pos == -1 ? "css" : uri.substring(pos + 1);
String url = renderAssetURL(asset.getLocation(), uri);
stream.provide(Chunk.create("<link rel=\"stylesheet\" type=\"text/"));
stream.provide(Chunk.create(ext));
stream.provide(Chunk.create("\" href=\""));
stream.provide(Chunk.create(url));
stream.provide(Chunk.create("\"/>\n"));
}
}
List<Asset> modules = Collections.emptyList();
for (Asset asset : resolvedAssets) {
if (asset.getType().equals("module")) {
if (modules.isEmpty()) {
modules = new ArrayList<Asset>();
}
modules.add(asset);
}
}
if (!modules.isEmpty()) {
renderAMD(modules, stream);
}
for (Asset asset : resolvedAssets) {
if (asset.isScript() && !Boolean.FALSE.equals(asset.getHeader())) {
String uri = asset.resolveURI(minifyAssets);
String url = renderAssetURL(asset.getLocation(), uri);
stream.provide(Chunk.create("<script type=\"text/javascript\" src=\""));
stream.provide(Chunk.create(url));
stream.provide(Chunk.create("\"></script>\n"));
}
}
for (Element headerTag : headerTags) {
try {
StringBuilder buffer = new StringBuilder();
Tools.encodeHtml(headerTag, buffer);
stream.provide(Chunk.create(buffer));
}
catch (IOException e) {
e.printStackTrace();
}
}
stream.provide(Chunk.create(
"</head>\n" +
"<body>\n"));
}
void sendFooter(Stream stream) {
for (Asset asset : resolvedAssets) {
if (asset.isScript() && Boolean.FALSE.equals(asset.getHeader())) {
String uri = asset.resolveURI(minifyAssets);
String url = renderAssetURL(asset.getLocation(), uri);
stream.provide(Chunk.create("<script type=\"text/javascript\" src=\""));
stream.provide(Chunk.create(url));
stream.provide(Chunk.create("\"></script>\n"));
}
}
stream.provide(Chunk.create(
"</body>\n" +
"</html>\n"));
}
private void renderAMD(Iterable<Asset> modules, Stream stream) {
StringBuilder buffer = new StringBuilder();
buffer.append("<script type=\"text/javascript\">");
buffer.append(" var require={");
buffer.append("\"paths\":{");
for (Iterator<Asset> i = modules.iterator(); i.hasNext();) {
Asset module = i.next();
buffer.append("\"").append(module.getId()).append("\":\"");
String uri = module.resolveURI(minifyAssets);
uri = uri.substring(0, uri.lastIndexOf(".js"));
buffer.append(renderAssetURL(module.getLocation(), uri));
buffer.append("\"");
if (i.hasNext()) {
buffer.append(",");
}
}
buffer.append("}");
buffer.append("};");
buffer.append("</script>");
stream.provide(Chunk.create(buffer));
}
}
}