/*
* Copyright (C) 2014 Civilian Framework.
*
* Licensed under the Civilian License (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.civilian-framework.org/license.txt
*
* 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.civilian.asset;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.civilian.Response;
import org.civilian.content.ContentType;
import org.civilian.util.Check;
import org.civilian.util.IoUtil;
/**
* Asset represents a static application resource.
* A static resource is an image, css file or javascript file, etc.<br>
*/
public abstract class Asset
{
private static final String MAX_AGE = String.valueOf(30 * 24 * 60 * 60); // 30 days
private static final SimpleDateFormat HTTP_DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
static
{
TimeZone gmtZone = TimeZone.getTimeZone("GMT");
HTTP_DATE_FORMAT.setTimeZone(gmtZone);
}
/**
* Returns the content type of the asset or null
* if not known or determined.
*/
public ContentType getContentType()
{
return contentType_;
}
/**
* Sets the content type of the asset.
*/
public void setContentType(ContentType contentType)
{
contentType_ = contentType;
}
/**
* Returns the encoding of the asset, or null if not known
*/
public String getEncoding()
{
return encoding_;
}
/**
* Sets the encoding of the asset.
*/
public void setEncoding(String encoding)
{
encoding_ = encoding;
}
/**
* Returns the byte length of the asset data.
*/
public long length()
{
return length_;
}
/**
* Sets the byte length of the asset data.
*/
public void setLength(long length)
{
length_ = length;
}
/**
* Sets the last modified date of the asset.
* @param ms the date in milliseconds since epoch.
* Pass a value < 0 for unknown dates.
*/
public void setLastModified(long ms)
{
if (ms < 0)
{
lastModified_ = -1L;
lastModifiedHttp_ = null;
}
else
{
lastModified_ = ms;
Date date = new Date(ms);
synchronized(HTTP_DATE_FORMAT)
{
lastModifiedHttp_ = HTTP_DATE_FORMAT.format(date);
}
}
}
/**
* Returns the last modified date of the asset.
* @return the date as milliseconds since epoch, or -1 if not known.
*/
public long lastModified()
{
return lastModified_;
}
/**
* Returns if an cached asset file is still valid, i.e.
* its source has not changed since the Asset was created.
*/
public abstract boolean isValid();
/**
* Reads the asset content into memory.
*/
public void readContent() throws IOException
{
if (content_ == null)
{
try(InputStream in = getInputStream())
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
IoUtil.copy(in, out);
setContent(out.toByteArray());
}
}
}
/**
* Sets the asset content.
*/
public void setContent(byte[] content)
{
content_ = Check.notNull(content, "content");
setLength(content.length);
}
/**
* Returns the asset content, or null if not
* yet read into memory.
*/
protected byte[] getContent()
{
return content_;
}
/**
* Writes the asset content to the response.
*/
public void write(Response response, boolean writeContent) throws IOException
{
writeHeaders(response);
if (writeContent &&
(length_ != 0) &&
checkIfModified(response))
{
writeContent(response);
}
}
/**
* Writes a last-modified and max-age header to the response,
* if the last modified date is known.
*/
protected void writeHeaders(Response response)
{
if (lastModifiedHttp_ != null)
{
response.getHeaders().set("Last-Modified", lastModifiedHttp_);
response.getHeaders().set("max-age", MAX_AGE);
}
}
/**
* Sets content-related headers and writes the asset content
* to the response.
*/
protected void writeContent(Response response) throws IOException
{
response.setStatus(Response.Status.SC200_OK);
if (contentType_ != null)
response.setContentType(contentType_);
if (encoding_ != null)
response.setContentEncoding(encoding_);
if (length_ >= 0)
response.setContentLength(length_);
OutputStream out = response.getContentStream();
if (content_ != null)
out.write(content_);
else
{
try(InputStream in = getInputStream())
{
IoUtil.copy(in, out);
}
}
}
private boolean checkIfModified(Response response)
{
long modifiedSince = response.getRequest().getHeaders().getDate("If-Modified-Since");
if (modifiedSince != -1)
{
if (lastModified_ < modifiedSince + 1000)
{
response.setStatus(Response.Status.NOT_MODIFIED);
return false;
}
}
return true;
}
/**
* Returns an InputStream for the asset content.
*/
public abstract InputStream getInputStream() throws IOException;
/**
* Returns a Reader for the asset content.
* If no encoding is set on the asset, an IllegalArgumentException
* is thrown.
*/
public Reader getReader() throws IOException
{
Check.notNull(encoding_, "encoding");
return new BufferedReader(new InputStreamReader(getInputStream(), encoding_));
}
/**
* Returns a debug string for the asset.
*/
@Override public abstract String toString();
private byte[] content_;
private ContentType contentType_;
private String encoding_;
private long length_ = -1L;
private long lastModified_ = -1L;
private String lastModifiedHttp_;
}