org.springfield.carl.homer.CarlResource.java Source code

Java tutorial

Introduction

Here is the source code for org.springfield.carl.homer.CarlResource.java

Source

/* 
* CarlResource.java
* 
* Copyright (c) 2017 Noterik B.V.
* 
* This file is part of Carl, related to the Noterik Springfield project.
*
* Carl is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Carl is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Carl.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.springfield.carl.homer;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.restlet.Client;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.CacheDirective;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Parameter;
import org.restlet.data.Protocol;
import org.restlet.data.ServerInfo;
import org.restlet.data.Status;
import org.restlet.engine.adapter.HttpRequest;
import org.restlet.engine.header.Header;
import org.restlet.engine.header.HeaderConstants;
import org.restlet.ext.servlet.ServletUtils;
import org.restlet.representation.FileRepresentation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;
import org.restlet.util.Series;
import org.springfield.carl.fs.ParentVideo;
import org.springfield.carl.homer.CarlResource;

/**
 * File server with ticketing support
 *
 * @author Pieter van Leeuwen
 * @copyright Copyright: Noterik B.V. 2017
 * @package org.springfield.carl.server
 *
 */

public class CarlResource extends ServerResource {
    private static final Logger LOG = Logger.getLogger(CarlResource.class);
    private static final String SERVER_INFO = "Carl/0.1.0";
    private static final String[] SUPPORTED_EXTENSIONS = { "vtt", "jpg" };
    private static final String os = System.getProperty("os.name").toLowerCase();

    /**
     * Handle GET request
     */
    @Get
    public void handleGet() {
        CarlProperties conf = LazyHomer.getMyCarlProperties();
        String basePath = conf.getBasePath();

        HttpServletRequest req = ServletUtils.getRequest(getRequest());
        String baseurl = req.getScheme() + "://" + req.getServerName() + ":" + req.getLocalPort();
        LOG.debug("base url = " + baseurl);

        String fileIdentifier = getIdentifier(Request.getCurrent().getResourceRef().getPath(),
                conf.getContextPath());
        LOG.info("identifier = " + fileIdentifier);

        // set server info
        ServerInfo serverInfo = new ServerInfo();
        serverInfo.setAgent(SERVER_INFO);
        getResponse().setServerInfo(serverInfo);

        File resource = new File(basePath + fileIdentifier);

        // check if requested file exists
        if (resource.exists() && resource.isFile()) {
            // check if we support this type
            String extension = fileIdentifier.lastIndexOf(".") == -1 ? ""
                    : fileIdentifier.substring(fileIdentifier.lastIndexOf(".") + 1);
            if (!supportedExtension(extension)) {
                getResponse().setStatus(Status.SERVER_ERROR_NOT_IMPLEMENTED);
                return;
            }

            // get query
            Form queryForm = getRequest().getResourceRef().getQueryAsForm(CharacterSet.UTF_8);

            String filePath = null;
            Status status = null;

            status = Status.SUCCESS_OK;
            filePath = basePath + fileIdentifier;

            getResponse().setStatus(status);

            if (!status.equals(Status.SUCCESS_OK)) {
                return;
            }

            Series<Header> series = ((HttpRequest) getRequest()).getHeaders();
            Header range = series.getFirst("range");

            File file = new File(filePath);

            String parentVideo = "";

            Pattern pattern = Pattern.compile("/domain/.*/video/[^/]*/");
            Matcher matcher = pattern.matcher(fileIdentifier);
            if (matcher.find()) {
                parentVideo = matcher.group(0);
            }

            if (new ParentVideo(parentVideo, baseurl).isPrivate()) {
                String apiKey = conf.getApiKey();

                /** TODO: Abstract ticket handling in separate class **/
                String ticket = queryForm.getFirstValue("ticket", true, "").toLowerCase();
                LOG.debug("ticket = " + ticket);

                //Range request
                if (range != null) {
                    String byteRange = range.getValue();
                    LOG.debug("Requested byte range " + byteRange);
                    byteRange = byteRange.substring(byteRange.indexOf("=") + 1);
                    long start = Long.parseLong(byteRange.substring(0, byteRange.indexOf("-")));
                    long end = -1;
                    if (byteRange.indexOf("-") < byteRange.length() - 1) {
                        end = Long.parseLong(byteRange.substring(byteRange.indexOf("-") + 1));
                    }

                    if (end == -1l) {
                        end = file.length();
                    }

                    LOG.debug("start = " + start + " end = " + end);

                    //Safari fix that does multiple 0-1 requests
                    if (start == 0l && end > 1l) {
                        //only allowed with ticket

                        //get ticket
                        StringRepresentation entity = new StringRepresentation(
                                "<fsxml><properties><uri>" + fileIdentifier + "</uri></properties></fsxml>");
                        entity.setMediaType(MediaType.TEXT_XML);
                        Request request = new Request(Method.PUT, baseurl + "/lenny/acl/ticketaccess/" + ticket,
                                entity);
                        if (apiKey != null && !apiKey.equals("")) {
                            Series<Header> headers = (Series<Header>) request.getAttributes()
                                    .get("org.restlet.http.headers");
                            if (headers == null) {
                                headers = new Series<Header>(Header.class);
                                request.getAttributes().put("org.restlet.http.headers", headers);
                            }
                            headers.set("X-Api-Key", apiKey);
                        }
                        Context context = new Context();
                        Series<Parameter> parameters = context.getParameters();
                        parameters.add("socketTimeout", "1000");
                        context.setParameters(parameters);
                        Client client = new Client(context, Protocol.HTTP);
                        Response response = client.handle(request);

                        LOG.debug("response = " + response);
                        LOG.debug(response.getEntityAsText());

                        try {
                            Document fsxml = DocumentHelper.parseText(response.getEntityAsText());
                            boolean allowed = fsxml.selectSingleNode("//properties/allowed") == null ? false
                                    : Boolean
                                            .parseBoolean(fsxml.selectSingleNode("//properties/allowed").getText());

                            if (!allowed) {
                                status = Status.CLIENT_ERROR_FORBIDDEN;
                                getResponse().setStatus(status);
                                return;
                            }
                        } catch (DocumentException e) {
                            status = Status.SERVER_ERROR_INTERNAL;
                            getResponse().setStatus(status);
                            return;
                        }
                        request.release();
                        response.release();
                    } else {
                        //for now unlimited allowed
                    }
                } else {
                    //entire request only allowed once

                    //get ticket
                    StringRepresentation entity = new StringRepresentation(
                            "<fsxml><properties><uri>" + fileIdentifier + "</uri></properties></fsxml>");
                    entity.setMediaType(MediaType.TEXT_XML);
                    Request request = new Request(Method.PUT, baseurl + "/lenny/acl/ticketaccess/" + ticket,
                            entity);
                    if (apiKey != null && !apiKey.equals("")) {
                        Series<Header> headers = (Series<Header>) request.getAttributes()
                                .get("org.restlet.http.headers");
                        if (headers == null) {
                            headers = new Series<Header>(Header.class);
                            request.getAttributes().put("org.restlet.http.headers", headers);
                        }
                        headers.set("X-Api-Key", apiKey);
                    }
                    Context context = new Context();
                    Series<Parameter> parameters = context.getParameters();
                    parameters.add("socketTimeout", "1000");
                    context.setParameters(parameters);
                    Client client = new Client(context, Protocol.HTTP);
                    Response response = client.handle(request);

                    LOG.debug("response = " + response);
                    LOG.debug(response.getEntityAsText());

                    try {
                        Document fsxml = DocumentHelper.parseText(response.getEntityAsText());
                        boolean allowed = fsxml.selectSingleNode("//properties/allowed") == null ? false
                                : Boolean.parseBoolean(fsxml.selectSingleNode("//properties/allowed").getText());

                        if (!allowed) {
                            status = Status.CLIENT_ERROR_FORBIDDEN;
                            getResponse().setStatus(status);
                            return;
                        }
                    } catch (DocumentException e) {
                        status = Status.SERVER_ERROR_INTERNAL;
                        getResponse().setStatus(status);
                        return;
                    }
                    request.release();
                    response.release();
                }
            }

            MediaType type = MediaType.ALL;

            if (extension.toLowerCase().equals("jpg")) {
                type = MediaType.IMAGE_JPEG;
            }

            FileRepresentation rep = new FileRepresentation(file, type);

            getResponse().setEntity(rep);

            if (new ParentVideo(parentVideo, baseurl).isPrivate()) {
                Series<Header> responseHeaders = (Series<Header>) getResponse().getAttributes()
                        .get(HeaderConstants.ATTRIBUTE_HEADERS);
                if (responseHeaders == null) {
                    responseHeaders = new Series(Header.class);
                    getResponse().getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS, responseHeaders);
                }

                responseHeaders.add(new Header("Pragma", "no-cache"));

                getResponse().getEntity().setExpirationDate(new Date(0));
                getResponse().setCacheDirectives(new ArrayList<CacheDirective>());
                getResponse().getCacheDirectives().add(CacheDirective.noCache());
            }
            return;
        } else if (resource.exists() && resource.isDirectory()) {
            //resource is a folder
            LOG.debug("FORBIDDEN");
            getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN);
            return;
        } else {
            LOG.debug("NOT FOUND");
            getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND);
            return;
        }
    }

    /**
     * get file identifier from uri
     * 
     * @param uri - the requested uri
     * @param contextPath - the context path
     * @return file identifier
     */
    private String getIdentifier(String uri, String contextPath) {
        //running windows
        if (os.contains("windows")) {
            contextPath = contextPath.replace("\\", "/");
        }

        LOG.debug("Uri = " + uri + " contextPath  = " + contextPath);

        String[] path = contextPath.split("/");

        return uri.substring(uri.indexOf(path[path.length - 1]) + path[path.length - 1].length());
    }

    /**
     * check if this file type is in the list of supported extensions
     * 
     * @param extension - extension of the file
     * @return true if supported, otherwise false
     */
    private boolean supportedExtension(String extension) {
        extension = extension.toLowerCase();

        for (int i = 0; i < SUPPORTED_EXTENSIONS.length; i++) {
            if (SUPPORTED_EXTENSIONS[i].equals(extension)) {
                return true;
            }
        }
        return false;
    }
}