org.alfresco.repo.content.http.HttpAlfrescoContentReader.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.content.http.HttpAlfrescoContentReader.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.content.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Locale;

import javax.servlet.http.HttpServletResponse;

import org.alfresco.error.AlfrescoRuntimeException;
import org.springframework.extensions.surf.util.I18NUtil;
import org.alfresco.repo.content.AbstractContentReader;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentStreamListener;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * The reader that does the actual communication with the Alfresco HTTP
 * application.
 * 
 * @see HttpAlfrescoStore
 * @since 2.1
 * @author Derek Hulley
 */
public class HttpAlfrescoContentReader extends AbstractContentReader {
    private static final String ERR_NO_CONNECTION = "content.http_reader.err.no_connection";
    private static final String ERR_NO_AUTHENTICATION = "content.http_reader.err.no_authentication";
    private static final String ERR_CHECK_CLUSTER = "content.http_reader.err.check_cluster";
    private static final String ERR_UNRECOGNIZED = "content.http_reader.err.unrecognized";

    private static final String DEFAULT_URL = "{0}/dr?contentUrl={1}&ticket={2}";
    private static final String INFO_ONLY = "&infoOnly=true";

    private static Log logger = LogFactory.getLog(HttpAlfrescoContentReader.class);

    private TransactionService transactionService;
    private AuthenticationService authenticationService;
    private String baseHttpUrl;
    // Helpers
    private HttpClient httpClient;
    private PropagateTicketCallback ticketCallback;
    // Cached values
    private boolean isInfoCached;
    private boolean cachedExists;
    private long cachedLastModified;
    private long cachedSize;

    public HttpAlfrescoContentReader(TransactionService transactionService,
            AuthenticationService authenticationService, String baseHttpUrl, String contentUrl) {
        super(contentUrl);
        this.transactionService = transactionService;
        this.authenticationService = authenticationService;
        this.baseHttpUrl = baseHttpUrl;
        // Helpers
        this.httpClient = new HttpClient();
        this.ticketCallback = new PropagateTicketCallback();
        // A trip to the remote server has not been made
        cachedExists = false;
        cachedSize = 0L;
        cachedLastModified = 0L;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(100);
        sb.append("HttpAlfrescoContentReader").append("[ contentUrl=").append(getContentUrl()).append("]");
        return sb.toString();
    }

    /**
     * Helper class to wrap the ticket creation in order to force propagation of the
     * authentication ticket around the cluster.
     * 
     * @since 2.1
     * @author Derek Hulley
     */
    private class PropagateTicketCallback implements RetryingTransactionCallback<String> {
        public String execute() throws Throwable {
            return authenticationService.getCurrentTicket();
        }
    }

    private void getInfo() {
        RunAsWork<Object> getInfoRunAs = new RunAsWork<Object>() {
            public Object doWork() throws Exception {
                getInfoImpl();
                return null;
            }
        };
        AuthenticationUtil.runAs(getInfoRunAs, AuthenticationUtil.SYSTEM_USER_NAME);
    }

    private void getInfoImpl() {
        String contentUrl = getContentUrl();
        // Info will be cached
        isInfoCached = true;
        // Authenticate as the system user for the call
        GetMethod method = null;
        try {
            String ticket = transactionService.getRetryingTransactionHelper().doInTransaction(ticketCallback, false,
                    true);
            String url = HttpAlfrescoContentReader.generateURL(baseHttpUrl, contentUrl, ticket, true);

            method = new GetMethod(url);
            int statusCode = httpClient.executeMethod(method);
            if (statusCode == HttpServletResponse.SC_OK) {
                // Get the information values from the request
                String responseSize = method.getResponseHeader("alfresco.dr.size").getValue();
                String responseLastModified = method.getResponseHeader("alfresco.dr.lastModified").getValue();
                String responseMimetype = method.getResponseHeader("alfresco.dr.mimetype").getValue();
                String responseEncoding = method.getResponseHeader("alfresco.dr.encoding").getValue();
                String responseLocale = method.getResponseHeader("alfresco.dr.locale").getValue();
                // Fill in this reader's values
                cachedSize = DefaultTypeConverter.INSTANCE.convert(Long.class, responseSize);
                cachedLastModified = DefaultTypeConverter.INSTANCE.convert(Date.class, responseLastModified)
                        .getTime();
                setMimetype(DefaultTypeConverter.INSTANCE.convert(String.class, responseMimetype));
                setEncoding(DefaultTypeConverter.INSTANCE.convert(String.class, responseEncoding));
                setLocale(DefaultTypeConverter.INSTANCE.convert(Locale.class, responseLocale));
                // It exists
                cachedExists = true;
                // Done
                if (logger.isDebugEnabled()) {
                    logger.debug("\n" + "HttpReader content found: \n" + "   Reader: " + this + "\n" + "   Server: "
                            + baseHttpUrl);
                }
            } else {
                // Check the return codes
                if (statusCode == HttpServletResponse.SC_NO_CONTENT) {
                    // It doesn't exist, which is not an error.  The defaults are fine.
                } else if (statusCode == HttpServletResponse.SC_FORBIDDEN) {
                    // If the authentication fails, then the server is there, but probably not
                    // clustered correctly.
                    logger.error(I18NUtil.getMessage(ERR_NO_AUTHENTICATION, baseHttpUrl));
                    logger.error(I18NUtil.getMessage(ERR_CHECK_CLUSTER));
                } else {
                    // What is this?
                    logger.error(I18NUtil.getMessage(ERR_UNRECOGNIZED, baseHttpUrl, contentUrl, statusCode));
                    logger.error(I18NUtil.getMessage(ERR_CHECK_CLUSTER));
                }
                // Done
                if (logger.isDebugEnabled()) {
                    logger.debug("\n" + "HttpReader content not found: \n" + "   Reader: " + this + "\n"
                            + "   Server: " + baseHttpUrl);
                }
            }
        } catch (ConnectException e) {
            logger.error(I18NUtil.getMessage(ERR_NO_CONNECTION, baseHttpUrl));
        } catch (Throwable e) {
            throw new AlfrescoRuntimeException("Reader exists check failed: " + this, e);
        } finally {
            if (method != null) {
                try {
                    method.releaseConnection();
                } catch (Throwable e) {
                }
            }
        }
    }

    public synchronized boolean exists() {
        if (!isInfoCached) {
            getInfo();
        }
        return cachedExists;
    }

    public synchronized long getLastModified() {
        if (!isInfoCached) {
            getInfo();
        }
        return cachedLastModified;
    }

    public synchronized long getSize() {
        if (!isInfoCached) {
            getInfo();
        }
        return cachedSize;
    }

    @Override
    protected ContentReader createReader() throws ContentIOException {
        return new HttpAlfrescoContentReader(transactionService, authenticationService, baseHttpUrl,
                getContentUrl());
    }

    @Override
    protected ReadableByteChannel getDirectReadableChannel() throws ContentIOException {
        RunAsWork<ReadableByteChannel> getChannelRunAs = new RunAsWork<ReadableByteChannel>() {
            public ReadableByteChannel doWork() throws Exception {
                return getDirectReadableChannelImpl();
            }
        };
        return AuthenticationUtil.runAs(getChannelRunAs, AuthenticationUtil.SYSTEM_USER_NAME);
    }

    private ReadableByteChannel getDirectReadableChannelImpl() throws ContentIOException {
        String contentUrl = getContentUrl();

        try {
            if (!exists()) {
                throw new IOException("Content doesn't exist");
            }
            String ticket = transactionService.getRetryingTransactionHelper().doInTransaction(ticketCallback, false,
                    true);
            String url = HttpAlfrescoContentReader.generateURL(baseHttpUrl, contentUrl, ticket, false);

            GetMethod method = new GetMethod(url);
            int statusCode = httpClient.executeMethod(method);
            if (statusCode == HttpServletResponse.SC_OK) {
                // Get the stream from the request
                InputStream contentStream = method.getResponseBodyAsStream();
                // Attach a listener to the stream to ensure that the HTTP request is cleaned up
                this.addListener(new StreamCloseListener(method));
                // Done
                if (logger.isDebugEnabled()) {
                    logger.debug("\n" + "HttpReader retrieve intput stream: \n" + "   Reader: " + this + "\n"
                            + "   Server: " + baseHttpUrl);
                }
                return Channels.newChannel(contentStream);
            } else {
                // The content exists, but we failed to get it
                throw new IOException("Failed to get content remote content that supposedly exists.");
            }
        } catch (Throwable e) {
            throw new ContentIOException("Failed to open stream: \n" + "   Reader:        " + this + "\n"
                    + "   Remote server: " + baseHttpUrl, e);
        }
    }

    /**
     * A listener to ensure that the HTTP method gets closed when the content stream it
     * is serving to the reader is closed.
     * 
     * @since 2.1
     * @author Derek Hulley
     */
    private static class StreamCloseListener implements ContentStreamListener {
        private GetMethod getMethod;

        private StreamCloseListener(GetMethod getMethod) {
            this.getMethod = getMethod;
        }

        public void contentStreamClosed() throws ContentIOException {
            try {
                getMethod.releaseConnection();
            } catch (Throwable e) {
            }
        }
    }

    /**
     * Helper to generate a URL based on the ContentStore URL and ticket.
     * 
     * @param baseHttpUrl   the first part of the URL pointing to the Alfresoc Web Application
     * @param contentUrl    the content URL - never null
     * @param ticket        the authentication ticket
     * @param infoOnly      <tt>true</tt> to add the info-only flag
     * 
     * @return              Returns the URL with which to access the servlet
     */
    public final static String generateURL(String baseHttpUrl, String contentUrl, String ticket, boolean infoOnly) {
        String url = MessageFormat.format(DEFAULT_URL, baseHttpUrl, contentUrl, ticket);
        if (infoOnly) {
            url += INFO_ONLY;
        }
        return url;
    }
}