org.wyona.yanel.impl.resources.node.NodeResourceV101.java Source code

Java tutorial

Introduction

Here is the source code for org.wyona.yanel.impl.resources.node.NodeResourceV101.java

Source

/*
 * Copyright 2006 Wyona
 *
 *  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.wyona.org/licenses/APACHE-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.wyona.yanel.impl.resources.node;

import org.wyona.yanel.core.Resource;
import org.wyona.yanel.core.ResourceNotFoundException;
import org.wyona.yanel.core.api.attributes.CreatableV2;
import org.wyona.yanel.core.api.attributes.IntrospectableV1;
import org.wyona.yanel.core.api.attributes.ModifiableV2;
import org.wyona.yanel.core.api.attributes.VersionableV2;
import org.wyona.yanel.core.api.attributes.ViewableV2;
import org.wyona.yanel.core.api.attributes.WorkflowableV1;
import org.wyona.yanel.core.attributes.versionable.RevisionInformation;
import org.wyona.yanel.core.attributes.viewable.View;
import org.wyona.yanel.core.attributes.viewable.ViewDescriptor;
import org.wyona.yanel.core.workflow.WorkflowException;
import org.wyona.yanel.core.workflow.WorkflowHelper;
import org.wyona.yanel.servlet.communication.HttpRequest;

import org.wyona.yarep.core.Node;
import org.wyona.yarep.core.Repository;
import org.wyona.yarep.core.Revision;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.Date;
import java.util.Enumeration;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import org.apache.commons.fileupload.util.Streams;

/**
 * Generic Node Resource
 */
public class NodeResourceV101 extends Resource
        implements ViewableV2, ModifiableV2, VersionableV2, IntrospectableV1, WorkflowableV1, CreatableV2 {

    private static Logger log = LogManager.getLogger(NodeResourceV101.class);

    private String uploadMimeType;

    /**
     *
     */
    public NodeResourceV101() {
    }

    /**
     * @see org.wyona.yanel.core.api.attributes.ViewableV2#getViewDescriptors()
     */
    public ViewDescriptor[] getViewDescriptors() {
        return null;
    }

    /**
     * @see org.wyona.yanel.core.api.attributes.VersionableV2#getView(String, String)
     */
    public View getView(String viewId, String revisionName) throws Exception {
        // TODO: Check first whether revision of node exists...
        View view = new View();

        view.setInputStream(getNode().getRevision(revisionName).getInputStream());
        view.setMimeType(getMimeType());
        view.setEncoding(getResourceConfigProperty("encoding"));

        return view;
    }

    /**
     * @see org.wyona.yanel.core.api.attributes.ViewableV2#getView(String)
     */
    public View getView(String viewId) throws Exception {
        if (!exists()) {
            throw new ResourceNotFoundException("No such repository node: " + getRepoPath());
        }

        View view = new View();
        view.setMimeType(getMimeType());
        view.setEncoding(getResourceConfigProperty("encoding"));

        String range = getEnvironment().getRequest().getHeader("Range");
        if (range != null) { // INFO: Also see http://stackoverflow.com/questions/12768812/video-streaming-to-ipad-does-not-work-with-tapestry5, http://balusc.blogspot.ch/2009/02/fileservlet-supporting-resume-and.html
            if (!range.equals("bytes=0-")) {
                log.warn("DEBUG: Specific range requested for node '" + getRepoPath() + "': " + range);
                String[] ranges = range.split("=")[1].split("-");
                int from = Integer.parseInt(ranges[0]);
                int to = Integer.parseInt(ranges[1]);
                int len = to - from + 1;
                view.setResponse(false); // INFO: In this case we write directly into the response...
                HttpServletResponse response = getEnvironment().getResponse();
                response.setStatus(206);
                response.setHeader("Accept-Ranges", "bytes");
                String responseRange = String.format("bytes %d-%d/%d", from, to, getSize());
                response.setHeader("Connection", "close");
                response.setHeader("Content-Range", responseRange);
                log.debug("Content-Range:" + responseRange);
                response.setDateHeader("Last-Modified", new Date().getTime());
                response.setContentLength(len);
                log.debug("Content length: " + len);

                OutputStream os = response.getOutputStream();
                InputStream is = new java.io.BufferedInputStream(getNode().getInputStream());

                try {
                    byte[] buf = new byte[4096];
                    is.skip(from);
                    while (len != 0) {
                        int read = is.read(buf, 0, len >= buf.length ? buf.length : len);
                        if (read != -1) {
                            os.write(buf, 0, read);
                            len -= read;
                        }
                    }
                } catch (Exception e) {
                    log.error("Exception '" + e.getMessage() + "' while handling request '" + getRepoPath()
                            + "' (Range: " + range + "), whereas see stack trace for details...");
                    log.error(e, e);
                } finally {
                    if (is != null) {
                        is.close();
                        log.warn("DEBUG: Making sure to close input stream of '" + getNode().getPath()
                                + "' (Range: " + range + ").");
                    }
                }
                return view;
            } else {
                //log.debug("Range requested for node '" + getRepoPath()+ "': " + range);
            }
        } else {
            //log.debug("No range requested for node: " + getRepoPath());
        }

        view.setInputStream(getNode().getInputStream());

        return view;
    }

    /**
     * Get mime type
     */
    private String getMimeType() throws Exception {
        // TODO: Also check mime type of data repository node

        String mimeType = getResourceConfigProperty("mime-type");

        if (mimeType != null)
            return mimeType;

        // TODO: Load config mime.types ...
        String suffix = org.wyona.commons.io.PathUtil.getSuffix(getPath());
        if (suffix != null) {
            log.debug("SUFFIX: " + suffix);
            mimeType = getMimeTypeBySuffix(suffix);
        } else {
            log.warn("mime-type will be set to application/octet-stream, because no suffix for " + getPath());
            mimeType = "application/octet-stream";
        }
        return mimeType;
    }

    /**
     *
     */
    public Reader getReader() throws Exception {
        return new InputStreamReader(getInputStream(), "UTF-8");
    }

    /**
     *
     */
    public InputStream getInputStream() throws Exception {
        return getNode().getInputStream();
    }

    /**
     *
     */
    public Writer getWriter() throws Exception {
        log.error("Not implemented yet!");
        return null;
    }

    /**
     *
     */
    public OutputStream getOutputStream() throws Exception {
        log.error("TODO: Use existsNode() method!");
        if (!getRealm().getRepository().existsNode(getPath())) {
            // TODO: create node recursively ...
            log.error("TODO: Use getNode() method!");
            getRealm().getRepository().getNode(new org.wyona.commons.io.Path(getPath()).getParent().toString())
                    .addNode(new org.wyona.commons.io.Path(getPath()).getName().toString(),
                            org.wyona.yarep.core.NodeType.RESOURCE);
        }
        return getNode().getOutputStream();
    }

    /**
     *
     */
    public void write(InputStream in) throws Exception {
        log.warn("Not implemented yet!");
    }

    /**
     *
     */
    public long getLastModified() throws Exception {
        Node node = getNode();
        long lastModified;
        if (node.isResource()) {
            lastModified = node.getLastModified();
        } else {
            lastModified = 0;
        }

        return lastModified;
    }

    /**
     * Delete data of node resource
     */
    public boolean delete() throws Exception {
        log.warn("TODO: Check if this node is referenced by other nodes!");
        getNode().delete();
        return true;
    }

    /**
     * @see org.wyona.yanel.core.api.attributes.VersionableV2#getRevisions()
     */
    public RevisionInformation[] getRevisions() throws Exception {
        Revision[] revisions = getNode().getRevisions();

        if (revisions != null) {
            RevisionInformation[] revisionInfos = new RevisionInformation[revisions.length];

            for (int i = 0; i < revisions.length; i++) {
                revisionInfos[i] = new RevisionInformation(revisions[i]);
            }
            if (revisions.length > 0) {
                log.warn("Node \"" + getPath() + "\" does not seem to have any revisions! The repository \""
                        + getRealm().getRepository() + "\"  might not support revisions!");
            }
            return revisionInfos;
        }
        log.warn("Node '" + getNode().getPath() + "' has no revisions!");
        return null;
    }

    public void checkin(String comment) throws Exception {
        Node node = getNode();
        node.checkin(comment);
        /*
        if (node.isCheckedOut()) {
        String checkoutUserID = node.getCheckoutUserID();
        if (checkoutUserID.equals(userID)) {
            node.checkin();
        } else {
            throw new Exception("Resource is checked out by another user: " + checkoutUserID);
        }
        } else {
        throw new Exception("Resource is not checked out.");
        }
        */
    }

    public void checkout(String userID) throws Exception {
        Node node = getNode();
        node.checkout(userID);
        /*
        if (node.isCheckedOut()) {
        String checkoutUserID = node.getCheckoutUserID();
        if (checkoutUserID.equals(userID)) {
            log.warn("Resource " + getPath() + " is already checked out by this user: " + checkoutUserID);
        } else {
            throw new Exception("Resource is already checked out by another user: " + checkoutUserID);
        }
        } else {
        node.checkout(userID);
        }
        */
    }

    /**
     * Cancel checkout or rather release the lock
     */
    public void cancelCheckout() throws Exception {
        Node node = getNode();
        log.warn("Release the lock of '" + node.getPath() + "'");
        node.cancelCheckout();
    }

    public void restore(String revisionName) throws Exception {
        getNode().restore(revisionName);
    }

    public Date getCheckoutDate() throws Exception {
        log.warn("Get checkout date not implemented yet!");
        // Node node = getNode();
        // return node.getCheckoutDate();
        return null;
    }

    public String getCheckoutUserID() throws Exception {
        Node node = getNode();
        return node.getCheckoutUserID();
    }

    public boolean isCheckedOut() throws Exception {
        Node node = getNode();
        return node.isCheckedOut();
    }

    /**
     * @see org.wyona.yanel.core.api.attributes.ViewableV2#exists()
     */
    public boolean exists() throws Exception {
        return getRealm().getRepository().existsNode(getRepoPath());
    }

    /**
     * @see org.wyona.yanel.core.api.attributes.ViewableV2#getSize()
     */
    public long getSize() throws Exception {
        Node node = getNode();
        long size;
        if (node.isResource()) {
            size = node.getSize();
        } else {
            size = 0;
        }
        return size;
    }

    /**
     *
     */
    public Object getProperty(String name) {
        log.warn("No implemented yet!");
        return null;
    }

    /**
     *
     */
    public String[] getPropertyNames() {
        String[] props = { "data" };
        return props;
    }

    /**
     *
     */
    public void setProperty(String name, Object value) {
        log.warn("No implemented yet!");
    }

    /**
     * @see org.wyona.yanel.core.api.attributes.CreatableV2#create(HttpServletRequest)
     */
    public void create(HttpServletRequest request) {
        try {
            Repository repo = getRealm().getRepository();

            if (request instanceof HttpRequest) {
                HttpRequest yanelRequest = (HttpRequest) request;
                if (yanelRequest.isMultipartRequest()) {
                    Enumeration parameters = yanelRequest.getFileNames();
                    if (parameters.hasMoreElements()) {
                        String name = (String) parameters.nextElement();

                        Node newNode = org.wyona.yanel.core.util.YarepUtil.addNodes(repo, getPath().toString(),
                                org.wyona.yarep.core.NodeType.RESOURCE);
                        OutputStream output = newNode.getOutputStream();
                        InputStream is = yanelRequest.getInputStream(name);
                        Streams.copy(is, output, true);
                        uploadMimeType = yanelRequest.getContentType(name);

                        String suffix = org.wyona.commons.io.PathUtil.getSuffix(newNode.getPath());
                        if (suffix != null) {
                            if (!getMimeTypeBySuffix(suffix).equals(uploadMimeType)) {
                                log.warn("Upload request content type '" + uploadMimeType
                                        + "' is NOT the same as the guessed mime type '"
                                        + getMimeTypeBySuffix(suffix) + "' based on the suffix (Path: "
                                        + newNode.getPath() + ")");
                            }
                        }
                        newNode.setMimeType(uploadMimeType);
                    }
                } else {
                    log.error("this is NOT a multipart request");
                }
            } else {
                log.error("this is NOT a HttpRequest");
            }

            // TODO: Introspection should not be hardcoded!
            /*          String name = new org.wyona.commons.io.Path(getPath()).getName();
                        String parent = new org.wyona.commons.io.Path(getPath()).getParent().toString();
                        String nameWithoutSuffix = name;
                        int lastIndex = name.lastIndexOf(".");
                        if (lastIndex > 0) nameWithoutSuffix = name.substring(0, lastIndex);
                        String introspectionPath = parent + "/introspection-" + nameWithoutSuffix + ".xml";
                
                        org.wyona.yanel.core.util.YarepUtil.addNodes(repo, introspectionPath, org.wyona.yarep.core.NodeType.RESOURCE);
                        writer = new java.io.OutputStreamWriter(repo.getNode(introspectionPath).getOutputStream());
                        writer.write(getIntrospection(name));
                        writer.close();*/
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * @see org.wyona.yanel.core.api.attributes.CreatableV2#createRTIProperties(HttpServletRequest)
     */
    public java.util.HashMap createRTIProperties(HttpServletRequest request) {
        java.util.HashMap map = new java.util.HashMap();
        String mimeType = request.getParameter("rp.mime-type");
        if (mimeType == null) {
            log.warn("No mime type has been set explicitely! Use content type of upload request: "
                    + this.uploadMimeType);
            mimeType = this.uploadMimeType;
        }
        map.put("mime-type", mimeType);
        map.put("encoding", request.getParameter("rp.encoding"));
        return map;
    }

    /**
     * @see org.wyona.yanel.core.api.attributes.CreatableV2#getCreateName(String)
     */
    public String getCreateName(String suggestedName) {
        if (suggestedName != null && !suggestedName.equals(""))
            return suggestedName;
        if (request instanceof HttpRequest) {
            HttpRequest yanelRequest = (HttpRequest) request;
            if (yanelRequest.isMultipartRequest()) {
                Enumeration parameters = yanelRequest.getFileNames();
                if (parameters.hasMoreElements()) {
                    return fixAssetName(yanelRequest.getFilesystemName((String) parameters.nextElement()));
                }
            } else {
                log.error("this is NOT a multipart request");
            }
        } else {
            log.error("this is NOT a HttpRequest");
        }
        return null;
    }

    /**
     * @see org.wyona.yanel.core.api.attributes.CreatableV2#getPropertyType(String)
     */
    public String getPropertyType(String name) {
        return CreatableV2.TYPE_UPLOAD;
    }

    /**
     * Get introspection document
     */
    public String getIntrospection() throws Exception {
        String name = org.wyona.commons.io.PathUtil.getName(getPath());
        StringBuffer buf = new StringBuffer();
        buf.append("<?xml version=\"1.0\"?>");
        buf.append("<introspection xmlns=\"http://www.wyona.org/neutron/2.0\">");

        buf.append("<navigation>");
        buf.append("  <sitetree href=\"./\" method=\"PROPFIND\"/>");
        buf.append("</navigation>");

        buf.append("<resource name=\"" + name + "\">");
        buf.append("<edit mime-type=\"" + this.getMimeType() + "\">");
        buf.append("<checkout url=\"?yanel.resource.usecase=checkout\" method=\"GET\"/>");
        buf.append("<checkin  url=\"?yanel.resource.usecase=checkin\"  method=\"PUT\"/>");
        buf.append("<release-lock url=\"?yanel.resource.usecase=release-lock\" method=\"GET\"/>");
        buf.append("</edit>");

        buf.append(getWorkflowIntrospection());

        buf.append("</resource>");
        buf.append("</introspection>");
        return buf.toString();
    }

    /**
     *
     */
    public String getMimeTypeBySuffix(String suffix) {
        // TODO: use MimeTypeUtil
        if (suffix.equals("html")) {
            return "text/html";
        } else if (suffix.equals("htm")) {
            return "text/html";
        } else if (suffix.equals("xhtml")) {
            return "application/xhtml+xml";
        } else if (suffix.equals("xml")) {
            return "application/xml";
        } else if (suffix.equals("xsd")) {
            return "application/xml";
            // TODO: Clarify ...
            //return "application/xsd+xml";
        } else if (suffix.equals("xsl")) {
            return "application/xml";
            // TODO: Clarify ...
            //return "application/xslt+xml";
        } else if (suffix.equals("css")) {
            return "text/css";
        } else if (suffix.equals("js")) {
            return "application/x-javascript";
        } else if (suffix.equals("png")) {
            return "image/png";
        } else if (suffix.equals("jpg") || suffix.equals("jpeg")) {
            return "image/jpeg";
        } else if (suffix.equals("gif")) {
            return "image/gif";
        } else if (suffix.equals("pdf")) {
            return "application/pdf";
        } else if (suffix.equals("doc")) {
            return "application/msword";
        } else if (suffix.equals("odt")) {
            return "application/vnd.oasis.opendocument.text";
        } else if (suffix.equals("odg")) {
            return "application/vnd.oasis.opendocument.graphics";
        } else if (suffix.equals("sxc")) {
            return "application/vnd.sun.xml.calc";
        } else if (suffix.equals("xpi")) {
            return "application/x-xpinstall";
        } else if (suffix.equals("zip")) {
            return "application/zip";
        } else if (suffix.equals("jar")) { // http://en.wikipedia.org/wiki/Jar_(file_format)
            return "application/java-archive";
        } else if (suffix.equals("war")) {
            return "application/java-archive";
        } else if (suffix.equals("flv")) {
            return "video/x-flv";
        } else if (suffix.equals("swf")) {
            return "application/x-shockwave-flash";
        } else if (suffix.equals("txt")) {
            return "text/plain";
        } else if (suffix.equals("mov")) {
            return "video/quicktime";
        } else if (suffix.equals("mp3")) {
            return "audio/mpeg";
        } else if (suffix.equals("mp4")) {
            return "video/mp4";
        } else if (suffix.equals("m4v")) {
            return "video/x-m4v";
        } else if (suffix.equals("ogv")) {
            return "video/ogg";
        } else if (suffix.equals("webm")) {
            return "video/webm";
        } else if (suffix.equals("wav")) {
            return "audio/x-wav";
        } else if (suffix.equals("svg")) {
            return "image/svg+xml";
        } else if (suffix.equals("ico")) {
            return "image/x-icon";
        } else if (suffix.equals("woff2")) {
            return "font/woff2";
        } else {
            log.warn("Could not determine mime-type from suffix '" + suffix + "' (path: " + getPath()
                    + "). Return application/octet-stream!");
            return "application/octet-stream";
        }
    }

    /**
     *
     */
    public String getWorkflowIntrospection() throws WorkflowException {
        return WorkflowHelper.getWorkflowIntrospection(this);
    }

    /**
     *
     */
    public void removeWorkflowVariable(String name) throws WorkflowException {
        WorkflowHelper.removeWorkflowVariable(this, name);
    }

    /**
     *
     */
    public void setWorkflowVariable(String name, String value) throws WorkflowException {
        WorkflowHelper.setWorkflowVariable(this, name, value);
    }

    /**
     *
     */
    public String getWorkflowVariable(String name) throws WorkflowException {
        return WorkflowHelper.getWorkflowVariable(this, name);
    }

    /**
     *
     */
    public Date getWorkflowDate(String revision) throws WorkflowException {
        return WorkflowHelper.getWorkflowDate(this, revision);
    }

    /**
     *
     */
    public void setWorkflowState(String state, String revision) throws WorkflowException {
        WorkflowHelper.setWorkflowState(this, state, revision);
    }

    /**
     *
     */
    public String getWorkflowState(String revision) throws WorkflowException {
        return WorkflowHelper.getWorkflowState(this, revision);
    }

    /**
     *
     */
    public View getLiveView(String viewid) throws Exception {
        return WorkflowHelper.getLiveView(this, viewid);
    }

    /**
     *
     */
    public boolean isLive() throws WorkflowException {
        return WorkflowHelper.isLive(this);
    }

    /**
     *
     */
    public void doTransition(String transitionID, String revision) throws WorkflowException {
        WorkflowHelper.doTransition(this, transitionID, revision);
    }

    protected String fixAssetName(String name) {
        // some browsers may send the whole path:
        int i = name.lastIndexOf("\\");
        if (i > -1) {
            name = name.substring(i + 1);
        }
        i = name.lastIndexOf("/");
        if (i > -1) {
            name = name.substring(i + 1);
        }
        name = name.replaceAll(" |&|%|\\?", "_");
        return name;
    }

    /**
     * Get repository node
     */
    private Node getNode() throws ResourceNotFoundException {
        try {
            String path = getRepoPath();
            try {
                return getRealm().getRepository().getNode(path);
            } catch (org.wyona.yarep.core.NoSuchNodeException e) {
                throw new ResourceNotFoundException(path);
                //throw new ResourceNotFoundException(path, getRealm(), getRealm().getRepository());
            }
        } catch (Exception e) {
            throw new ResourceNotFoundException(e);
        }
    }

    /**
     * Get repository path (We do not overwrite the getPath() method, because it's still used inside this class at many places without checking for the 'src' property!)
     */
    private String getRepoPath() throws Exception {
        String path = getPath();
        if (getResourceConfigProperty("src") != null) {
            path = getResourceConfigProperty("src");
        }
        return path;
    }
}