org.opendatakit.aggregate.externalservice.AbstractExternalService.java Source code

Java tutorial

Introduction

Here is the source code for org.opendatakit.aggregate.externalservice.AbstractExternalService.java

Source

/**
 * Copyright (C) 2010 University of Washington
 *
 * 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.opendatakit.aggregate.externalservice;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.opendatakit.aggregate.ContextFactory;
import org.opendatakit.aggregate.client.externalserv.ExternServSummary;
import org.opendatakit.aggregate.constants.BeanDefs;
import org.opendatakit.aggregate.constants.common.ExternalServicePublicationOption;
import org.opendatakit.aggregate.constants.common.ExternalServiceType;
import org.opendatakit.aggregate.constants.common.OperationalStatus;
import org.opendatakit.aggregate.exception.ODKExternalServiceException;
import org.opendatakit.aggregate.form.IForm;
import org.opendatakit.aggregate.format.element.ElementFormatter;
import org.opendatakit.aggregate.format.header.HeaderFormatter;
import org.opendatakit.aggregate.submission.Submission;
import org.opendatakit.aggregate.task.UploadSubmissions;
import org.opendatakit.common.datamodel.DeleteHelper;
import org.opendatakit.common.persistence.CommonFieldsBase;
import org.opendatakit.common.persistence.Datastore;
import org.opendatakit.common.persistence.EntityKey;
import org.opendatakit.common.persistence.exception.ODKDatastoreException;
import org.opendatakit.common.persistence.exception.ODKEntityPersistException;
import org.opendatakit.common.persistence.exception.ODKOverQuotaException;
import org.opendatakit.common.security.User;
import org.opendatakit.common.utils.HttpClientFactory;
import org.opendatakit.common.web.CallingContext;
import org.opendatakit.common.web.constants.HtmlConsts;

/**
 *
 * @author wbrunette@gmail.com
 * @author mitchellsundt@gmail.com
 *
 */
public abstract class AbstractExternalService implements ExternalService {

    private static final String NO_BATCH_FUNCTIONALITY_ERROR = "ERROR! External Service does NOT implement a BATCH function to upload multiple submissions - AbstractExternalService";

    /**
     * Datastore entity holding registration of an external service for a specific
     * form and the cursor position within that form that was last processed by
     * this service.
     */
    protected final FormServiceCursor fsc;

    protected final IForm form;

    protected final ElementFormatter formatter;

    protected final HeaderFormatter headerFormatter;

    // these do not take entity bodies...
    protected static final String DELETE = "DELETE";
    protected static final String GET = "GET";

    // these do...
    protected static final String POST = "POST";
    protected static final String PUT = "PUT";
    protected static final String PATCH = "PATCH";

    // and also share all session cookies and credentials across all sessions...
    // these are thread-safe, so this is OK.
    protected static final CookieStore cookieStore = new BasicCookieStore();
    protected static final CredentialsProvider credsProvider = new BasicCredentialsProvider();

    protected static final int SERVICE_TIMEOUT_MILLISECONDS = 60000;

    protected static final int SOCKET_ESTABLISHMENT_TIMEOUT_MILLISECONDS = 60000;

    protected static final Charset UTF_CHARSET = Charset.forName(HtmlConsts.UTF8_ENCODE);

    protected AbstractExternalService(IForm form, FormServiceCursor formServiceCursor, ElementFormatter formatter,
            HeaderFormatter headerFormatter, CallingContext cc) {
        this.form = form;
        this.formatter = formatter;
        this.headerFormatter = headerFormatter;
        this.fsc = formServiceCursor;
    }

    protected abstract String getOwnership();

    protected abstract CommonFieldsBase retrieveObjectEntity();

    protected abstract List<? extends CommonFieldsBase> retrieveRepeatElementEntities();

    protected abstract void insertData(Submission submission, CallingContext cc) throws ODKExternalServiceException;

    @Override
    public boolean canBatchSubmissions() {
        return false;
    }

    @Override
    public void sendSubmissions(List<Submission> submissions, boolean streaming, CallingContext cc)
            throws ODKExternalServiceException {
        throw new ODKExternalServiceException(NO_BATCH_FUNCTIONALITY_ERROR);
    }

    @Override
    public void sendSubmission(Submission submission, CallingContext cc) throws ODKExternalServiceException {
        insertData(submission, cc);
    }

    @Override
    public void setUploadCompleted(CallingContext cc) throws ODKEntityPersistException, ODKOverQuotaException {
        fsc.setUploadCompleted(true);
        if (fsc.getExternalServicePublicationOption() == ExternalServicePublicationOption.UPLOAD_ONLY) {
            fsc.setOperationalStatus(OperationalStatus.COMPLETED);
        }
        Datastore ds = cc.getDatastore();
        User user = cc.getCurrentUser();
        ds.putEntity(fsc, user);
    }

    protected HttpResponse sendHttpRequest(String method, String url, HttpEntity entity,
            List<NameValuePair> qparams, CallingContext cc) throws IOException {

        HttpParams httpParams = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParams, SOCKET_ESTABLISHMENT_TIMEOUT_MILLISECONDS);
        HttpConnectionParams.setSoTimeout(httpParams, SERVICE_TIMEOUT_MILLISECONDS);

        // setup client
        HttpClientFactory factory = (HttpClientFactory) cc.getBean(BeanDefs.HTTP_CLIENT_FACTORY);
        HttpClient client = factory.createHttpClient(httpParams);

        // support redirecting to handle http: => https: transition
        HttpClientParams.setRedirecting(httpParams, true);
        // support authenticating
        HttpClientParams.setAuthenticating(httpParams, true);

        // redirect limit is set to some unreasonably high number
        // resets of the socket cause a retry up to MAX_REDIRECTS times,
        // so we should be careful not to set this too high...
        httpParams.setParameter(ClientPNames.MAX_REDIRECTS, 32);
        httpParams.setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true);

        // context holds authentication state machine, so it cannot be
        // shared across independent activities.
        HttpContext localContext = new BasicHttpContext();

        localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
        localContext.setAttribute(ClientContext.CREDS_PROVIDER, credsProvider);

        HttpUriRequest request = null;
        if (entity == null && (POST.equals(method) || PATCH.equals(method) || PUT.equals(method))) {
            throw new IllegalStateException("No body supplied for POST, PATCH or PUT request");
        } else if (entity != null && !(POST.equals(method) || PATCH.equals(method) || PUT.equals(method))) {
            throw new IllegalStateException("Body was supplied for GET or DELETE request");
        }

        URI nakedUri;
        try {
            nakedUri = new URI(url);
        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalStateException(e);
        }

        if (qparams == null) {
            qparams = new ArrayList<NameValuePair>();
        }
        URI uri;
        try {
            uri = new URI(nakedUri.getScheme(), nakedUri.getUserInfo(), nakedUri.getHost(), nakedUri.getPort(),
                    nakedUri.getPath(), URLEncodedUtils.format(qparams, HtmlConsts.UTF8_ENCODE), null);
        } catch (URISyntaxException e1) {
            e1.printStackTrace();
            throw new IllegalStateException(e1);
        }
        System.out.println(uri.toString());

        if (GET.equals(method)) {
            HttpGet get = new HttpGet(uri);
            request = get;
        } else if (DELETE.equals(method)) {
            HttpDelete delete = new HttpDelete(uri);
            request = delete;
        } else if (PATCH.equals(method)) {
            HttpPatch patch = new HttpPatch(uri);
            patch.setEntity(entity);
            request = patch;
        } else if (POST.equals(method)) {
            HttpPost post = new HttpPost(uri);
            post.setEntity(entity);
            request = post;
        } else if (PUT.equals(method)) {
            HttpPut put = new HttpPut(uri);
            put.setEntity(entity);
            request = put;
        } else {
            throw new IllegalStateException("Unexpected request method");
        }

        HttpResponse resp = client.execute(request);
        return resp;
    }

    @Override
    public void abandon(CallingContext cc) throws ODKDatastoreException {
        if (fsc.getOperationalStatus() != OperationalStatus.COMPLETED) {
            fsc.setOperationalStatus(OperationalStatus.ABANDONED);
            persist(cc);
        }
    }

    @Override
    public void delete(CallingContext cc) throws ODKDatastoreException {
        CommonFieldsBase serviceEntity = retrieveObjectEntity();
        List<? extends CommonFieldsBase> repeats = retrieveRepeatElementEntities();

        Datastore ds = cc.getDatastore();
        User user = cc.getCurrentUser();

        if (repeats != null) {
            List<EntityKey> keys = new ArrayList<EntityKey>();
            for (CommonFieldsBase repeat : repeats) {
                keys.add(repeat.getEntityKey());
            }
            DeleteHelper.deleteEntities(keys, cc);
            repeats.clear();
        }

        ds.deleteEntity(serviceEntity.getEntityKey(), user);
        ds.deleteEntity(fsc.getEntityKey(), user);
    }

    @Override
    public void persist(CallingContext cc) throws ODKEntityPersistException, ODKOverQuotaException {
        Datastore ds = cc.getDatastore();
        User user = cc.getCurrentUser();

        CommonFieldsBase serviceEntity = retrieveObjectEntity();
        List<? extends CommonFieldsBase> repeats = retrieveRepeatElementEntities();

        if (repeats != null) {
            ds.putEntities(repeats, user);
        }
        ds.putEntity(serviceEntity, user);
        ds.putEntity(fsc, user);
    }

    @Override
    public FormServiceCursor getFormServiceCursor() {
        return fsc;
    }

    @Override
    public ExternServSummary transform() {
        return new ExternServSummary(fsc.getUri(), fsc.getCreatorUriUser(), fsc.getOperationalStatus(),
                fsc.getEstablishmentDateTime(), fsc.getExternalServicePublicationOption(), fsc.getUploadCompleted(),
                fsc.getLastUploadCursorDate(), fsc.getLastStreamingCursorDate(), fsc.getExternalServiceType(),
                getOwnership(), getDescriptiveTargetString());
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        int hashCode = 13;
        if (retrieveObjectEntity() != null)
            hashCode += retrieveObjectEntity().hashCode();
        if (fsc != null)
            hashCode += fsc.hashCode();
        return hashCode;
    }

    protected void postUploadTask(CallingContext cc) throws ODKExternalServiceException {
        // upload data to external service
        if (!fsc.getExternalServicePublicationOption().equals(ExternalServicePublicationOption.STREAM_ONLY)) {

            UploadSubmissions uploadTask = (UploadSubmissions) cc.getBean(BeanDefs.UPLOAD_TASK_BEAN);
            CallingContext ccDaemon = ContextFactory.duplicateContext(cc);
            ccDaemon.setAsDaemon(true);
            uploadTask.createFormUploadTask(fsc, true, ccDaemon);
        }
    }

    /**
     * Helper function for constructors.
     *
     */
    protected static FormServiceCursor createFormServiceCursor(IForm form, CommonFieldsBase entity,
            ExternalServicePublicationOption externalServiceOption, ExternalServiceType type, CallingContext cc)
            throws ODKDatastoreException {
        FormServiceCursor formServiceCursor = FormServiceCursor.createFormServiceCursor(form, type, entity, cc);
        formServiceCursor.setExternalServiceOption(externalServiceOption);
        formServiceCursor.setIsExternalServicePrepared(false);
        formServiceCursor.setOperationalStatus(OperationalStatus.ESTABLISHED);
        formServiceCursor.setEstablishmentDateTime(new Date());
        formServiceCursor.setUploadCompleted(false);
        return formServiceCursor;
    }

    /**
     * Helper function for constructors.
     *
     * @param parameterTableRelation
     * @param cc
     * @return
     * @throws ODKDatastoreException
     */
    protected static final <T extends CommonFieldsBase> T newEntity(T parameterTableRelation, CallingContext cc)
            throws ODKDatastoreException {
        Datastore ds = cc.getDatastore();
        User user = cc.getCurrentUser();
        return ds.createEntityUsingRelation(parameterTableRelation, user);
    }

    /**
     * Helper function for constructors.
     *
     * @param parameterTableRelation
     * @param fsc
     * @param cc
     * @return
     * @throws ODKDatastoreException
     */
    protected static final <T extends CommonFieldsBase> T retrieveEntity(T parameterTableRelation,
            FormServiceCursor fsc, CallingContext cc) throws ODKDatastoreException {
        Datastore ds = cc.getDatastore();
        User user = cc.getCurrentUser();
        return ds.getEntity(parameterTableRelation, fsc.getAuriService(), user);
    }

}