org.callimachusproject.server.chain.TransactionHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.callimachusproject.server.chain.TransactionHandler.java

Source

/*
 * Copyright (c) 2013 3 Round Stones Inc., Some Rights Reserved
 *
 * 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 org.callimachusproject.server.chain;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;

import javax.xml.datatype.DatatypeConfigurationException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.protocol.HttpContext;
import org.callimachusproject.client.CloseableEntity;
import org.callimachusproject.client.HttpUriResponse;
import org.callimachusproject.io.ChannelUtil;
import org.callimachusproject.repository.CalliRepository;
import org.callimachusproject.repository.auditing.ActivityFactory;
import org.callimachusproject.repository.auditing.AuditingRepositoryConnection;
import org.callimachusproject.server.AsyncExecChain;
import org.callimachusproject.server.exceptions.InternalServerError;
import org.callimachusproject.server.exceptions.ServiceUnavailable;
import org.callimachusproject.server.helpers.CalliContext;
import org.callimachusproject.server.helpers.CompletedResponse;
import org.callimachusproject.server.helpers.Request;
import org.callimachusproject.server.helpers.RequestActivityFactory;
import org.callimachusproject.server.helpers.ResourceOperation;
import org.callimachusproject.server.helpers.ResponseBuilder;
import org.callimachusproject.server.helpers.ResponseCallback;
import org.openrdf.OpenRDFException;
import org.openrdf.model.URI;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.base.RepositoryConnectionWrapper;
import org.openrdf.repository.object.ObjectConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TransactionHandler implements AsyncExecChain {
    private static final int ONE_PACKET = 1024;

    private final Logger logger = LoggerFactory.getLogger(ResourceOperation.class);
    private final Map<String, CalliRepository> repositories = new LinkedHashMap<String, CalliRepository>();
    private final AsyncExecChain handler;
    final Executor executor;

    public TransactionHandler(AsyncExecChain handler, Executor executor) {
        this.handler = handler;
        this.executor = executor;
    }

    public synchronized void addOrigin(String origin, CalliRepository repository) {
        repositories.put(origin, repository);
    }

    public synchronized void removeOrigin(String origin) {
        repositories.remove(origin);
    }

    @Override
    public Future<HttpResponse> execute(HttpHost target, HttpRequest request, HttpContext ctx,
            FutureCallback<HttpResponse> callback) {
        final Request req = new Request(request, ctx);
        String origin = req.getOrigin();
        CalliRepository repo = getRepository(origin);
        if (repo == null || !repo.isInitialized())
            return notSetup(origin, request, ctx, callback);
        final CalliContext context = CalliContext.adapt(ctx);
        try {
            context.setCalliRepository(repo);
            final ObjectConnection con = repo.getConnection();
            con.begin();
            long now = context.getReceivedOn();
            if (!req.isSafe()) {
                initiateActivity(now, con, context);
            }
            context.setObjectConnection(con);
            final ResourceOperation op = new ResourceOperation(req, con);
            context.setResourceTransaction(op);
            boolean success = false;
            try {
                Future<HttpResponse> future = handler.execute(target, request, context,
                        new ResponseCallback(callback) {
                            public void completed(HttpResponse result) {
                                try {
                                    createSafeHttpEntity(result, con);
                                    super.completed(result);
                                } catch (RepositoryException ex) {
                                    failed(ex);
                                } catch (IOException ex) {
                                    failed(ex);
                                } catch (RuntimeException ex) {
                                    failed(ex);
                                } finally {
                                    context.setResourceTransaction(null);
                                    context.setObjectConnection(null);
                                    context.setCalliRepository(null);
                                }
                            }

                            public void failed(Exception ex) {
                                endTransaction(con);
                                context.setResourceTransaction(null);
                                context.setObjectConnection(null);
                                context.setCalliRepository(null);
                                super.failed(ex);
                            }

                            public void cancelled() {
                                endTransaction(con);
                                context.setResourceTransaction(null);
                                context.setObjectConnection(null);
                                context.setCalliRepository(null);
                                super.cancelled();
                            }
                        });
                success = true;
                return future;
            } finally {
                if (!success) {
                    endTransaction(con);
                }
            }
        } catch (OpenRDFException ex) {
            throw new InternalServerError(ex);
        } catch (DatatypeConfigurationException ex) {
            throw new InternalServerError(ex);
        }
    }

    private synchronized CalliRepository getRepository(String origin) {
        return repositories.get(origin);
    }

    private synchronized Future<HttpResponse> notSetup(String origin, HttpRequest request, HttpContext ctx,
            FutureCallback<HttpResponse> callback) {
        String msg = "No origins are configured";
        if (!repositories.isEmpty()) {
            String closest = closest(origin, repositories.keySet());
            msg = "Origin " + origin + " is not configured, perhaps you wanted " + closest;
        }
        logger.warn(msg);
        ResponseBuilder rb = new ResponseBuilder(request, ctx);
        HttpUriResponse resp = rb.exception(new ServiceUnavailable(msg));
        return new CompletedResponse(callback, resp);
    }

    private String closest(String origin, Set<String> origins) {
        Set<Character> base = toSet(origin.toCharArray());
        String closest = null;
        Set<Character> common = null;
        for (String o : origins) {
            Set<Character> set = toSet(o.toCharArray());
            set.retainAll(base);
            if (common == null || set.size() > common.size()) {
                common = set;
                closest = o;
            }
        }
        return closest;
    }

    private Set<Character> toSet(char[] charArray) {
        Set<Character> set = new HashSet<Character>(charArray.length);
        for (int i = 0; i < charArray.length; i++) {
            set.add(charArray[i]);
        }
        return set;
    }

    void createSafeHttpEntity(HttpResponse resp, ObjectConnection con) throws IOException, RepositoryException {
        boolean endNow = true;
        try {
            if (resp.getEntity() != null) {
                int code = resp.getStatusLine().getStatusCode();
                HttpEntity entity = resp.getEntity();
                long length = entity.getContentLength();
                if ((code == 200 || code == 203) && (length < 0 || length > ONE_PACKET)) {
                    // chunk stream entity, close store connection later
                    resp.setEntity(endEntity(entity, con));
                    endNow = false;
                } else {
                    // copy entity, close store now
                    resp.setEntity(copyEntity(entity, (int) length));
                    endNow = true;
                }
            } else {
                // no entity, close store now
                resp.setHeader("Content-Length", "0");
                endNow = true;
            }
        } finally {
            if (endNow) {
                endTransaction(con);
            }
        }
    }

    /**
     * Request has been fully read and response has been fully written.
     */
    public void endTransaction(ObjectConnection con) {
        try {
            if (con.isOpen()) {
                con.rollback();
                con.close();
            }
        } catch (RepositoryException e) {
            logger.error(e.toString(), e);
        }
    }

    private ByteArrayEntity copyEntity(HttpEntity entity, int length) throws IOException {
        InputStream in = entity.getContent();
        try {
            if (length < 0) {
                length = ONE_PACKET;
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream(length);
            ChannelUtil.transfer(in, baos);
            ByteArrayEntity bae = new ByteArrayEntity(baos.toByteArray());
            bae.setContentEncoding(entity.getContentEncoding());
            bae.setContentType(entity.getContentType());
            return bae;
        } finally {
            in.close();
        }
    }

    private CloseableEntity endEntity(HttpEntity entity, final ObjectConnection con) {
        return new CloseableEntity(entity, new Closeable() {
            public void close() {
                try {
                    executor.execute(new Runnable() {
                        public void run() {
                            endTransaction(con);
                        }
                    });
                } catch (RejectedExecutionException ex) {
                    endTransaction(con);
                }
            }
        });
    }

    private void initiateActivity(long now, ObjectConnection con, CalliContext ctx)
            throws RepositoryException, DatatypeConfigurationException {
        AuditingRepositoryConnection audit = findAuditing(con);
        if (audit != null) {
            ActivityFactory delegate = audit.getActivityFactory();
            URI bundle = con.getVersionBundle();
            assert bundle != null;
            URI activity = delegate.createActivityURI(bundle, con.getValueFactory());
            audit.setActivityFactory(new RequestActivityFactory(activity, delegate, ctx, now));
        }
    }

    private AuditingRepositoryConnection findAuditing(RepositoryConnection con) throws RepositoryException {
        if (con instanceof AuditingRepositoryConnection)
            return (AuditingRepositoryConnection) con;
        if (con instanceof RepositoryConnectionWrapper)
            return findAuditing(((RepositoryConnectionWrapper) con).getDelegate());
        return null;
    }

}