package ameba.mvc.assets;
import ameba.container.server.Request;
import ameba.core.Application;
import ameba.message.internal.MediaType;
import ameba.util.MimeType;
import org.apache.commons.lang3.RandomStringUtils;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ExtendedUriInfo;
import org.glassfish.jersey.spi.ExceptionMappers;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.util.Date;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* <p>AssetsResource class.</p>
*
* @author ICode
* @since 13-8-17 下午2:55
*
*/
@Path("assets")
@Singleton
public class AssetsResource {
@Inject
private Application.Mode mode;
@Inject
private Provider<ExceptionMappers> mappers;
private static void closeJarFileIfNeeded(final JarURLConnection jarConnection,
final JarFile jarFile) throws IOException {
if (!jarConnection.getUseCaches()) {
jarFile.close();
}
}
private static EntityTag computeEntityTag(final File file) {
final StringBuilder sb = new StringBuilder();
final long fileLength = file.length();
final long lastModified = file.lastModified();
if ((fileLength >= 0) || (lastModified >= 0)) {
sb.append(fileLength).append('-').
append(lastModified);
return new EntityTag(sb.toString());
}
return new EntityTag(RandomStringUtils.random(10));
}
/**
* <p>getResource.</p>
*
* @param fileName a {@link java.lang.String} object.
* @param request request
* @param uriInfo uriInfo
* @return a {@link javax.ws.rs.core.Response} object.
* @throws java.net.URISyntaxException uri error
* @throws java.io.IOException io error
*/
@GET
@Path("{file:.*}")
public Response getResource(@PathParam("file") String fileName,
@Context ContainerRequest request,
@Context ExtendedUriInfo uriInfo) throws URISyntaxException, IOException {
if (!fileName.startsWith("/")) {
fileName = "/" + fileName;
}
URI rawUri = ((Request) request).getRawReqeustUri();
String reqPath = rawUri.getPath();
int pathFileIndex = reqPath.lastIndexOf("/");
String reqFileName = reqPath;
if (pathFileIndex != -1) {
reqFileName = reqPath.substring(pathFileIndex);
}
if (!fileName.endsWith(reqFileName)) {
if (pathFileIndex != -1) {
fileName = fileName.substring(0, fileName.lastIndexOf("/")) + reqFileName;
} else {
fileName = reqFileName;
}
}
List<String> uris = uriInfo.getMatchedURIs(true);
String mapName = uris.get(uris.size() - 1);
URL url = AssetsFeature.lookupAsset(mapName, fileName);
File fileResource = null;
String filePath = null;
boolean found = false;
JarURLInputStream jarURLInputStream = null;
if (url != null) {
// url may point to a folder or a file
if ("file".equals(url.getProtocol())) {
final File file = new File(url.toURI());
if (file.exists()) {
if (file.isDirectory()) {
final File welcomeFile = new File(file, "/index.html");
if (welcomeFile.exists() && welcomeFile.isFile()) {
fileResource = welcomeFile;
filePath = welcomeFile.getPath();
found = true;
}
} else {
fileResource = file;
filePath = file.getPath();
found = true;
}
}
} else {
if ("jar".equals(url.getProtocol())) {
URLConnection urlConnection = url.openConnection();
final JarURLConnection jarUrlConnection = (JarURLConnection) urlConnection;
JarEntry jarEntry = jarUrlConnection.getJarEntry();
final JarFile jarFile = jarUrlConnection.getJarFile();
// check if this is not a folder
// we can't rely on jarEntry.isDirectory() because of http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6233323
InputStream is = null;
if (jarEntry.isDirectory() ||
(is = jarFile.getInputStream(jarEntry)) == null) { // it's probably a folder
final String welcomeResource =
jarEntry.getName().endsWith("/") ?
jarEntry.getName() + "index.html" :
jarEntry.getName() + "/index.html";
jarEntry = jarFile.getJarEntry(welcomeResource);
if (jarEntry != null) {
is = jarFile.getInputStream(jarEntry);
}
}
if (is != null) {
jarURLInputStream = new JarURLInputStream(
jarUrlConnection,
jarEntry,
jarFile, is);
filePath = jarEntry.getName();
found = true;
} else {
closeJarFileIfNeeded(jarUrlConnection, jarFile);
}
}
}
}
if (!found) {
return notFound();
}
File file = fileResource;
Response.ResponseBuilder builder = null;
if (file == null) {
// if it's not a jar file - we don't know what to do with that
// so not adding it to the file cache
if ("jar".equals(url.getProtocol())) {
file = getJarFile(
// we need that because url.getPath() may have url encoded symbols,
// which are getting decoded when calling uri.getPath()
new URI(url.getPath()).getPath()
);
}
}
if (file == null) {
return notFound();
}
EntityTag eTag = null;
Date lastModified = null;
if (isFileCacheEnabled()) {
eTag = computeEntityTag(file);
lastModified = new Date(file.lastModified());
builder = request.evaluatePreconditions(
lastModified, eTag);
}
// the resoruce's information was modified, return it
if (builder == null) {
builder = Response.ok();
if (fileResource != null) {
builder.entity(fileResource.toPath());
} else {
builder.entity(jarURLInputStream)
.header(HttpHeaders.CONTENT_LENGTH, jarURLInputStream.jarEntry.getSize());
}
if (isFileCacheEnabled()) {
builder.tag(eTag).lastModified(lastModified);
}
int dot = filePath.lastIndexOf('.');
if (dot > 0) {
String ext = filePath.substring(dot + 1);
String ct = MimeType.get(ext, MediaType.APPLICATION_OCTET_STREAM);
if (ct != null) {
builder.header(HttpHeaders.CONTENT_TYPE, ct);
}
} else {
builder.type(MimeType.get("html"));
}
}
return builder.build();
}
private Response notFound() {
Throwable e = new NotFoundException();
return Response.fromResponse(mappers.get().findMapping(e).toResponse(e))
.type(MediaType.TEXT_HTML_TYPE).build();
}
private boolean isFileCacheEnabled() {
return mode.isProd();
}
private File getJarFile(final String path) throws MalformedURLException, FileNotFoundException {
final int jarDelimIdx = path.indexOf("!/");
if (jarDelimIdx == -1) {
return null;
}
final File file = new File(path.substring(0, jarDelimIdx));
if (!file.exists() || !file.isFile()) {
return null;
}
return file;
}
private static class JarURLInputStream extends java.io.FilterInputStream {
private final JarURLConnection jarConnection;
private final JarFile jarFile;
private final JarEntry jarEntry;
JarURLInputStream(final JarURLConnection jarConnection,
final JarEntry jarEntry,
final JarFile jarFile,
final InputStream src) {
super(src);
this.jarConnection = jarConnection;
this.jarFile = jarFile;
this.jarEntry = jarEntry;
}
@Override
public void close() throws IOException {
try {
super.close();
} finally {
closeJarFileIfNeeded(jarConnection, jarFile);
}
}
}
}