com.msopentech.odatajclient.proxy.api.impl.Container.java Source code

Java tutorial

Introduction

Here is the source code for com.msopentech.odatajclient.proxy.api.impl.Container.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 com.msopentech.odatajclient.proxy.api.impl;

import com.msopentech.odatajclient.engine.client.ODataClient;
import com.msopentech.odatajclient.engine.communication.header.ODataHeaderValues;
import com.msopentech.odatajclient.engine.communication.request.UpdateType;
import com.msopentech.odatajclient.engine.communication.request.batch.ODataBatchRequest;
import com.msopentech.odatajclient.engine.communication.request.batch.ODataBatchResponseItem;
import com.msopentech.odatajclient.engine.communication.request.batch.ODataChangeset;
import com.msopentech.odatajclient.engine.communication.request.batch.ODataChangesetResponseItem;
import com.msopentech.odatajclient.engine.communication.request.cud.ODataDeleteRequest;
import com.msopentech.odatajclient.engine.communication.request.cud.ODataEntityUpdateRequest;
import com.msopentech.odatajclient.engine.communication.request.streamed.ODataMediaEntityUpdateRequest;
import com.msopentech.odatajclient.engine.communication.request.streamed.ODataStreamUpdateRequest;
import com.msopentech.odatajclient.engine.communication.response.ODataBatchResponse;
import com.msopentech.odatajclient.engine.communication.response.ODataEntityCreateResponse;
import com.msopentech.odatajclient.engine.communication.response.ODataEntityUpdateResponse;
import com.msopentech.odatajclient.engine.communication.response.ODataResponse;
import com.msopentech.odatajclient.engine.data.ODataEntity;
import com.msopentech.odatajclient.engine.data.ODataLink;
import com.msopentech.odatajclient.engine.data.ODataLinkType;
import com.msopentech.odatajclient.engine.format.ODataMediaFormat;
import com.msopentech.odatajclient.engine.uri.URIBuilder;
import com.msopentech.odatajclient.engine.utils.URIUtils;
import com.msopentech.odatajclient.proxy.api.AbstractContainer;
import com.msopentech.odatajclient.proxy.api.EntityContainerFactory;
import com.msopentech.odatajclient.proxy.api.annotations.NavigationProperty;
import com.msopentech.odatajclient.proxy.api.context.AttachedEntity;
import com.msopentech.odatajclient.proxy.api.context.AttachedEntityStatus;
import com.msopentech.odatajclient.proxy.api.context.EntityLinkDesc;
import com.msopentech.odatajclient.proxy.utils.EngineUtils;
import java.io.InputStream;
import java.lang.reflect.Proxy;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class Container implements AbstractContainer {

    private static final long serialVersionUID = -3320312269235907501L;

    /**
     * Logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(Container.class);

    private final ODataClient client;

    private final EntityContainerFactory factory;

    Container(final ODataClient client, final EntityContainerFactory factory) {
        this.client = client;
        this.factory = factory;
    }

    /**
     * Transactional changes commit.
     */
    @Override
    public void flush() {
        final ODataBatchRequest request = client.getBatchRequestFactory().getBatchRequest(factory.getServiceRoot());
        final ODataBatchRequest.BatchStreamManager streamManager = request.execute();
        final ODataChangeset changeset = streamManager.addChangeset();

        final TransactionItems items = new TransactionItems();
        final List<EntityLinkDesc> delayedUpdates = new ArrayList<EntityLinkDesc>();

        int pos = 0;

        for (AttachedEntity attachedEntity : EntityContainerFactory.getContext().entityContext()) {
            final AttachedEntityStatus status = attachedEntity.getStatus();
            if (((status != AttachedEntityStatus.ATTACHED && status != AttachedEntityStatus.LINKED)
                    || attachedEntity.getEntity().isChanged()) && !items.contains(attachedEntity.getEntity())) {
                pos++;
                pos = processEntityContext(attachedEntity.getEntity(), pos, items, delayedUpdates, changeset);
            }
        }

        processDelayedUpdates(delayedUpdates, pos, items, changeset);

        final ODataBatchResponse response = streamManager.getResponse();

        if (response.getStatusCode() != 202) {
            throw new IllegalStateException("Operation failed");
        }

        final Iterator<ODataBatchResponseItem> iter = response.getBody();

        if (!items.isEmpty()) {
            if (!iter.hasNext()) {
                throw new IllegalStateException("Unexpected operation result");
            }

            final ODataBatchResponseItem item = iter.next();
            if (!(item instanceof ODataChangesetResponseItem)) {
                throw new IllegalStateException(
                        "Unexpected batch response item " + item.getClass().getSimpleName());
            }

            final ODataChangesetResponseItem chgres = (ODataChangesetResponseItem) item;

            for (Integer changesetItemId : items.sortedValues()) {
                LOG.debug("Expected changeset item {}", changesetItemId);
                final ODataResponse res = chgres.next();
                if (res.getStatusCode() >= 400) {
                    throw new IllegalStateException("Transaction failed: " + res.getStatusMessage());
                }

                final EntityTypeInvocationHandler handler = items.get(changesetItemId);

                if (handler != null) {
                    if (res instanceof ODataEntityCreateResponse) {
                        LOG.debug("Upgrade created object '{}'", handler);
                        handler.setEntity(((ODataEntityCreateResponse) res).getBody());
                    } else if (res instanceof ODataEntityUpdateResponse) {
                        LOG.debug("Upgrade updated object '{}'", handler);
                        handler.setEntity(((ODataEntityUpdateResponse) res).getBody());
                    }
                }
            }
        }

        EntityContainerFactory.getContext().detachAll();
    }

    private void batch(final EntityTypeInvocationHandler handler, final ODataEntity entity,
            final ODataChangeset changeset) {

        switch (EntityContainerFactory.getContext().entityContext().getStatus(handler)) {
        case NEW:
            batchCreate(handler, entity, changeset);
            break;

        case CHANGED:
            batchUpdate(handler, entity, changeset);
            break;

        case DELETED:
            batchDelete(handler, entity, changeset);
            break;

        default:
            if (handler.isChanged()) {
                batchUpdate(handler, entity, changeset);
            }
        }
    }

    private void batchCreate(final EntityTypeInvocationHandler handler, final ODataEntity entity,
            final ODataChangeset changeset) {

        LOG.debug("Create '{}'", handler);

        final URIBuilder uriBuilder = client.getURIBuilder(factory.getServiceRoot())
                .appendEntitySetSegment(handler.getEntitySetName());
        changeset.addRequest(client.getCUDRequestFactory().getEntityCreateRequest(uriBuilder.build(), entity));
    }

    private void batchUpdateMediaEntity(final EntityTypeInvocationHandler handler, final URI uri,
            final InputStream input, final ODataChangeset changeset) {

        LOG.debug("Update media entity '{}'", uri);

        final ODataMediaEntityUpdateRequest req = client.getStreamedRequestFactory()
                .getMediaEntityUpdateRequest(uri, input);

        req.setContentType(StringUtils.isBlank(handler.getEntity().getMediaContentType())
                ? ODataMediaFormat.WILDCARD.toString()
                : ODataMediaFormat.fromFormat(handler.getEntity().getMediaContentType()).toString());

        if (StringUtils.isNotBlank(handler.getETag())) {
            req.setIfMatch(handler.getETag());
        }

        changeset.addRequest(req);
    }

    private void batchUpdateMediaResource(final EntityTypeInvocationHandler handler, final URI uri,
            final InputStream input, final ODataChangeset changeset) {

        LOG.debug("Update media entity '{}'", uri);

        final ODataStreamUpdateRequest req = client.getStreamedRequestFactory().getStreamUpdateRequest(uri, input);

        if (StringUtils.isNotBlank(handler.getETag())) {
            req.setIfMatch(handler.getETag());
        }

        changeset.addRequest(req);
    }

    private void batchUpdate(final EntityTypeInvocationHandler handler, final ODataEntity changes,
            final ODataChangeset changeset) {

        LOG.debug("Update '{}'", changes.getEditLink());

        final ODataEntityUpdateRequest req = client.getCUDRequestFactory().getEntityUpdateRequest(UpdateType.PATCH,
                changes);
        req.setPrefer(ODataHeaderValues.preferReturnContent);

        if (StringUtils.isNotBlank(handler.getETag())) {
            req.setIfMatch(handler.getETag());
        }

        changeset.addRequest(req);
    }

    private void batchUpdate(final EntityTypeInvocationHandler handler, final URI uri, final ODataEntity changes,
            final ODataChangeset changeset) {

        LOG.debug("Update '{}'", uri);

        final ODataEntityUpdateRequest req = client.getCUDRequestFactory().getEntityUpdateRequest(uri,
                UpdateType.PATCH, changes);
        req.setPrefer(ODataHeaderValues.preferReturnContent);

        if (StringUtils.isNotBlank(handler.getETag())) {
            req.setIfMatch(handler.getETag());
        }

        changeset.addRequest(req);
    }

    private void batchDelete(final EntityTypeInvocationHandler handler, final ODataEntity entity,
            final ODataChangeset changeset) {

        LOG.debug("Delete '{}'", entity.getEditLink());

        final ODataDeleteRequest req = client.getCUDRequestFactory()
                .getDeleteRequest(URIUtils.getURI(factory.getServiceRoot(), entity.getEditLink().toASCIIString()));

        if (StringUtils.isNotBlank(handler.getETag())) {
            req.setIfMatch(handler.getETag());
        }

        changeset.addRequest(req);
    }

    private int processEntityContext(final EntityTypeInvocationHandler handler, int pos,
            final TransactionItems items, final List<EntityLinkDesc> delayedUpdates,
            final ODataChangeset changeset) {

        LOG.debug("Process '{}'", handler);

        items.put(handler, null);

        final ODataEntity entity = handler.getEntity();
        entity.getNavigationLinks().clear();

        final AttachedEntityStatus currentStatus = EntityContainerFactory.getContext().entityContext()
                .getStatus(handler);

        if (AttachedEntityStatus.DELETED != currentStatus) {
            entity.getProperties().clear();
            EngineUtils.addProperties(client, factory.getMetadata(), handler.getPropertyChanges(), entity);
        }

        for (Map.Entry<NavigationProperty, Object> property : handler.getLinkChanges().entrySet()) {
            final ODataLinkType type = Collection.class.isAssignableFrom(property.getValue().getClass())
                    ? ODataLinkType.ENTITY_SET_NAVIGATION
                    : ODataLinkType.ENTITY_NAVIGATION;

            final Set<EntityTypeInvocationHandler> toBeLinked = new HashSet<EntityTypeInvocationHandler>();
            final String serviceRoot = factory.getServiceRoot();

            for (Object proxy : type == ODataLinkType.ENTITY_SET_NAVIGATION ? (Collection) property.getValue()
                    : Collections.singleton(property.getValue())) {

                final EntityTypeInvocationHandler target = (EntityTypeInvocationHandler) Proxy
                        .getInvocationHandler(proxy);

                final AttachedEntityStatus status = EntityContainerFactory.getContext().entityContext()
                        .getStatus(target);

                final URI editLink = target.getEntity().getEditLink();

                if ((status == AttachedEntityStatus.ATTACHED || status == AttachedEntityStatus.LINKED)
                        && !target.isChanged()) {
                    entity.addLink(buildNavigationLink(property.getKey().name(),
                            URIUtils.getURI(serviceRoot, editLink.toASCIIString()), type));
                } else {
                    if (!items.contains(target)) {
                        pos = processEntityContext(target, pos, items, delayedUpdates, changeset);
                        pos++;
                    }

                    final Integer targetPos = items.get(target);
                    if (targetPos == null) {
                        // schedule update for the current object
                        LOG.debug("Schedule '{}' from '{}' to '{}'", type.name(), handler, target);
                        toBeLinked.add(target);
                    } else if (status == AttachedEntityStatus.CHANGED) {
                        entity.addLink(buildNavigationLink(property.getKey().name(),
                                URIUtils.getURI(serviceRoot, editLink.toASCIIString()), type));
                    } else {
                        // create the link for the current object
                        LOG.debug("'{}' from '{}' to (${}) '{}'", type.name(), handler, targetPos, target);

                        entity.addLink(
                                buildNavigationLink(property.getKey().name(), URI.create("$" + targetPos), type));
                    }
                }
            }

            if (!toBeLinked.isEmpty()) {
                delayedUpdates.add(new EntityLinkDesc(property.getKey().name(), handler, toBeLinked, type));
            }
        }

        // insert into the batch
        LOG.debug("{}: Insert '{}' into the batch", pos, handler);
        batch(handler, entity, changeset);

        items.put(handler, pos);

        int startingPos = pos;

        if (handler.getEntity().isMediaEntity()) {

            // update media properties
            if (!handler.getPropertyChanges().isEmpty()) {
                final URI targetURI = currentStatus == AttachedEntityStatus.NEW ? URI.create("$" + startingPos)
                        : URIUtils.getURI(factory.getServiceRoot(),
                                handler.getEntity().getEditLink().toASCIIString());
                batchUpdate(handler, targetURI, entity, changeset);
                pos++;
                items.put(handler, pos);
            }

            // update media content
            if (handler.getStreamChanges() != null) {
                final URI targetURI = currentStatus == AttachedEntityStatus.NEW
                        ? URI.create("$" + startingPos + "/$value")
                        : URIUtils.getURI(factory.getServiceRoot(),
                                handler.getEntity().getEditLink().toASCIIString() + "/$value");

                batchUpdateMediaEntity(handler, targetURI, handler.getStreamChanges(), changeset);

                // update media info (use null key)
                pos++;
                items.put(null, pos);
            }
        }

        for (Map.Entry<String, InputStream> streamedChanges : handler.getStreamedPropertyChanges().entrySet()) {
            final URI targetURI = currentStatus == AttachedEntityStatus.NEW ? URI.create("$" + startingPos)
                    : URIUtils.getURI(factory.getServiceRoot(),
                            EngineUtils.getEditMediaLink(streamedChanges.getKey(), entity).toASCIIString());

            batchUpdateMediaResource(handler, targetURI, streamedChanges.getValue(), changeset);

            // update media info (use null key)
            pos++;
            items.put(handler, pos);
        }

        return pos;
    }

    private ODataLink buildNavigationLink(final String name, final URI uri, final ODataLinkType type) {
        switch (type) {
        case ENTITY_NAVIGATION:
            return client.getObjectFactory().newEntityNavigationLink(name, uri);

        case ENTITY_SET_NAVIGATION:
            return client.getObjectFactory().newFeedNavigationLink(name, uri);

        default:
            throw new IllegalArgumentException("Invalid link type " + type.name());
        }
    }

    private void processDelayedUpdates(final List<EntityLinkDesc> delayedUpdates, int pos,
            final TransactionItems items, final ODataChangeset changeset) {

        for (EntityLinkDesc delayedUpdate : delayedUpdates) {
            pos++;
            items.put(delayedUpdate.getSource(), pos);

            final ODataEntity changes = client.getObjectFactory()
                    .newEntity(delayedUpdate.getSource().getEntity().getName());

            AttachedEntityStatus status = EntityContainerFactory.getContext().entityContext()
                    .getStatus(delayedUpdate.getSource());

            final URI sourceURI;
            if (status == AttachedEntityStatus.CHANGED) {
                sourceURI = URIUtils.getURI(factory.getServiceRoot(),
                        delayedUpdate.getSource().getEntity().getEditLink().toASCIIString());
            } else {
                int sourcePos = items.get(delayedUpdate.getSource());
                sourceURI = URI.create("$" + sourcePos);
            }

            for (EntityTypeInvocationHandler target : delayedUpdate.getTargets()) {
                status = EntityContainerFactory.getContext().entityContext().getStatus(target);

                final URI targetURI;
                if (status == AttachedEntityStatus.CHANGED) {
                    targetURI = URIUtils.getURI(factory.getServiceRoot(),
                            target.getEntity().getEditLink().toASCIIString());
                } else {
                    int targetPos = items.get(target);
                    targetURI = URI.create("$" + targetPos);
                }

                changes.addLink(delayedUpdate.getType() == ODataLinkType.ENTITY_NAVIGATION
                        ? client.getObjectFactory().newEntityNavigationLink(delayedUpdate.getSourceName(),
                                targetURI)
                        : client.getObjectFactory().newFeedNavigationLink(delayedUpdate.getSourceName(),
                                targetURI));

                LOG.debug("'{}' from {} to {}",
                        new Object[] { delayedUpdate.getType().name(), sourceURI, targetURI });
            }

            batchUpdate(delayedUpdate.getSource(), sourceURI, changes, changeset);
        }
    }

    private class TransactionItems {

        private final List<EntityTypeInvocationHandler> keys = new ArrayList<EntityTypeInvocationHandler>();

        private final List<Integer> values = new ArrayList<Integer>();

        public EntityTypeInvocationHandler get(final Integer value) {
            if (value != null && values.contains(value)) {
                return keys.get(values.indexOf(value));
            } else {
                return null;
            }
        }

        public Integer get(final EntityTypeInvocationHandler key) {
            if (key != null && keys.contains(key)) {
                return values.get(keys.indexOf(key));
            } else {
                return null;
            }
        }

        public void remove(final EntityTypeInvocationHandler key) {
            if (keys.contains(key)) {
                values.remove(keys.indexOf(key));
                keys.remove(key);
            }
        }

        public void put(final EntityTypeInvocationHandler key, final Integer value) {
            // replace just in case of null current value; otherwise add the new entry
            if (key != null && keys.contains(key) && values.get(keys.indexOf(key)) == null) {
                remove(key);
            }
            keys.add(key);
            values.add(value);
        }

        public List<Integer> sortedValues() {
            final List<Integer> sortedValues = new ArrayList<Integer>(values);
            Collections.<Integer>sort(sortedValues);
            return sortedValues;
        }

        public boolean contains(final EntityTypeInvocationHandler key) {
            return keys.contains(key);
        }

        public int size() {
            return keys.size();
        }

        public boolean isEmpty() {
            return keys.isEmpty();
        }
    }
}