org.apache.olingo.ext.proxy.commons.AbstractPersistenceManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.olingo.ext.proxy.commons.AbstractPersistenceManager.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 org.apache.olingo.ext.proxy.commons;

import java.lang.reflect.InvocationHandler;
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.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;

import org.apache.commons.lang3.StringUtils;
import org.apache.olingo.client.api.EdmEnabledODataClient;
import org.apache.olingo.client.api.communication.header.ODataPreferences;
import org.apache.olingo.client.api.communication.request.cud.ODataDeleteRequest;
import org.apache.olingo.client.api.communication.request.cud.ODataEntityUpdateRequest;
import org.apache.olingo.client.api.communication.request.cud.ODataReferenceAddingRequest;
import org.apache.olingo.client.api.communication.request.streamed.ODataMediaEntityUpdateRequest;
import org.apache.olingo.client.api.communication.request.streamed.ODataStreamUpdateRequest;
import org.apache.olingo.client.core.uri.URIUtils;
import org.apache.olingo.client.api.domain.ClientEntity;
import org.apache.olingo.client.api.domain.ClientLink;
import org.apache.olingo.client.api.domain.ClientLinkType;
import org.apache.olingo.ext.proxy.AbstractService;
import org.apache.olingo.ext.proxy.api.EdmStreamValue;
import org.apache.olingo.ext.proxy.api.PersistenceManager;
import org.apache.olingo.ext.proxy.api.annotations.NavigationProperty;
import org.apache.olingo.ext.proxy.context.AttachedEntity;
import org.apache.olingo.ext.proxy.context.AttachedEntityStatus;
import org.apache.olingo.ext.proxy.context.EntityLinkDesc;
import org.apache.olingo.ext.proxy.utils.ClassUtils;
import org.apache.olingo.ext.proxy.utils.CoreUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

abstract class AbstractPersistenceManager implements PersistenceManager {

    /**
     * Logger.
     */
    protected static final Logger LOG = LoggerFactory.getLogger(AbstractPersistenceManager.class);

    private static final long serialVersionUID = 2065240290461241515L;

    protected final AbstractService<?> service;

    AbstractPersistenceManager(final AbstractService<?> factory) {
        this.service = factory;
    }

    @Override
    public Future<Void> flushAsync() {
        return service.getClient().getConfiguration().getExecutor().submit(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                flush();
                return ClassUtils.returnVoid();
            }
        });
    }

    protected abstract void doFlush(PersistenceChanges changes, TransactionItems items);

    @Override
    public void flush() {
        final PersistenceChanges changes = new PersistenceChanges();
        final TransactionItems items = new TransactionItems();

        int pos = 0;
        final List<EntityLinkDesc> delayedUpdates = new ArrayList<EntityLinkDesc>();
        for (AttachedEntity attachedEntity : service.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, changes);
            }
        }

        processDelayedUpdates(delayedUpdates, pos, items, changes);

        // remove null values
        items.normalize();

        for (URI uri : service.getContext().entityContext().getFurtherDeletes()) {
            pos++;
            queueDelete(uri, null, changes);
            items.put(null, pos);
        }

        if (!items.isEmpty()) {
            doFlush(changes, items);
        }

        service.getContext().detachAll();
    }

    private ClientLink buildNavigationLink(final String name, final URI uri, final ClientLinkType type) {
        ClientLink result;

        switch (type) {
        case ENTITY_NAVIGATION:
            result = service.getClient().getObjectFactory().newEntityNavigationLink(name, uri);
            break;

        case ENTITY_SET_NAVIGATION:
            result = service.getClient().getObjectFactory().newEntitySetNavigationLink(name, uri);
            break;

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

        return result;
    }

    protected int processEntityContext(final EntityInvocationHandler handler, int pos, final TransactionItems items,
            final List<EntityLinkDesc> delayedUpdates, final PersistenceChanges changeset) {
        int posNumber = pos;
        items.put(handler, null);

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

        final AttachedEntityStatus currentStatus = service.getContext().entityContext().getStatus(handler);
        LOG.debug("Process '{}({})'", handler, currentStatus);

        if (AttachedEntityStatus.DELETED != currentStatus) {
            entity.getProperties().clear();
            CoreUtils.addProperties(service.getClient(), handler.getPropertyChanges(), entity);

            entity.getAnnotations().clear();
            CoreUtils.addAnnotations(service.getClient(), handler.getAnnotations(), entity);

            for (Map.Entry<String, AnnotatableInvocationHandler> entry : handler.getPropAnnotatableHandlers()
                    .entrySet()) {
                CoreUtils.addAnnotations(service.getClient(), entry.getValue().getAnnotations(),
                        entity.getProperty(entry.getKey()));
            }
        }

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

            final Set<EntityInvocationHandler> toBeLinked = new HashSet<EntityInvocationHandler>();

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

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

                final AttachedEntityStatus status;
                if (!service.getContext().entityContext().isAttached(target)) {
                    status = resolveNavigationLink(property.getKey(), target);
                } else {
                    status = service.getContext().entityContext().getStatus(target);
                }

                LOG.debug("Found link to '{}({})'", target, status);

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

                if ((status == AttachedEntityStatus.ATTACHED || status == AttachedEntityStatus.LINKED)
                        && !target.isChanged()) {
                    LOG.debug("Add link to '{}'", target);
                    entity.addLink(buildNavigationLink(property.getKey().name(),
                            URIUtils.getURI(service.getClient().getServiceRoot(), editLink.toASCIIString()), type));
                } else {
                    if (!items.contains(target)) {
                        posNumber = processEntityContext(target, posNumber, items, delayedUpdates, changeset);
                        posNumber++;
                    }

                    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) {
                        LOG.debug("Changed: '{}' from '{}' to (${}) '{}'", type.name(), handler, targetPos, target);
                        entity.addLink(buildNavigationLink(property.getKey().name(),
                                URIUtils.getURI(service.getClient().getServiceRoot(), 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));
            }

            if (property.getValue() instanceof Proxy) {
                final InvocationHandler target = Proxy.getInvocationHandler(property.getValue());

                if (target instanceof EntityCollectionInvocationHandler) {
                    for (String ref : ((EntityCollectionInvocationHandler<?>) target).referenceItems) {
                        delayedUpdates.add(new EntityLinkDesc(property.getKey().name(), handler, ref));
                    }
                }
            }
        }

        for (Map.Entry<String, AnnotatableInvocationHandler> entry : handler.getNavPropAnnotatableHandlers()
                .entrySet()) {

            CoreUtils.addAnnotations(service.getClient(), entry.getValue().getAnnotations(),
                    entity.getNavigationLink(entry.getKey()));
        }

        final AttachedEntityStatus processedStatus = queue(handler, entity, changeset);
        if (processedStatus != null) {
            // insert into the process queue
            LOG.debug("{}: Insert '{}' into the process queue", posNumber, handler);
            items.put(handler, posNumber);
        } else {
            posNumber--;
        }

        if (processedStatus != AttachedEntityStatus.DELETED) {
            int startingPos = posNumber;

            if (handler.getEntity().isMediaEntity() && handler.isChanged()) {
                // update media properties
                if (!handler.getPropertyChanges().isEmpty()) {
                    final URI targetURI = currentStatus == AttachedEntityStatus.NEW ? URI.create("$" + startingPos)
                            : URIUtils.getURI(service.getClient().getServiceRoot(),
                                    handler.getEntity().getEditLink().toASCIIString());
                    queueUpdate(handler, targetURI, entity, changeset);
                    posNumber++;
                    items.put(handler, posNumber);
                    LOG.debug("{}: Update media properties for '{}' into the process queue", posNumber, handler);
                }

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

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

                    // update media info (use null key)
                    posNumber++;
                    items.put(null, posNumber);
                    LOG.debug("{}: Update media info for '{}' into the process queue", posNumber, handler);
                }
            }

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

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

                // update media info (use null key)
                posNumber++;
                items.put(handler, posNumber);
                LOG.debug("{}: Update media info (null key) for '{}' into the process queue", posNumber, handler);
            }
        }

        return posNumber;
    }

    protected void processDelayedUpdates(final List<EntityLinkDesc> delayedUpdates, int pos,
            final TransactionItems items, final PersistenceChanges changeset) {
        int posNumber = pos;
        for (EntityLinkDesc delayedUpdate : delayedUpdates) {
            if (StringUtils.isBlank(delayedUpdate.getReference())) {

                posNumber++;
                items.put(delayedUpdate.getSource(), posNumber);

                final ClientEntity changes = service.getClient().getObjectFactory()
                        .newEntity(delayedUpdate.getSource().getEntity().getTypeName());

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

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

                for (EntityInvocationHandler target : delayedUpdate.getTargets()) {
                    status = service.getContext().entityContext().getStatus(target);

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

                    changes.addLink(delayedUpdate.getType() == ClientLinkType.ENTITY_NAVIGATION
                            ? service.getClient().getObjectFactory()
                                    .newEntityNavigationLink(delayedUpdate.getSourceName(), targetURI)
                            : service.getClient().getObjectFactory()
                                    .newEntitySetNavigationLink(delayedUpdate.getSourceName(), targetURI));

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

                queueUpdate(delayedUpdate.getSource(), sourceURI, changes, changeset);
            } else {
                URI sourceURI = URIUtils.getURI(service.getClient().getServiceRoot(),
                        delayedUpdate.getSource().getEntity().getEditLink().toASCIIString() + "/"
                                + delayedUpdate.getSourceName() + "/$ref");

                if (queueUpdateLinkViaRef(delayedUpdate.getSource(), sourceURI,
                        URI.create(delayedUpdate.getReference()), changeset)) {
                    posNumber++;
                    items.put(delayedUpdate.getSource(), posNumber);
                }
            }
        }
    }

    private AttachedEntityStatus queue(final EntityInvocationHandler handler, final ClientEntity entity,
            final PersistenceChanges changeset) {

        switch (service.getContext().entityContext().getStatus(handler)) {
        case NEW:
            queueCreate(handler, entity, changeset);
            return AttachedEntityStatus.NEW;

        case DELETED:
            queueDelete(handler, entity, changeset);
            return AttachedEntityStatus.DELETED;

        default:
            if (handler.isChanged(false)) {
                queueUpdate(handler, entity, changeset);
                return AttachedEntityStatus.CHANGED;
            } else {
                return null;
            }
        }
    }

    private void queueCreate(final EntityInvocationHandler handler, final ClientEntity entity,
            final PersistenceChanges changeset) {

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

        changeset.addChange(service.getClient().getCUDRequestFactory()
                .getEntityCreateRequest(handler.getEntitySetURI(), entity), handler);
    }

    private void queueUpdateMediaEntity(final EntityInvocationHandler handler, final URI uri,
            final EdmStreamValue input, final PersistenceChanges changeset) {

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

        final ODataMediaEntityUpdateRequest<?> req = service.getClient().getCUDRequestFactory()
                .getMediaEntityUpdateRequest(uri, input.getStream());

        if (StringUtils.isNotBlank(input.getContentType())) {
            req.setContentType(input.getContentType());
        }

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

        changeset.addChange(req, handler);
    }

    private void queueUpdateMediaResource(final EntityInvocationHandler handler, final URI uri,
            final EdmStreamValue input, final PersistenceChanges changeset) {

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

        final ODataStreamUpdateRequest req = service.getClient().getCUDRequestFactory().getStreamUpdateRequest(uri,
                input.getStream());

        if (StringUtils.isNotBlank(input.getContentType())) {
            req.setContentType(input.getContentType());
        }

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

        changeset.addChange(req, handler);
    }

    private void queueUpdate(final EntityInvocationHandler handler, final ClientEntity changes,
            final PersistenceChanges changeset) {

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

        final ODataEntityUpdateRequest<ClientEntity> req = ((EdmEnabledODataClient) service.getClient())
                .getCUDRequestFactory().getEntityUpdateRequest(handler.getEntityURI(),
                        org.apache.olingo.client.api.communication.request.cud.UpdateType.PATCH, changes);

        req.setPrefer(new ODataPreferences().returnContent());

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

        changeset.addChange(req, handler);
    }

    private boolean queueUpdateLinkViaRef(final EntityInvocationHandler handler, final URI source,
            final URI targetRef, final PersistenceChanges changeset) {

        LOG.debug("Update '{}'", targetRef);
        URI sericeRoot = handler.getClient().newURIBuilder(handler.getClient().getServiceRoot()).build();

        final ODataReferenceAddingRequest req = ((org.apache.olingo.client.api.EdmEnabledODataClient) service
                .getClient()).getCUDRequestFactory().getReferenceAddingRequest(sericeRoot, source, targetRef);

        req.setPrefer(new ODataPreferences().returnContent());

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

        changeset.addChange(req, handler);
        return true;
    }

    private void queueUpdate(final EntityInvocationHandler handler, final URI uri, final ClientEntity changes,
            final PersistenceChanges changeset) {

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

        final ODataEntityUpdateRequest<ClientEntity> req = ((EdmEnabledODataClient) service.getClient())
                .getCUDRequestFactory().getEntityUpdateRequest(uri,
                        org.apache.olingo.client.api.communication.request.cud.UpdateType.PATCH, changes);

        req.setPrefer(new ODataPreferences().returnContent());

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

        changeset.addChange(req, handler);
    }

    private void queueDelete(final EntityInvocationHandler handler, final ClientEntity entity,
            final PersistenceChanges changeset) {
        final URI deleteURI = entity.getEditLink() == null ? handler.getEntityURI() : entity.getEditLink();
        changeset.addChange(buildDeleteRequest(deleteURI, handler.getETag()), handler);
    }

    private void queueDelete(final URI deleteURI, final String etag, final PersistenceChanges changeset) {
        changeset.addChange(buildDeleteRequest(deleteURI, etag), null);
    }

    private ODataDeleteRequest buildDeleteRequest(final URI deleteURI, final String etag) {

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

        final ODataDeleteRequest req = service.getClient().getCUDRequestFactory().getDeleteRequest(deleteURI);

        if (StringUtils.isNotBlank(etag)) {
            req.setIfMatch(etag);
        }

        return req;
    }

    private AttachedEntityStatus resolveNavigationLink(final NavigationProperty property,
            final EntityInvocationHandler handler) {
        if (handler.getUUID().getEntitySetURI() == null) {
            //Load key
            CoreUtils.getKey(service.getClient(), handler, handler.getTypeRef(), handler.getEntity());
            handler.updateUUID(CoreUtils.getTargetEntitySetURI(service.getClient(), property), handler.getTypeRef(),
                    null);
            service.getContext().entityContext().attach(handler, AttachedEntityStatus.NEW);
            return AttachedEntityStatus.NEW;
        } else {
            // existent object
            service.getContext().entityContext().attach(handler, AttachedEntityStatus.LINKED);
            return AttachedEntityStatus.LINKED;
        }
    }
}