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

org.openmdx.base.rest.spi.AbstractRestInteraction Maven / Gradle / Ivy

There is a newer version: 2.18.10
Show newest version
/*
 * ====================================================================
 * Project:     openMDX/Core, http://www.openmdx.org/
 * Description: Abstract REST Interaction
 * Owner:       OMEX AG, Switzerland, http://www.omex.ch
 * ====================================================================
 *
 * This software is published under the BSD license as listed below.
 * 
 * Copyright (c) 2009-2017, 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.rest.spi;

import java.util.Collection;
import java.util.List;

import javax.resource.NotSupportedException;
import javax.resource.ResourceException;
import javax.resource.cci.ConnectionMetaData;
import javax.resource.cci.IndexedRecord;
import javax.resource.cci.Interaction;
import javax.resource.cci.InteractionSpec;
import javax.resource.cci.Record;

import org.openmdx.base.exception.ServiceException;
import org.openmdx.base.mof.cci.Multiplicity;
import org.openmdx.base.naming.Path;
import org.openmdx.base.resource.Records;
import org.openmdx.base.resource.spi.AbstractInteraction;
import org.openmdx.base.resource.spi.ResourceExceptions;
import org.openmdx.base.resource.spi.RestInteractionSpec;
import org.openmdx.base.rest.cci.ConsumerRecord;
import org.openmdx.base.rest.cci.MessageRecord;
import org.openmdx.base.rest.cci.ObjectRecord;
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.RestConnectionMetaData;
import org.openmdx.base.rest.cci.ResultRecord;
import org.openmdx.kernel.exception.BasicException;

/**
 * Abstract REST Interaction
 */
public class AbstractRestInteraction extends AbstractInteraction {

    /**
     * Constructor without delegate
     */
    protected AbstractRestInteraction(
        RestConnection connection
    ){
        super(connection);
    }

    /**
     * Constructor with delegate
     */
    protected AbstractRestInteraction(
    	RestConnection connection,
		Interaction delegate
	){
		super(connection, delegate);
	}

	/**
     * Provide a response path by appending "*-";
     * 
     * @param requestId
     * 
     * @return a response path 
     */
    protected Path newResponseId(
        Path requestId
    ){
        return requestId.getParent().getChild(requestId.getLastSegment().toClassicRepresentation() + "*-");
    }

    protected QueryRecord newQuery(
        Path resourceIdentifier
    ){
        QueryRecord query = new org.openmdx.base.rest.spi.QueryRecord();
        query.setResourceIdentifier(resourceIdentifier);
        return query;
    }
    
    protected ObjectRecord newObject(
        Path path
    ){
        ObjectRecord object = new org.openmdx.base.rest.spi.ObjectRecord();
        object.setResourceIdentifier(path);
        return object;
    }
    
    /**
     * Retrieve the REST connection specification
     * 
     * @return the REST ConnectionSpec
     * 
     * @throws ServiceException 
     */
    protected boolean isBulkLoad(
    ) throws ResourceException {
        ConnectionMetaData metaData = getConnection().getMetaData();
        return metaData instanceof RestConnectionMetaData && ((RestConnectionMetaData)metaData).isBulkLoad();
    }
    
    /**
     * Pass the request to another handler
     */
    protected boolean pass(
        RestInteractionSpec ispec, 
        RequestRecord input,
        Record output
    ) throws ResourceException {
        return hasDelegate() && super.execute(ispec, input, output);
    }
    
    /**
     * GET Collection
     */
    protected boolean find(
        RestInteractionSpec ispec, 
        QueryRecord input, 
        ResultRecord output
    ) throws ResourceException {
        return pass(
            ispec, 
            input,
            output
        );
    }

    /**
     * GET and consume Collection
     */
    protected boolean consume(
        RestInteractionSpec ispec, 
        QueryRecord input, 
        ConsumerRecord output
    ) throws ResourceException {
        return pass(
            ispec, 
            input,
            output
        );
    }

    
    /**
     * GET Object
     */
    protected boolean get(
        RestInteractionSpec ispec, 
        QueryRecord input, 
        ResultRecord output
    ) throws ResourceException {
        return pass(
            ispec, 
            input,
            output
        );
    }
    
    /**
     * DELETE Collection
     */
    protected boolean delete(
        RestInteractionSpec ispec, 
        QueryRecord input
    ) throws ResourceException {
        return pass(
            ispec, 
            input,
            null
        );
    }

    /**
     * Validate an object's version
     */
    protected boolean verify(
        RestInteractionSpec ispec, 
        ObjectRecord input
    ) throws ResourceException {
        return pass(
            ispec, 
            input,
            null
        );
    }
    
    /**
     * PUT Object
     */
    protected boolean update(
        RestInteractionSpec ispec, 
        ObjectRecord input, 
        ResultRecord output
    ) throws ResourceException {
        return pass(
            ispec, 
            input,
            output
        );
    }

    /**
     * DELETE Object
     */
    protected boolean delete(
        RestInteractionSpec ispec, 
        ObjectRecord input
    ) throws ResourceException {
        return pass(
            ispec, 
            input,
            null
        );
    }
    
    /**
     * Invoke Method
     */
    protected boolean invoke(
        RestInteractionSpec ispec, 
        MessageRecord input, 
        MessageRecord output
    ) throws ResourceException {
        return pass(
            ispec, 
            input,
            output
        );
    }

    /**
     * Create Object
     */
    protected boolean create(
        RestInteractionSpec ispec, 
        ObjectRecord input, 
        ResultRecord output
    ) throws ResourceException {
        return pass(
            ispec, 
            input,
            output
        );
    }
    
    /**
     * Make a transient object persistent
     * 
     * @param ispec the PUT interaction specification
     * @param input the object record
     * @param output the output record, which may be null
     * @return true if the object has been made persistent
     */
    protected boolean move(
        RestInteractionSpec ispec, 
        ObjectRecord input,
        ResultRecord output
    ) throws ResourceException {
        return pass(
            ispec, 
            input,
            output
        );
    }
    
    /**
     * Validate the record type
     * 
     * @return the validated record
     * 
     * @throws NotSupportedException
     */
    protected static  T cast(
        String usage,
        Class to,
        Object value,
        boolean optional
    ) throws NotSupportedException{
        if(to.isInstance(value) || (optional && value == null)) {
            return to.cast(value);
        }
        throw ResourceExceptions.initHolder(
            new NotSupportedException(
                "Unexpected " + (usage == null ? to.getSimpleName() : usage),
                BasicException.newEmbeddedExceptionStack(
                    BasicException.Code.DEFAULT_DOMAIN,
                    BasicException.Code.BAD_PARAMETER,
                    new BasicException.Parameter("supported", to.getName()),
                    new BasicException.Parameter("actual", value == null ? null : value.getClass().getName())
                )
            )
        );
    }

    /* (non-Javadoc)
     * @see javax.resource.cci.Interaction#execute(javax.resource.cci.InteractionSpec, javax.resource.cci.Record, javax.resource.cci.Record)
     */
    @Override
    public boolean execute(
        InteractionSpec ispec, 
        Record input, 
        Record output
    ) throws ResourceException {
    	assertOpened();
        RestInteractionSpec interactionSpec = AbstractRestInteraction.cast(
            "interaction spec", 
            RestInteractionSpec.class, 
            ispec, 
            false // optional
        );            
        if(input instanceof MessageRecord) {
            if(output == null || output instanceof MessageRecord) {
                return handleOperation(interactionSpec, (MessageRecord)input, (MessageRecord)output);
            }
        } else if (input instanceof QueryRecord) {
            if(output instanceof ConsumerRecord) {
                return handleConsumer(interactionSpec, (QueryRecord)input, (ConsumerRecord)output);
            } else if (output == null || output instanceof ResultRecord) {
                return handleQuery(interactionSpec, (QueryRecord) input, (ResultRecord)output);
            }
        } else if (input instanceof ObjectRecord) {
            if (output == null || output instanceof ResultRecord) {
                return handleObject(interactionSpec, (ObjectRecord) input, (ResultRecord)output);
            }
        }
        throw ResourceExceptions.initHolder(
    		new NotSupportedException(
				"Unexpected record ",
				BasicException.newEmbeddedExceptionStack(
					BasicException.Code.DEFAULT_DOMAIN,
					BasicException.Code.BAD_PARAMETER,
					new BasicException.Parameter("supported-input", QueryRecord.NAME, ObjectRecord.NAME, MessageRecord.NAME),
					new BasicException.Parameter("actual-input", input == null ? null : input.getRecordName()),
                    new BasicException.Parameter("supported-output", ResultRecord.NAME, ConsumerRecord.NAME),
                    new BasicException.Parameter("actual-output", output == null ? null : output.getRecordName())
				)
			)                        
		);
    }

    private boolean handleOperation(
		RestInteractionSpec interactionSpec,
		MessageRecord input,
		MessageRecord output
	) throws ResourceException {
		return invoke(
		    interactionSpec,
		    input,
		    output
		);
	}

	private boolean handleObject(
		RestInteractionSpec interactionSpec,
		ObjectRecord objectRecord, 
		ResultRecord outputRecord
	) throws ResourceException {
		return objectRecord.getTransientObjectId() == null ? handlePersistentObject(
	    	interactionSpec, 
	    	objectRecord,
			outputRecord
		) : handleTransientObject(
	    	interactionSpec, 
	    	objectRecord,
			outputRecord
		);
	}

	private boolean handleTransientObject(
		RestInteractionSpec interactionSpec,
		ObjectRecord objectRecord, 
		ResultRecord outputRecord
	) throws ResourceException {
		switch(interactionSpec.getFunction()) {
		    case POST: {
		    	final Path transientObjectId = new Path(objectRecord.getTransientObjectId());
		    	final ObjectRecord compatibilityObject = objectRecord.clone();
		    	compatibilityObject.setResourceIdentifier(transientObjectId);
		        return create(
		            interactionSpec,
		            compatibilityObject,
		            outputRecord
		        );
		    }
		    case PUT: {
				return move(
		            interactionSpec,
		            objectRecord,
		            outputRecord
		        );
		    }
		    default:
		        return false;
		}
	}

	private boolean handlePersistentObject(
		RestInteractionSpec interactionSpec,
		ObjectRecord objectRecord, 
		ResultRecord outputRecord
	) throws ResourceException {
		switch(interactionSpec.getFunction()) {
		    case GET:
		        return verify(
		            interactionSpec, 
		            objectRecord
		        );
		    case PUT: 
		        return update(
		            interactionSpec, 
		            objectRecord, 
		            outputRecord
		        );
		    case DELETE:
		        return delete(
		            interactionSpec, 
		            objectRecord
		        );
		    case POST: 
		        return create(
		            interactionSpec, 
		            objectRecord, 
		            outputRecord
		        );
		    default: 
		        return false;
		}
	}

	/**
	 * Retrieves or deletes the matching objects
	 * 
	 * @throws ResourceException
	 */
	private boolean handleQuery(
		RestInteractionSpec interactionSpec,
		QueryRecord queryRecord, 
		ResultRecord outputRecord
	) throws ResourceException {
		switch(interactionSpec.getFunction()) {
		    case GET:
		        return isCollectionRequest(queryRecord) ? find(
		            interactionSpec, 
		            queryRecord, 
		            outputRecord
		        ) : get(
		            interactionSpec, 
		            queryRecord, 
		            outputRecord
		        );
		    case DELETE: 
		        return delete(
		            interactionSpec, 
		            queryRecord
		        );
		    default: 
		        return false;
		}
	}

    /**
     * Consumes the matching objects
     * 
     * @throws ResourceException
     */
    private boolean handleConsumer(
        RestInteractionSpec interactionSpec,
        QueryRecord queryRecord,
        ConsumerRecord consumer
    ) throws ResourceException {
        switch(interactionSpec.getFunction()) {
            case GET:
                return isCollectionRequest(queryRecord) ? consume(
                    interactionSpec, 
                    queryRecord, 
                    consumer
                ) : handleSingletonConsumer(
                    interactionSpec,
                    queryRecord,
                    consumer
                );
          default:
              return false;
        }
    }

    private boolean handleSingletonConsumer(
        RestInteractionSpec interactionSpec,
        QueryRecord queryRecord,
        ConsumerRecord consumer
    ) throws ResourceException {
        final ResultRecord outputRecord = Records.getRecordFactory().createIndexedRecord(ResultRecord.class);
        final boolean success = get(
            interactionSpec, 
            queryRecord, 
            outputRecord
        );
        if(success) {
            for(Object object : outputRecord) {
                final ObjectRecord objectRecord = AbstractRestInteraction.cast(
                    "singleton object", 
                    ObjectRecord.class, 
                    object, 
                    true
                );
                consumer.accept(objectRecord);
            }
        }
        return success;
    }

    private boolean isCollectionRequest(QueryRecord queryRecord) {
        final Path xri = queryRecord.getResourceIdentifier();
        return xri.isContainerPath() || xri.isPattern();
    }
	
    /* (non-Javadoc)
     * @see org.openmdx.base.rest.spi.RestConnection#execute(javax.resource.cci.InteractionSpec, javax.resource.cci.Record)
     */
    @Override
    public Record execute(
        InteractionSpec ispec, 
        Record input
    ) throws ResourceException {
    	RestInteractionSpec interactionSpec = AbstractRestInteraction.cast(
		    "interaction spec", 
		    RestInteractionSpec.class, 
		    ispec, 
		    true
		);
        final Record reply = createReply(
        	input, 
        	interactionSpec.getInteractionVerb() == InteractionSpec.SYNC_SEND
        );
        return execute(interactionSpec, input, reply) ? reply : null;
   }

	public static Record createReply(
		Record input, 
		boolean sendOnly
	) throws ResourceException {
		if(sendOnly){
            return null;
        } else if(input instanceof MessageRecord) {
            return Records.getRecordFactory().createMappedRecord(MessageRecord.class);
        } else {
            return Records.getRecordFactory().createIndexedRecord(ResultRecord.class);
        }
	}
	
	@SuppressWarnings("unchecked")
	protected IndexedRecord toIndexedRecordList(
		List values	
	) throws ResourceException{
		final IndexedRecord target = Records.getRecordFactory().createIndexedRecord(Multiplicity.LIST.code());
		target.addAll(values);
		return target;
	}

	@SuppressWarnings("unchecked")
	protected IndexedRecord toIndexedRecordSet(
		Collection values	
	) throws ResourceException{
		final IndexedRecord target = Records.getRecordFactory().createIndexedRecord(Multiplicity.SET.code());
		target.addAll(values);
		return target;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy