net.community.chest.gitcloud.facade.backend.git.BackendUploadPackFactory.java Source code

Java tutorial

Introduction

Here is the source code for net.community.chest.gitcloud.facade.backend.git.BackendUploadPackFactory.java

Source

/* Copyright 2013 Lyor Goldstein
 *
 * 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.apache.org/licenses/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 net.community.chest.gitcloud.facade.backend.git;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicReference;

import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;

import net.community.chest.gitcloud.facade.git.PackFactory;

import org.apache.commons.io.HexDumpOutputStream;
import org.apache.commons.io.input.TeeInputStream;
import org.apache.commons.io.output.AsciiLineOutputStream;
import org.apache.commons.io.output.LineLevelAppender;
import org.apache.commons.io.output.TeeOutputStream;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.PacketLineOut;
import org.eclipse.jgit.transport.RefAdvertiser;
import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
import org.eclipse.jgit.transport.UploadPack;
import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
import org.eclipse.jgit.transport.resolver.UploadPackFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.SystemPropertyUtils;

/**
 * @author Lyor Goldstein
 * @since Sep 12, 2013 9:19:26 AM
 */
@Component
public class BackendUploadPackFactory<C> extends PackFactory<C> implements UploadPackFactory<C> {
    public static final int DEFAULT_UPLOAD_TIMEOUT_SEC = DEFAULT_TIMEOUT_SEC;
    public static final String UPLOAD_TIMEOUT_SEC_PROP = "gitcloud.backend.upload.pack.timeout.sec";
    private static final String UPLOAD_TIMEOUT_SEC_INJECTION_VALUE = SystemPropertyUtils.PLACEHOLDER_PREFIX
            + UPLOAD_TIMEOUT_SEC_PROP + SystemPropertyUtils.VALUE_SEPARATOR + DEFAULT_UPLOAD_TIMEOUT_SEC
            + SystemPropertyUtils.PLACEHOLDER_SUFFIX;
    // TODO Add a unit test to ensure field exists in any future version of jgit
    private static final Field pckOutField = Validate.notNull(
            FieldUtils.getDeclaredField(PacketLineOutRefAdvertiser.class, "pckOut", true), "Missing pckOut file",
            ArrayUtils.EMPTY_OBJECT_ARRAY);

    // we need this subterfuge since the GitBackendServlet has no injection capabilities
    private static final AtomicReference<UploadPackFactory<?>> holder = new AtomicReference<UploadPackFactory<?>>(
            null);

    @SuppressWarnings("unchecked")
    public static final <T> UploadPackFactory<T> getInstance() {
        return (UploadPackFactory<T>) holder.get();
    }

    private final int uploadTimeoutValue;

    @Inject
    public BackendUploadPackFactory(@Value(UPLOAD_TIMEOUT_SEC_INJECTION_VALUE) int timeoutValue) {
        Assert.state(timeoutValue > 0, "Bad timeout value: " + timeoutValue);
        uploadTimeoutValue = timeoutValue;

        synchronized (holder) {
            Assert.state(holder.get() == null, "Double registered factory");
            holder.set(this);
        }
    }

    @Override
    public UploadPack create(final C request, Repository db)
            throws ServiceNotEnabledException, ServiceNotAuthorizedException {
        final File dir = db.getDirectory();
        final String logPrefix;
        if (request instanceof HttpServletRequest) {
            HttpServletRequest req = (HttpServletRequest) request;
            logPrefix = "create(" + req.getMethod() + ")[" + req.getRequestURI() + "][" + req.getQueryString()
                    + "]";
        } else {
            logPrefix = "create(" + dir.getAbsolutePath() + ")";
        }
        if (logger.isDebugEnabled()) {
            logger.debug(logPrefix + ": " + dir.getAbsolutePath());
        }

        UploadPack up = new UploadPack(db) {
            @Override
            @SuppressWarnings("synthetic-access")
            public void upload(InputStream input, OutputStream output, OutputStream messages) throws IOException {
                InputStream effIn = input;
                OutputStream effOut = output, effMessages = messages;
                if (logger.isTraceEnabled()) {
                    LineLevelAppender inputAppender = new LineLevelAppender() {
                        @Override
                        public void writeLineData(CharSequence lineData) throws IOException {
                            logger.trace(logPrefix + " upload(C): " + lineData);
                        }

                        @Override
                        public boolean isWriteEnabled() {
                            return true;
                        }
                    };
                    effIn = new TeeInputStream(effIn, new HexDumpOutputStream(inputAppender), true);

                    LineLevelAppender outputAppender = new LineLevelAppender() {
                        @Override
                        public void writeLineData(CharSequence lineData) throws IOException {
                            logger.trace(logPrefix + " upload(S): " + lineData);
                        }

                        @Override
                        public boolean isWriteEnabled() {
                            return true;
                        }
                    };
                    effOut = new TeeOutputStream(effOut, new HexDumpOutputStream(outputAppender));

                    if (effMessages != null) {
                        LineLevelAppender messagesAppender = new LineLevelAppender() {
                            @Override
                            public void writeLineData(CharSequence lineData) throws IOException {
                                logger.trace(logPrefix + " upload(M): " + lineData);
                            }

                            @Override
                            public boolean isWriteEnabled() {
                                return true;
                            }
                        };
                        // TODO review the decision to use an AsciiLineOutputStream here
                        effMessages = new TeeOutputStream(effMessages, new AsciiLineOutputStream(messagesAppender));
                    }
                }

                super.upload(effIn, effOut, effMessages);
            }

            @Override
            @SuppressWarnings("synthetic-access")
            public void sendAdvertisedRefs(RefAdvertiser adv) throws IOException, ServiceMayNotContinueException {
                RefAdvertiser effAdv = adv;
                if (logger.isTraceEnabled() && (adv instanceof PacketLineOutRefAdvertiser)) {
                    PacketLineOut pckOut = (PacketLineOut) ReflectionUtils.getField(pckOutField, adv);
                    effAdv = new PacketLineOutRefAdvertiser(pckOut) {
                        private final PacketLineOut pckLog = new PacketLineOut( // TODO review the decision to use an AsciiLineOutputStream here
                                new AsciiLineOutputStream(new LineLevelAppender() {
                                    @Override
                                    public void writeLineData(CharSequence lineData) throws IOException {
                                        logger.trace(logPrefix + " S: " + lineData);
                                    }

                                    @Override
                                    public boolean isWriteEnabled() {
                                        return true;
                                    }
                                }));

                        @Override
                        protected void writeOne(CharSequence line) throws IOException {
                            String s = line.toString();
                            super.writeOne(s);
                            pckLog.writeString(s);
                        }

                        @Override
                        protected void end() throws IOException {
                            super.end();
                            pckLog.end();
                        }
                    };
                }

                super.sendAdvertisedRefs(effAdv);
            }
        };
        up.setTimeout(uploadTimeoutValue);
        return up;
    }
}