de.unioninvestment.eai.portal.portlet.crud.scripting.domain.container.rest.ReSTDelegateImpl.java Source code

Java tutorial

Introduction

Here is the source code for de.unioninvestment.eai.portal.portlet.crud.scripting.domain.container.rest.ReSTDelegateImpl.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 de.unioninvestment.eai.portal.portlet.crud.scripting.domain.container.rest;

import static java.util.Collections.unmodifiableList;
import groovy.lang.Closure;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
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.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.DefaultHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.data.Container.Filter;

import de.unioninvestment.eai.portal.portlet.crud.config.GroovyScript;
import de.unioninvestment.eai.portal.portlet.crud.config.ReSTAttributeConfig;
import de.unioninvestment.eai.portal.portlet.crud.config.ReSTChangeConfig;
import de.unioninvestment.eai.portal.portlet.crud.config.ReSTChangeMethodConfig;
import de.unioninvestment.eai.portal.portlet.crud.config.ReSTContainerConfig;
import de.unioninvestment.eai.portal.portlet.crud.config.ReSTFormatConfig;
import de.unioninvestment.eai.portal.portlet.crud.domain.exception.BusinessException;
import de.unioninvestment.eai.portal.portlet.crud.domain.exception.InvalidConfigurationException;
import de.unioninvestment.eai.portal.portlet.crud.domain.exception.TechnicalCrudPortletException;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.GenericContainerRow;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.GenericContainerRowId;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.ReSTContainer;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.ReSTDelegate;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.authentication.Realm;
import de.unioninvestment.eai.portal.portlet.crud.domain.support.AuditLogger;
import de.unioninvestment.eai.portal.portlet.crud.scripting.model.ScriptRow;
import de.unioninvestment.eai.portal.support.scripting.ScriptBuilder;
import de.unioninvestment.eai.portal.support.vaadin.container.Column;
import de.unioninvestment.eai.portal.support.vaadin.container.GenericItem;
import de.unioninvestment.eai.portal.support.vaadin.container.MetaData;
import de.unioninvestment.eai.portal.support.vaadin.container.UpdateContext;

public class ReSTDelegateImpl implements ReSTDelegate {

    static final class ConstantStringProvider extends Closure<Object> {
        private final String string;
        private static final long serialVersionUID = 1L;

        ConstantStringProvider(Object owner, String newQueryUrl) {
            super(owner);
            this.string = newQueryUrl;
        }

        public String doCall() {
            return string;
        }
    }

    private static final List<Object[]> EMPTY_RESULT = unmodifiableList(new LinkedList<Object[]>());

    private static final Logger LOGGER = LoggerFactory.getLogger(ReSTDelegateImpl.class);

    private ReSTContainerConfig config;
    private ScriptBuilder scriptBuilder;

    private MetaData metaData;

    HttpClient httpClient = new DefaultHttpClient();
    PayloadParser parser;
    PayloadCreator creator;

    private ReSTContainer container;

    private Closure<Object> baseUrlProvider;
    private Closure<Object> queryUrlProvider;

    private AuditLogger auditLogger;

    public ReSTDelegateImpl(ReSTContainerConfig containerConfig, ReSTContainer container, Realm realm,
            ScriptBuilder scriptBuilder, AuditLogger auditLogger) {
        this.config = containerConfig;
        this.container = container;
        this.scriptBuilder = scriptBuilder;
        this.auditLogger = auditLogger;

        this.metaData = extractMetaData();
        this.parser = createParser();
        this.creator = createCreator();

        this.baseUrlProvider = scriptBuilder.buildClosure(config.getBaseUrl());
        this.queryUrlProvider = scriptBuilder.buildClosure(config.getQuery().getUrl());

        if (realm != null && httpClient instanceof DefaultHttpClient) {
            // for Testing => DefaultHttpClient cannot be fully mocked due to
            // final methods
            realm.applyBasicAuthentication((DefaultHttpClient) httpClient);
        }
    }

    private PayloadCreator createCreator() {
        if (config.getFormat() == ReSTFormatConfig.JSON) {
            return new JsonCreator(container, scriptBuilder);
        } else if (config.getFormat() == ReSTFormatConfig.XML) {
            return new XmlCreator(container, scriptBuilder);
        } else {
            throw new TechnicalCrudPortletException("Unknown ReST format: " + config.getFormat());
        }
    }

    private PayloadParser createParser() {
        if (config.getFormat() == ReSTFormatConfig.JSON) {
            return new JsonParser(config, scriptBuilder);
        } else if (config.getFormat() == ReSTFormatConfig.XML) {
            return new XmlParser(config, scriptBuilder);
        } else {
            throw new TechnicalCrudPortletException("Unknown ReST format: " + config.getFormat());
        }
    }

    private MetaData extractMetaData() {
        List<ReSTAttributeConfig> attributes = config.getQuery().getAttribute();
        List<Column> columns = new ArrayList<Column>(attributes.size());
        for (ReSTAttributeConfig attribute : attributes) {
            columns.add(new Column(attribute.getName(), attribute.getType(), attribute.isReadonly(),
                    attribute.isRequired(), attribute.isPrimaryKey(), null));
        }
        boolean insertSupported = config.getInsert() != null;
        boolean updateSupported = config.getUpdate() != null;
        boolean deleteSupported = config.getDelete() != null;

        return new MetaData(columns, insertSupported, updateSupported, deleteSupported, false, false);
    }

    @Override
    public MetaData getMetaData() {
        return metaData;
    }

    @Override
    public List<Object[]> getRows() {
        String queryUrl = (String) queryUrlProvider.call();
        if (StringUtils.isBlank(queryUrl)) {
            return EMPTY_RESULT;
        }
        HttpGet request = createQueryRequest();
        try {
            HttpResponse response = httpClient.execute(request);
            expectAnyStatusCode(response, HttpStatus.SC_OK);

            return parser.getRows(response);

        } catch (ClientProtocolException e) {
            LOGGER.error("Error during restful query", e);
            throw new BusinessException("portlet.crud.error.rest.io", e.getMessage());

        } catch (IOException e) {
            LOGGER.error("Error during restful query", e);
            throw new BusinessException("portlet.crud.error.rest.io", e.getMessage());

        } finally {
            request.releaseConnection();
        }
    }

    private HttpGet createQueryRequest() {
        String queryUrl = queryUrlProvider.call().toString();
        URI uri = createURI(queryUrl);
        HttpGet request = new HttpGet(uri);

        String mimetype = creator.getMimeType();
        if (config.getMimetype() != null) {
            mimetype = config.getMimetype();
        }
        request.addHeader("Accept", mimetype);
        return request;
    }

    private URI createURI(String postfix) {
        String uri = postfix;
        if (baseUrlProvider != null) {
            Object baseUrl = baseUrlProvider.call();
            if (baseUrl != null) {
                uri = baseUrl.toString() + postfix;
            }
        }
        try {
            return URI.create(uri);

        } catch (IllegalArgumentException e) {
            throw new InvalidConfigurationException("portlet.crud.error.config.rest.invalidUrl", uri);
        }
    }

    @Override
    public void setFilters(Filter[] filters) {
        throw new UnsupportedOperationException("Server side filtering is not supported by the ReST backend");
    }

    @Override
    public void update(List<GenericItem> items, UpdateContext context) {
        context.requireRefresh();
        try {
            for (GenericItem item : items) {

                if (item.isNewItem() || item.isModified()) {
                    ReSTChangeConfig changeConfig = findChangeConfig(item);
                    ReSTChangeMethodConfig method = findRequestMethod(item);
                    if (method == ReSTChangeMethodConfig.POST) {
                        sendPostRequest(item, changeConfig);
                    } else {
                        sendPutRequest(item, changeConfig);
                    }
                } else if (item.isDeleted()) {
                    sendDeleteRequest(item);
                }
            }
        } catch (ClientProtocolException e) {
            LOGGER.error("Error during restful operation", e);
            throw new BusinessException("portlet.crud.error.rest.io", e.getMessage());

        } catch (IOException e) {
            LOGGER.error("Error during restful operation", e);
            throw new BusinessException("portlet.crud.error.rest.io", e.getMessage());
        }
    }

    private ReSTChangeConfig findChangeConfig(GenericItem item) {
        ReSTChangeConfig changeConfig = item.isNewItem() ? config.getInsert() : config.getUpdate();
        return changeConfig;
    }

    private ReSTChangeMethodConfig findRequestMethod(GenericItem item) {
        ReSTChangeMethodConfig method = null;
        if (item.isNewItem()) {
            method = config.getInsert().getMethod();
            if (method == null) {
                method = ReSTChangeMethodConfig.POST;
            }
        } else {
            method = config.getUpdate().getMethod();
            if (method == null) {
                method = ReSTChangeMethodConfig.PUT;
            }
        }
        return method;
    }

    private void sendPostRequest(GenericItem item, ReSTChangeConfig changeConfig)
            throws IOException, ClientProtocolException {

        byte[] content = creator.create(item, changeConfig.getValue(), config.getCharset());
        URI uri = createURI(item, changeConfig.getUrl());
        HttpPost request = new HttpPost(uri);
        ContentType contentType = createContentType();
        request.setEntity(new ByteArrayEntity(content, contentType));

        try {
            HttpResponse response = httpClient.execute(request);

            auditLogger.auditReSTRequest(request.getMethod(), uri.toString(),
                    new String(content, config.getCharset()), response.getStatusLine().toString());

            expectAnyStatusCode(response, HttpStatus.SC_CREATED, HttpStatus.SC_NO_CONTENT);

        } finally {
            request.releaseConnection();
        }
    }

    private void sendPutRequest(GenericItem item, ReSTChangeConfig changeConfig)
            throws ClientProtocolException, IOException {

        byte[] content = creator.create(item, changeConfig.getValue(), config.getCharset());
        URI uri = createURI(item, changeConfig.getUrl());
        HttpPut request = new HttpPut(uri);
        ContentType contentType = createContentType();
        request.setEntity(new ByteArrayEntity(content, contentType));

        try {
            HttpResponse response = httpClient.execute(request);

            auditLogger.auditReSTRequest(request.getMethod(), uri.toString(),
                    new String(content, config.getCharset()), response.getStatusLine().toString());

            expectAnyStatusCode(response, HttpStatus.SC_OK, HttpStatus.SC_NO_CONTENT);

        } finally {
            request.releaseConnection();
        }
    }

    private void expectAnyStatusCode(HttpResponse response, int... acceptedStatusCodes) {

        StatusLine statusLine = response.getStatusLine();
        int statusCode = statusLine.getStatusCode();

        for (int i = 0; i < acceptedStatusCodes.length; i++) {
            if (acceptedStatusCodes[i] == statusCode) {
                return;
            }
        }
        throw new BusinessException("portlet.crud.error.rest.wrongStatus", statusCode,
                statusLine.getReasonPhrase());
    }

    private ContentType createContentType() {
        String mimetype = creator.getMimeType();
        if (config.getMimetype() != null) {
            mimetype = config.getMimetype();
        }
        return ContentType.create(mimetype, config.getCharset());
    }

    private void sendDeleteRequest(GenericItem item) throws ClientProtocolException, IOException {
        URI uri = createURI(item, config.getDelete().getUrl());
        HttpDelete request = new HttpDelete(uri);

        try {
            HttpResponse response = httpClient.execute(request);

            auditLogger.auditReSTRequest(request.getMethod(), uri.toString(), response.getStatusLine().toString());

            expectAnyStatusCode(response, HttpStatus.SC_OK, HttpStatus.SC_ACCEPTED, // marked for deletion
                    HttpStatus.SC_NO_CONTENT);

        } finally {
            request.releaseConnection();
        }
    }

    private URI createURI(GenericItem item, GroovyScript uriScript) {
        GenericContainerRowId containerRowId = new GenericContainerRowId(item.getId(),
                container.getPrimaryKeyColumns());
        GenericContainerRow containerRow = new GenericContainerRow(containerRowId, item, container, false, true);
        ScriptRow row = new ScriptRow(containerRow);
        Object postfix = scriptBuilder.buildClosure(uriScript).call(row);
        return createURI(postfix.toString());
    }

    public void setBaseUrl(final String newBaseUrl) {
        this.baseUrlProvider = new ConstantStringProvider(null, newBaseUrl);
    }

    public void setQueryUrl(final String newQueryUrl) {
        this.queryUrlProvider = new ConstantStringProvider(null, newQueryUrl);
    }
}