All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openmdx.base.dataprovider.cci.DataproviderRequestProcessor Maven / Gradle / Ivy

There is a newer version: 2.18.10
Show newest version
/*
 * ====================================================================
 * Project:     openMDX, http://www.openmdx.org/
 * Description: Dataprovider Request Processor
 * Owner:       OMEX AG, Switzerland, http://www.omex.ch
 * ====================================================================
 *
 * This software is published under the BSD license as listed below.
 * 
 * Copyright (c) 2004-2014, OMEX AG, Switzerland
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 * 
 * * Neither the name of the openMDX team nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * ------------------
 * 
 * This product includes software developed by other organizations as
 * listed in the NOTICE file.
 */
package org.openmdx.base.dataprovider.cci;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

import javax.resource.ResourceException;
import javax.resource.cci.Interaction;
import javax.resource.cci.MappedRecord;
import javax.resource.cci.Record;

import org.openmdx.application.dataprovider.cci.AttributeSpecifier;
import org.openmdx.application.dataprovider.cci.FilterProperty;
import org.openmdx.base.exception.RuntimeServiceException;
import org.openmdx.base.naming.Path;
import org.openmdx.base.query.Condition;
import org.openmdx.base.query.OrderSpecifier;
import org.openmdx.base.resource.InteractionSpecs;
import org.openmdx.base.resource.Records;
import org.openmdx.base.resource.spi.Port;
import org.openmdx.base.resource.spi.ResourceExceptions;
import org.openmdx.base.resource.spi.RestInteractionSpec;
import org.openmdx.base.rest.cci.MessageRecord;
import org.openmdx.base.rest.cci.ObjectRecord;
import org.openmdx.base.rest.cci.QueryFilterRecord;
import org.openmdx.base.rest.cci.QueryRecord;
import org.openmdx.base.rest.cci.RequestRecord;
import org.openmdx.base.rest.cci.RestConnection;
import org.openmdx.base.rest.cci.RestConnectionSpec;
import org.openmdx.base.rest.cci.ResultRecord;
import org.openmdx.base.rest.spi.AbstractMappedRecord;
import org.openmdx.kernel.exception.BasicException;
import org.openmdx.kernel.id.UUIDs;

/**
 * Convenience class to create and execute REST requests
 */
public final class DataproviderRequestProcessor implements Channel {

    /**
     * Constructor
     */
    public DataproviderRequestProcessor(
        List principalChain,
        Port port) throws ResourceException {
        this(new DataproviderRequestConnectionFactory(port, newConnectionSpec(principalChain)));
    }

    /**
     * Constructor
     * 
     * @param connectionFactory
     *            the JCA connection factory
     * 
     * 
     * @throws ResourceException
     */
    private DataproviderRequestProcessor(
        DataproviderRequestConnectionFactory connectionFactory) throws ResourceException {
        this(connectionFactory.getConnection());
    }

    /**
     * Constructor
     * 
     * @param connection
     *            the JCA connection
     */
    private DataproviderRequestProcessor(
        DataproviderRequestConnection connection) {
        this.connection = connection;
        this.pending = null;
    }

    /**
     * The request processor's REST connection
     */
    private final DataproviderRequestConnection connection;

    /**
     * The pending request futures in batching mode
     */
    private List pending;

    private static final InteractionSpecs INTERACTION_SPECS = InteractionSpecs.getRestInteractionSpecs(false);

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#isBatching()
     */
    @Override
    public boolean isBatching() {
        return this.pending != null;
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#beginBatch()
     */
    @Override
    public void beginBatch() throws ResourceException {
        if (isBatching()) {
            throw ResourceExceptions.initHolder(
                new javax.resource.spi.IllegalStateException(
                    "Request processor is already in batching mode", BasicException.newEmbeddedExceptionStack(
                        BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.ILLEGAL_STATE
                    )
                )
            );
        }
        this.pending = new ArrayList();
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#endBatch()
     */
    @Override
    public boolean endBatch() throws ResourceException {
        if (!this.isBatching()) {
            throw ResourceExceptions.initHolder(
                new javax.resource.spi.IllegalStateException(
                    "Request processor is not in batching mode", BasicException.newEmbeddedExceptionStack(
                        BasicException.Code.DEFAULT_DOMAIN, BasicException.Code.ILLEGAL_STATE
                    )
                )
            );
        }
        final Interaction interaction = this.connection.createInteraction();
        boolean overall = true;
        for (Pending pending: this.pending) {
            final boolean success = interaction.execute(pending.interactionSpec, pending.request, pending.response);
            pending.onCompletion(success);
            overall &= success;
        }
        interaction.close();
        this.pending = null;
        return overall;
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#forgetBatch()
     */
    @Override
    public void forgetBatch() {
        this.pending = null;
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#addGetRequest(org.openmdx.base.naming.Path)
     */
    @Override
    public ObjectRecord addGetRequest(
        Path resourceIdentifier) throws ResourceException {
        return this.addGetRequest(newQueryRecord(resourceIdentifier));
    }

    public ObjectRecord addGetRequest(
        Path resourceIdentifier,
        String fetchGroupName) throws ResourceException {
        final QueryRecord request = newQueryRecord(resourceIdentifier);
        request.setFetchGroupName(fetchGroupName);
        return this.addGetRequest(request);
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#addGetRequest(org.openmdx.base.rest.cci.QueryRecord)
     */
    @Override
    public ObjectRecord addGetRequest(
        QueryRecord request) throws ResourceException {
        final Pending requestFuture = new Pending(INTERACTION_SPECS.GET, request, newResultRecord());
        final ObjectRecord objectFuture = requestFuture.getObjectFuture();
        if (this.isBatching()) {
            this.pending.add(requestFuture);
            return objectFuture;
        } else {
            final Interaction interaction = this.connection.createInteraction();
            final boolean success = requestFuture.execute(interaction);
            interaction.close();
            return success ? objectFuture : null;
        }
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#addCreateRequest(org.openmdx.base.rest.cci.ObjectRecord)
     */
    @Override
    public void addCreateRequest(
        ObjectRecord object) throws ResourceException {
        this.addSendOnlyRequest(INTERACTION_SPECS.CREATE, object);
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#addUpdateRequest(org.openmdx.base.rest.cci.ObjectRecord)
     */
    @Override
    public void addUpdateRequest(
        ObjectRecord object) throws ResourceException {
        this.addSendOnlyRequest(INTERACTION_SPECS.UPDATE, object);
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#addRemoveRequest(org.openmdx.base.naming.Path)
     */
    @Override
    public void addRemoveRequest(
        Path path) throws ResourceException {
        this.addSendOnlyRequest(INTERACTION_SPECS.DELETE, newQueryRecord(path));
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#addFindRequest(org.openmdx.base.naming.Path)
     */
    @Override
    public ResultRecord addFindRequest(
        Path referenceFilter) throws ResourceException {
        final QueryRecord request = newQueryRecord(referenceFilter);
        return addFindRequest(request);
    }

    public ResultRecord addFindRequest(
        Path referenceFilter,
        String fetchGroupName) throws ResourceException {
        final QueryRecord request = newQueryRecord(referenceFilter);
        request.setFetchGroupName(fetchGroupName);
        return addFindRequest(request);
    }

    /**
     * Adds a find request selecting all objects where the
     * referenceFilter as well as the
     * attributeFilter evaluate to true.
     * No attributes are included.
     *
     * @param referenceFilter
     *            an object may be included into the result sets only if it
     *            is accessible through the path passed as
     *            referenceFilter
     * @param attributeFilter
     *            an object may be included into the result sets only if all
     *            the filter properties evaluate to true if applied to it;
     *            this argument may be null.
     *
     * @return the reply
     *
     * @exception ResourceException
     *                if the request fails
     * 
     * @deprecated avoid {@link org.openmdx.application.dataprovider.cci.FilterProperty}
     */
    @Deprecated
    public ResultRecord addFindRequest(
        Path referenceFilter,
        FilterProperty[] attributeFilter) throws ResourceException {
        final QueryRecord request = newQueryRecordWithFilter(referenceFilter);
        final List condition = FilterProperty.toCondition(attributeFilter);
        request.getQueryFilter().getCondition().addAll(condition);
        return addFindRequest(request);
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#addFindRequest(org.openmdx.base.rest.cci.QueryRecord)
     */
    @Override
    public ResultRecord addFindRequest(
        QueryRecord request) throws ResourceException {
        final ResultRecord response = Records.getRecordFactory().createIndexedRecord(ResultRecord.class);
        addSendReceiveRequest(INTERACTION_SPECS.GET, request, response);
        return response;
    }

    public void addFindRequest(
        QueryRecord request,
        ResultRecord response) throws ResourceException {
        addSendReceiveRequest(INTERACTION_SPECS.GET, request, response);
    }

    /**
     * Adds a find request selecting all objects where the
     * referenceFilter as well as the
     * attributeFilter evaluate to true.
     *
     * @param referenceFilter
     *            an object may be included into the result sets only if it
     *            is accessible through the path passed as
     *            referenceFilter
     * @param attributeFilter
     *            an object may be included into the result sets only if all
     *            the filter properties evaluate to true if applied to it;
     *            this argument may be null.
     * @param attributeSpecifiers
     *            An array of attribute specifiers
     *
     * @return the reply
     *
     * @exception ResourceException
     *                if the request fails
     * 
     * @deprecated avoid {@link org.openmdx.application.dataprovider.cci.FilterProperty} and
     *             {@link org.openmdx.application.dataprovider.cci.AttributeSpecifier}
     */
    @Deprecated
    public ResultRecord addFindRequest(
        Path referenceFilter,
        FilterProperty[] attributeFilter,
        AttributeSpecifier[] attributeSpecifiers) throws ResourceException {
        final QueryRecord request = newQueryRecordWithFilter(referenceFilter);
        final List condition = FilterProperty.toCondition(attributeFilter);
        final List order = AttributeSpecifier.toOrderSpecifier(attributeSpecifiers);
        request.getQueryFilter().getCondition().addAll(condition);
        request.getQueryFilter().getOrderSpecifier().addAll(order);
        return addFindRequest(request);
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#addOperationRequest(org.openmdx.base.rest.cci.MessageRecord)
     */
    @Override
    public MessageRecord addOperationRequest(
        MessageRecord request) throws ResourceException {
        final MessageRecord response = newMessageRecord();
        if (request.getMessageId() == null) {
            request.setResourceIdentifier(request.getResourceIdentifier().getChild(uuidAsString()));
        }
        addSendReceiveRequest(INTERACTION_SPECS.INVOKE, request, response);
        return response;
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#addSendReceiveRequest(org.openmdx.base.resource.spi.RestInteractionSpec, org.openmdx.base.rest.cci.RequestRecord, javax.resource.cci.Record)
     */
    @Override
    public void addSendReceiveRequest(
        RestInteractionSpec interactionSpec,
        RequestRecord request,
        Record response) throws ResourceException {
        if (this.isBatching()) {
            this.pending.add(new Pending(interactionSpec, request, response));
        } else {
            final Interaction interaction = this.connection.createInteraction();
            interaction.execute(interactionSpec, request, response);
            interaction.close();
        }
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#addSendOnlyRequest(org.openmdx.base.resource.spi.RestInteractionSpec, org.openmdx.base.rest.cci.RequestRecord)
     */
    @Override
    public void addSendOnlyRequest(
        RestInteractionSpec interactionSpec,
        RequestRecord request) throws ResourceException {
        if (this.isBatching()) {
            this.pending.add(new Pending(interactionSpec, request));
        } else {
            final Interaction interaction = this.connection.createInteraction();
            interaction.execute(interactionSpec, request);
            interaction.close();
        }
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#clone()
     */
    @Override
    public Object clone() {
        return new DataproviderRequestProcessor(this.connection);
    }

    protected final String uuidAsString() {
        return UUIDs.newUUID().toString();
    }

    private  T newRequestRecord(
        Class type,
        Path resourceIdentifier) throws ResourceException {
        final T requestRecord = Records.getRecordFactory().createMappedRecord(type);
        requestRecord.setResourceIdentifier(resourceIdentifier);
        return requestRecord;
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#newObjectRecord(org.openmdx.base.naming.Path, java.lang.String)
     */
    @Override
    public ObjectRecord newObjectRecord(
        Path resourceIdentifier,
        String type) throws ResourceException {
        final ObjectRecord objectRecord = newRequestRecord(ObjectRecord.class, resourceIdentifier);
        objectRecord.setValue(Records.getRecordFactory().createMappedRecord(type));
        return objectRecord;
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#newQueryRecord(org.openmdx.base.naming.Path)
     */
    @Override
    public final QueryRecord newQueryRecord(
        Path resourceIdentifier) throws ResourceException {
        return newRequestRecord(QueryRecord.class, resourceIdentifier);
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#newQueryRecordWithFilter(org.openmdx.base.naming.Path)
     */
    @Override
    public final QueryRecord newQueryRecordWithFilter(
        Path resourceIdentifier) throws ResourceException {
        final QueryRecord queryRecord = newQueryRecord(resourceIdentifier);
        QueryFilterRecord queryFilter = Records.getRecordFactory().createMappedRecord(QueryFilterRecord.class);
        queryRecord.setQueryFilter(queryFilter);
        return queryRecord;
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#newResultRecord()
     */
    @Override
    public final ResultRecord newResultRecord() throws ResourceException {
        return Records.getRecordFactory().createIndexedRecord(ResultRecord.class);
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#newMessageRecord()
     */
    @Override
    public final MessageRecord newMessageRecord() throws ResourceException {
        return Records.getRecordFactory().createMappedRecord(MessageRecord.class);
    }

    /* (non-Javadoc)
     * @see org.openmdx.base.dataprovider.cci.Channel#newMessageRecord()
     */
    @Override
    public final MessageRecord newMessageRecord(
        Path resourceIdentifier) throws ResourceException {
        final MessageRecord messageRecord = newMessageRecord();
        messageRecord.setResourceIdentifier(resourceIdentifier);
        return messageRecord;
    }

    private static RestConnectionSpec newConnectionSpec(
        List principalChain) {
        List userName = principalChain == null ? Collections.singletonList(System.getProperty("user.name")) : principalChain;
        return new RestConnectionSpec(userName.toString(), null);
    }

    // ------------------------------------------------------------------------
    // Class Pending
    // ------------------------------------------------------------------------

    protected static final class Pending {

        /**
         * Constructor
         */
        Pending(
            RestInteractionSpec interactionSpec,
            RequestRecord request) throws ResourceException {
            this(interactionSpec, request, null);
        }

        /**
         * Constructor
         */
        Pending(
            RestInteractionSpec interactionSpec,
            RequestRecord request,
            Record response) {
            this.interactionSpec = interactionSpec;
            this.request = request;
            this.response = response;
        }

        final RestInteractionSpec interactionSpec;
        final RequestRecord request;
        final Record response;
        private Boolean success;
        private ObjectFuture objectFuture;

        boolean execute(
            Interaction interaction) throws ResourceException {
            onCompletion(interaction.execute(interactionSpec, request, response));
            return success.booleanValue();
        }

        void onCompletion(
            boolean success) {
            this.success = Boolean.valueOf(success);
            if (this.objectFuture != null && response instanceof ResultRecord) {
                final ResultRecord resultRecord = (ResultRecord) response;
                if (!resultRecord.isEmpty()) {
                    this.objectFuture.setDelegate((ObjectRecord) resultRecord.get(0));
                }
            }
        }

        ObjectRecord getObjectFuture() {
            if (this.objectFuture == null) {
                this.objectFuture = new ObjectFuture();
            }
            return this.objectFuture;
        }

        @SuppressWarnings({"rawtypes", "synthetic-access"})
        class ObjectFuture extends AbstractMappedRecord implements ObjectRecord {

            private Members members;
            private ObjectRecord delegate;
            private static final long serialVersionUID = 2587154012169349226L;

            ObjectRecord getDelegate() {
                if (success == null) {
                    throw new IllegalStateException(
                        "The REST request has not been executed yet", 
                        BasicException.newEmbeddedExceptionStack(
                            BasicException.Code.DEFAULT_DOMAIN, 
                            BasicException.Code.ILLEGAL_STATE, 
                            new BasicException.Parameter(
                                "interactionVerb", 
                                interactionSpec.getInteractionVerbName()
                            ), new BasicException.Parameter(
                                "function", 
                                interactionSpec.getFunctionName()
                            ), new BasicException.Parameter(
                                BasicException.Parameter.XRI, 
                                request.getResourceIdentifier()
                            )
                        )
                    );
                }
                if (delegate == null) {
                    throw new RuntimeServiceException(
                        BasicException.Code.DEFAULT_DOMAIN, 
                        BasicException.Code.NO_RESPONSE,
                        "The REST request's result record did not contain an ObjectRecord", 
                        new BasicException.Parameter(
                            "interactionVerb", 
                            interactionSpec.getInteractionVerbName()
                        ), new BasicException.Parameter(
                            "function", 
                            interactionSpec.getFunctionName()
                        ), new BasicException.Parameter(
                            BasicException.Parameter.XRI, 
                            request.getResourceIdentifier()
                        ), new BasicException.Parameter(
                            "success", 
                            success
                        )
                    );
                }
                return delegate;
            }

            void setDelegate(
                ObjectRecord delegate) {
                this.delegate = delegate;
            }

            @Override
            public Path getResourceIdentifier() {
                return getDelegate().getResourceIdentifier();
            }

            @Override
            public void setResourceIdentifier(
                Path resourceIdentifier) {
                getDelegate().setResourceIdentifier(resourceIdentifier);
            }

            @Override
            public String getRecordName() {
                return getDelegate().getRecordName();
            }

            @Override
            public MappedRecord getValue() {
                return getDelegate().getValue();
            }

            @Override
            public void setValue(
                MappedRecord value) {
                getDelegate().setValue(value);
            }

            @Override
            public byte[] getVersion() {
                return getDelegate().getVersion();
            }

            @Override
            public void setVersion(
                byte[] version) {
                getDelegate().setVersion(version);
            }

            @Override
            public Object getLock() {
                return getDelegate().getLock();
            }

            @Override
            public void setLock(
                Object lock) {
                getDelegate().setLock(lock);
            }

            @Override
            public UUID getTransientObjectId() {
                return getDelegate().getTransientObjectId();
            }

            @Override
            public void setTransientObjectId(
                UUID transientObjectId) {
                getDelegate().setTransientObjectId(transientObjectId);
            }

            @Override
            public org.openmdx.base.rest.spi.ObjectRecord clone() {
                return ((org.openmdx.base.rest.spi.ObjectRecord) getDelegate()).clone();
            }

            @Override
            protected Members members() {
                if (this.members == null) {
                    this.members = Members.newInstance(Member.class);
                }
                return members;
            }

        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy