Package ir.xweb.module

Source Code of ir.xweb.module.ResourceModule

package ir.xweb.module;

import ir.xweb.data.DataTools;
import ir.xweb.server.XWebUser;
import ir.xweb.util.MimeType;
import ir.xweb.util.Tools;
import ir.xweb.util.XmlBundle;
import org.apache.commons.fileupload.FileItem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.*;

public class ResourceModule extends Module {

    private final static Logger logger = LoggerFactory.getLogger("ResourceModule");

    private final SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss zzz", Locale.ENGLISH);

    private final static String DEFAULT_BUNDLE = "bundle";

    private long startTempNumber = System.currentTimeMillis();

    /**
     * Temp directory. It can be absolute or relative path. If you use relative path
     * will be parent folder
     */
    public final static String PROPERTY_TEMP_DIR = "dir.temp";

    public final static String PROPERTY_DEFAULT_ID = "default.id";

    public final static String PROPERTY_DATA_DIR   = "dir.data";

    public final static String PROPERTY_TEMPLATE_DIR   = "dir.template";

    public final static String PROPERTY_RESOURCE_BUNDLE_DIR   = "dir.bundle";

    public final static String PROPERTY_STORE_PATTERN   = "store.pattern";

    public final static String PROPERTY_DEFAULT_LANGUAGE = "default.language";

    public final static String MODE_PUBLIC = "public";

    public final static String MODE_PROTECTED = "protected";

    public final static String MODE_PRIVATE= "private";

    private final String tempDirPath;

    /**
     * You should not use this field directly, maybe it's not or directory does no exist anymore.
     * We can use {@link #getTempDir() getTempDir()} instead
     */
    private File tempDir;

    private final File dataDir;

    private final File templateDir;

    /** Remember bundle dir can be NULL **/
    private final File bundleDir;

    private final String defaultId;

    private final String storePattern;

    private final String defaultLanguage;

    private ServletContext context;

    private final Map<String, XmlBundle> bundles = new HashMap<String, XmlBundle>();

    public ResourceModule(
            final Manager manager,
            final ModuleInfo info,
            final ModuleParam properties) throws ModuleException {

        super(manager, info, properties);

        // create and init temp dir
        this.tempDirPath = properties.getString(PROPERTY_TEMP_DIR, null);

        // We don't wanna create temp dir when we don't need it
        this.dataDir = properties.getFile(PROPERTY_DATA_DIR, getTempDirFromSystem());
        if((!this.dataDir.exists() && !this.dataDir.mkdirs()) || !this.dataDir.canWrite()) {
            throw new IllegalArgumentException("Data is not accessible: " + this.dataDir);
        }

        this.templateDir = properties.getFile(PROPERTY_TEMPLATE_DIR,
                new File(manager.getContext().getRealPath("template")));

        // set bundle directory and load default bundle
        this.bundleDir = properties.getFile(PROPERTY_RESOURCE_BUNDLE_DIR,
                new File(manager.getContext().getRealPath("bundle")));
        final File defaultBundle = new File(DEFAULT_BUNDLE, DEFAULT_BUNDLE + ".xml");
        if(defaultBundle.exists()) {
            try {
                bundles.put(DEFAULT_BUNDLE, new XmlBundle(defaultBundle));
            } catch (Exception ex) {
                new IllegalArgumentException("Illegal default bundle file: " + defaultBundle, ex);
            }
        }

        this.storePattern = properties.getString(PROPERTY_STORE_PATTERN, null);

        // deprecated
        this.defaultId = properties.getString(PROPERTY_DEFAULT_ID, null);

        this.defaultLanguage = properties.getString(
                PROPERTY_DEFAULT_LANGUAGE,
                getManager().getProperty(PROPERTY_DEFAULT_LANGUAGE));
    }

    @Override
    public void init(final ServletContext context) {
        this.context = context;
    }

    @Override
    public void process(
            final ServletContext context,
            final HttpServletRequest request,
            final HttpServletResponse response,
            final ModuleParam params,
            final HashMap<String, FileItem> files) throws IOException {

        final XWebUser user = (XWebUser)request.getSession().getAttribute(ir.xweb.server.Constants.SESSION_USER);

        if(params.containsKey("download")) {
            final String path = params.getString("download", null);
            final String id = params.getString("id", null);

            File file = null;
            if(id == null) {
                if(user == null) {
                    file = getFile(path);
                } else {
                    file = getFile(user, path);

                    // if file not found for current user, try to get it from default user
                    if(file == null) {
                        file = getFile(path);
                    }
                }
            } else {
                if(path.startsWith(MODE_PUBLIC + File.separator)) {
                    file = getFile(id, path);
                } else if(path.startsWith(MODE_PROTECTED + File.separator)) {
                    if(user != null) {
                        file = getFile(id, path);
                    } else {
                        logger.info("Try to access resource that is protected by user session is not active: " + path);
                    }
                } else if(isAdmin(user)) {
                    file = getFile(id, path);
                } else {
                    logger.info("Try to access resource that is private by user session is not active: " + path);
                }
            }

            if(file == null || !file.exists()) {
                // we do not rise exception because it's not important to log this message
                throw new ModuleException(HttpServletResponse.SC_NOT_FOUND, "Resource not found: " + path);
            } else {
                // check to store
                boolean store = false;
                //DataSource dataSource = getManager().getDataSource("data-resource");

                //if(dataSource != null) {
                    if(storePattern != null && path.matches(storePattern)) {
                        if (request.getHeader("Range") != null) {
                            // record it just for the first section of file
                            store = request.getHeader("Range").startsWith("bytes=0") || request.getHeader("Range").startsWith("bytes=-");
                        } else {
                            store = true;
                        }
                    }
                //}

                try {
                    // store count
                    if(store) {
                        storeResourceUsage(path);
                        //dataSource.setData(context, ResourceDataSource.DATA_SOURCE_STORE, path);
                    }

                    if (request.getHeader("Range") != null) {
                        rangeDownload(request, response, file);
                    } else {
                        fullDownload(request, response, file);
                    }

                } catch (Exception ex) {
                    ex.printStackTrace();
                    throw new ModuleException("", ex);
                }
            }
        }
    }

    private void rangeDownload(final HttpServletRequest request,
                               final HttpServletResponse response,
                               final File file) throws ServletException, IOException {

        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        final ArrayList<Long> rawRangesList = new ArrayList<Long>();
        final String range = request.getHeader("Range");
        if(!range.startsWith("bytes=")){
            response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            return;
        }
        try {
            final StringTokenizer tokenizer = new StringTokenizer(range.substring("bytes=".length()), ",");
            String token;
            while(tokenizer.hasMoreTokens()){
                token = tokenizer.nextToken();
                rawRangesList.add(getRangeStart(token));
                rawRangesList.add(getRangeEnd(token));
            }
        } catch (NumberFormatException e) {
            response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            return;
        }
        if(rawRangesList.size()==0){
            response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
            return;
        }

        //calculate size and validate values
        final long fileSize = file.length();
        final long validRanges [] = new long[rawRangesList.size()];
        long totalSize = 0;
        for (int i = 0; i < rawRangesList.size()/2; i++) {
            final Long start = rawRangesList.get(i);
            final Long end = rawRangesList.get(i + 1);
            if(start==null&&end==null){
                response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                return;
            }

            final long from;
            final long till;
            if (start == null) {
                from = fileSize - end.longValue();
                till = fileSize;
            } else if (end == null) {
                from = start.longValue();
                till = fileSize;
            } else {
                from = start.longValue();
                till = end.longValue() + 1;
            }
            if( from >= till || from < 0 || till < 0 ||till > fileSize){
                response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                return;
            }
            validRanges[i] = from;
            validRanges[i+1] = till;
            totalSize += till - from;
        }

        //set headers
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("ETag", getETag(file));
        response.setHeader("Last-Modified", getLastModified(file));
        response.setHeader("Content-Length", String.valueOf(totalSize));
        response.setHeader("Content-Type", MimeType.get(file));
        response.setHeader("Content-Range", getContentRange(validRanges, fileSize));
        response.setHeader("Content-Disposition",
                "inline; " +
                        "filename=" + file.getName() + "; " +
                        "modification-date=\"" + dateFormat.format(file.lastModified()) + "\"");

        //send body
        response.setBufferSize(0);
        final OutputStream out = response.getOutputStream();

        if (!"head".equalsIgnoreCase(request.getMethod())) {
            for (int i = 0; i < validRanges.length / 2; i++) {
                write(file, out, validRanges[i], validRanges[i + 1]);
            }
        }
    }

    private String getContentRange(final long [] ranges, final long fileSize) {
        //TODO
        return "bytes " + ranges[0] + "-" + (ranges[1]-1) + "/" + fileSize;
    }

    public File getFile(final String path) {
        if(this.defaultId == null) {
            throw new IllegalArgumentException("Default identifier not found. Please set: " + PROPERTY_DEFAULT_ID);
        }

        return getFile(this.defaultId, path);
    }

    public File getFile(final String id, final String path) {
        if(id == null) {
            throw new IllegalArgumentException("null id");
        }
        if(path == null) {
            throw new IllegalArgumentException("null path");
        }
        if(path.matches("^(\\./|\\.\\./|~/|\\\\)")) {   // try to hack
            throw new IllegalArgumentException("Illegal path: " + path);
        }

        File f = new File(this.dataDir, getUserDirectory(id) + File.separator + path);
        if(f.exists()) {
            return f;
        }

        return null;
    }

    public File getFile(final XWebUser user, final String path) {
        return getFile(user.getId(), path);
    }

    public File initTempFile() {
        startTempNumber++;
        final File file = new File(getTempDir(), Long.toString(startTempNumber));
        if(!file.exists()) {
            try {
                file.createNewFile();
                return file;
            } catch (Exception ex) {
                logger.error("Error to init temp file: " + file, ex);
            }
        }
        return null;
    }

    /**
     * Create temp file and write inputstream on that
     * @param is
     * @return
     */
    public File initTempFile(InputStream is) throws IOException {
        if(is == null) {
            throw new IllegalArgumentException("Inputstream null");
        }
        final File file = initTempFile();
        Files.copy(is, file.toPath());

        return file;

        /*FileOutputStream fos = null;

        try {
            fos = new FileOutputStream(file);
            int size;
            byte[] buffer = new byte[1024];

            while((size = is.read(buffer)) > 0) {
                fos.write(buffer, 0, size);
            }

            return file;
        } finally {
            if(fos != null) {
                try {
                    fos.flush();
                    fos.close();
                } catch (Exception ex) {}
            }
        }*/
    }

    /**
     * Create Unique directory inside temp directory
     * @return
     * @throws IOException
     */
    public File initTempDir() throws IOException {
        final File file = new File(getTempDir(), Long.toString(System.currentTimeMillis()));
        if(!file.exists() && !file.mkdirs()) {
            throw new IOException("Can not create temp dir. Please set '" + PROPERTY_TEMP_DIR + "' property. " + file);
        }
        return file;
    }

    /**
     * Write file with gzip checking, file <filepath>.gz exist and request support gz file, we will use GZ instead of
     * original file
     * @param request
     * @param response
     * @param file
     * @throws IOException
     */
    public void writeFile(
            final HttpServletRequest request,
            final HttpServletResponse response,
            final File file) throws IOException {

        if(file == null) {
            throw new IllegalArgumentException("Null file");
        }

        boolean zipSupport = false;
        final File zipFile = new File(file.getPath() + ".gz");

        if(!file.exists() && !zipFile.exists()) {
            throw new ModuleException(HttpServletResponse.SC_NOT_FOUND, file + " not found");
        }

        if(!response.containsHeader("Content-Encoding")) {
            if(zipFile.exists() && zipFile.canRead()) {
                final String encoding = request.getHeader("Accept-Encoding");
                if(encoding != null && encoding.toLowerCase().indexOf("gzip") > -1) {
                    zipSupport = true;
                }
            }
        }

        if(zipSupport) {
            response.addHeader("Content-Encoding", "gzip");
            if(!response.containsHeader("Content-Type")) {
                response.addHeader("Content-Type", MimeType.get(file));
            }
            writeFile(response, zipFile);
        } else {
            writeFile(response, file);
        }
    }

    public void writeFile(final HttpServletResponse response, final File file) throws IOException {
        if(file != null && file.exists()) {
            if(response == null) {
                throw new IllegalArgumentException("null response");
            }

            final long from = 0;
            final long till = file.length();

            // set headers
            response.setHeader("Accept-Ranges", "bytes");
            response.setHeader("ETag", getETag(file));
            response.setHeader("Last-Modified", getLastModified(file));
            response.setHeader("Content-Length", String.valueOf(till - from));
            response.setHeader("Content-Disposition",
                    "inline; " +
                    "filename=" + file.getName() + "; " +
                    "modification-date=\"" + dateFormat.format(file.lastModified()) + "\"");

            if(!response.containsHeader("Content-Type")) {
                response.addHeader("Content-Type", MimeType.get(file));
            }

            response.setBufferSize(0);
            final OutputStream out = response.getOutputStream();

            try {
                write(file, out, from, till);
            } catch (ServletException ex) {
                throw new IOException(ex);
            }
        } else {
            throw new ModuleException(HttpServletResponse.SC_NOT_FOUND,
                    (file == null ? null : file.getName()) + " not found");
        }
    }

    public File initResourceDir(final String path) {
        if(this.defaultId == null) {
            throw new IllegalArgumentException("Default identifier not found");
        }

        return initResourceDir(this.defaultId, path);
    }

    public File getDataDir() {
        return dataDir;
    }

    /**
     * Get temp directory. If temp directory removed by system, it will make new one.
     * @return
     */
    public File getTempDir() {
        if(this.tempDir == null || !this.tempDir.exists()) {
            if(this.tempDirPath != null) {
                final File d = new File(this.tempDirPath);
                if(d.isAbsolute()) {
                    this.tempDir = d;
                } else {
                    this.tempDir = new File(this.dataDir, d.getPath());
                }
            } else {
                this.tempDir = getTempDirFromSystem();
            }

            // create temp dir
            this.tempDir.mkdirs();
        }
        return this.tempDir;
    }

    public File getTemplateDir() {
        return this.templateDir;
    }

    private File getTempDirFromSystem() {
        try {
            return Files.createTempDirectory("" + System.currentTimeMillis()).toFile();
        } catch (Exception ex) {
            return new File(
                    System.getProperty("java.io.tmpdir") + File.separator + System.currentTimeMillis());
        }
    }

    public File initResourceDir(String id, String path) {
        File dir = new File(dataDir, id + File.separator + path);
        if(dir.exists() || dir.mkdirs()) {
            return dir;
        }
        return null;
    }

    public File initResourceFile(String userId, String path) {
        File dir = new File(dataDir, userId);
        File file = new File(dir, path);

        File p = file.getParentFile();
        if(!p.exists() && !p.mkdirs()) {
            return null;
        }

        return file;
    }

    /*public File initResourceDir(XWebUser user, String path) {
        File dir = new File(dataDir, user.getId() + File.separator + path);
        if(dir.exists() || dir.mkdirs()) {
            return dir;
        }
        return null;
    }*/

    /**
     * Write partial file data
     * @param file
     * @param out
     * @param from
     * @param till
     * @throws ServletException
     * @throws IOException
     */
    private void write(final File file, final OutputStream out, final long from, final long till)
            throws ServletException, IOException {

        final RandomAccessFile f = new RandomAccessFile(file, "r");
        final WritableByteChannel outChannel = Channels.newChannel(out);
        try {
            final MappedByteBuffer map = f.getChannel().map(FileChannel.MapMode.READ_ONLY, from, till - from);
            outChannel.write(map);
        } finally {
            if(f != null) {
                try {
                    f.close();
                } catch (Exception ex) {}
            }
        }
    }

    private String getETag(final File file) {
        return "W/\"" + file.length() + "-" + file.lastModified() + "\"";
    }

    private String getLastModified(final File file) {
        return this.dateFormat.format(new Date(file.lastModified()));
    }

    private void fullDownload(
            final HttpServletRequest request,
            final HttpServletResponse response,
            final File file) throws ServletException, IOException {

        final long from = 0;
        final long till = file.length();

        // set headers
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("ETag", getETag(file));
        response.setHeader("Last-Modified", getLastModified(file));
        response.setHeader("Content-Length", String.valueOf(till - from));
        response.setHeader("Content-Type", MimeType.get(file));
        response.setHeader("Content-Disposition",
                "inline; " +
                "filename=" + file.getName() + "; " +
                "modification-date=\"" + dateFormat.format(file.lastModified()) + "\"");

        response.setBufferSize(0);
        final OutputStream out = response.getOutputStream();

        if(!"head".equalsIgnoreCase(request.getMethod())){
            write(file, out, from, till);
        }
    }

    private Long getRangeStart(final String rangeInterval) throws NumberFormatException{
        final int index = rangeInterval.indexOf("-");
        if(index < 0){
            throw new NumberFormatException();
        }
        final String value = rangeInterval.substring(0, index);
        if(value.length() == 0){
            return null;
        }

        return new Long(value);
    }

    private Long getRangeEnd(final String rangeInterval)throws NumberFormatException{
        final int index = rangeInterval.indexOf("-");
        if(index < 0){
            throw new NumberFormatException();
        }
        final String value = rangeInterval.substring(index+1);
        if(value.length() == 0){
            return null;
        }

        return new Long(value);
    }

    public String applyXmlTemplate(
            final String template,
            final String language,
            final String xml) {
        // find XSLT template
        final File xsltFile = getTemplateFile(template, language, ".xsl");
        if(xsltFile != null) {
            try {
                final String html = applyXslt(xsltFile, xml);

                return html;
            } catch (Exception ex) {
                logger.error("Error to apply XSLT template: " + xsltFile);
            }
        }

        return null;
    }

    public String applyTemplate(
            final String template,
            final String language,
            final Map<String, String> params) {

        // find XSLT template
        final File xsltFile = getTemplateFile(template, language, ".xsl");
        if(xsltFile != null) {
            try {
                final DataTools dataTools = new DataTools();

                final String xml = dataTools.write("xml", null, params);
                final String html = applyXslt(xsltFile, xml);

                return html;
            } catch (Exception ex) {
                logger.error("Error to apply XSLT template: " + xsltFile);
            }
        }

        // find text template
        final File textFile = getTemplateFile(template, language, ".txt");
        if(textFile != null) {
            try {
                final String text = applyText(textFile, params);

                return text;
            } catch (Exception ex) {
                logger.error("Error to apply XSLT template: " + xsltFile);
            }
        }

        return null;
    }

    private String applyText(final File template, final Map<String, String> params) throws Exception {
        String text = Tools.readTextFile(template);

        for(Map.Entry<String, String> e:params.entrySet()) {
            text = text.replace("${" + e.getKey() + "}", e.getValue());
        }

        return text;
    }

    private String applyXslt(final File template, final String xml) throws Exception {
        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

        final DocumentBuilder builder = factory.newDocumentBuilder();
        final InputSource is = new InputSource();
        is.setCharacterStream(new StringReader(xml));

        // Use a Transformer for output
        final TransformerFactory tFactory = TransformerFactory.newInstance();
        final StreamSource styleSource = new StreamSource(template);
        final Transformer transformer = tFactory.newTransformer(styleSource);

        final StringWriter w = new StringWriter();
        final DOMSource source = new DOMSource(builder.parse(is));
        final StreamResult result = new StreamResult(w);
        transformer.transform(source, result);

        return w.toString();
    }

    /*private String paramToXml(final Map<String, String> params) throws IOException {
        final Element root = new Element("params");

        for(Map.Entry<String, String> e:params.entrySet()) {
            final Element param = new Element(e.getKey());
            param.setText(e.getValue());
            root.addContent(param);
        }

        final XMLOutputter xmlOutput = new XMLOutputter();

        final StringWriter w = new StringWriter();
        xmlOutput.setFormat(Format.getRawFormat());
        xmlOutput.output(new Document(root), w);

        return w.toString();
    }*/

    private File getTemplateFile(
            final String template,
            final String language,
            final String extension) {

        File xsltFile = null;
        // find XSLT file
        if(language != null) {
            // ex: template-fa_IR.xsl
            xsltFile = new File(templateDir, template + "-" + language + extension);

            if(!xsltFile.exists()) {
                // ex: template-fa.xsl
                xsltFile = new File(templateDir, template + "-" + language.split("[_-]")[0] + extension);
            }
        }

        if(xsltFile == null || !xsltFile.exists()) {
            xsltFile = new File(templateDir, template + extension);
        }

        return xsltFile.exists() ? xsltFile : null;
    }

    public String getLanguage(final HttpServletRequest request) {
        // Get language from parameter
        String language = null;

        if(request != null) {
            language = request.getParameter("language");

            // Get language from cookie
            if(language == null) {
                final Cookie[] cookies = request.getCookies();
                if(cookies != null) {
                    for(Cookie c:cookies) {
                        if("language".equals(c.getName())) {
                            language = c.getValue();
                            break;
                        }
                    }
                }
            }

            // get language from user-agent
            if(language == null) {
                final Enumeration locales = request.getLocales();
                while (locales.hasMoreElements()) {
                    Locale locale = (Locale) locales.nextElement();
                    language = locale.getDisplayLanguage();
                    break;
                }
            }
        }

        // default language
        if(language == null) {
            language = defaultLanguage;
        }

        // default server language
        if(language == null) {
            language = Locale.getDefault().getDisplayLanguage();
        }

        return language;
    }

    public String getString(final Locale locale, final String key, final String defaultValue) {
        return getString(locale == null ? null : locale.getDisplayLanguage(), key, defaultValue);
    }

    public String getString(final String language, final String key, final String defaultValue) {
        final String l = language == null ? defaultLanguage : language;

        // try to load bundle if is not loaded
        XmlBundle b = bundles.get(l);
        if(b == null) {
            final File f = new File(this.bundleDir, l + ".xml");
            if(f.exists()) {
                try {
                    b = new XmlBundle(f);
                    bundles.put(l, b);
                } catch (Exception ex) {
                    logger.error("Error to load bundle file (" + l + "): " + f);
                }
            }
        }

        final String result = b == null ? defaultValue : b.getString(key);

        return result == null ? key : result;
    }

    protected boolean isAdmin(final XWebUser user) {
        return false;
    }

    protected void storeResourceUsage(final String path) {

    }

    protected String getUserDirectory(final String id) {
        return id;
    }

}
TOP

Related Classes of ir.xweb.module.ResourceModule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.