/* * Copyright (c) 2016 Cisco and/or its affiliates. * * Licensed 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 io.fd.honeycomb.v3po.translate.impl.write; import static com.google.common.base.Preconditions.checkArgument; import com.google.common.base.Optional; import com.google.common.collect.Lists; import io.fd.honeycomb.v3po.translate.impl.TraversalType; import io.fd.honeycomb.v3po.translate.util.RWUtils; import io.fd.honeycomb.v3po.translate.write.ChildWriter; import io.fd.honeycomb.v3po.translate.write.WriteContext; import io.fd.honeycomb.v3po.translate.write.WriteFailedException; import io.fd.honeycomb.v3po.translate.write.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.opendaylight.yangtools.yang.binding.Augmentation; import org.opendaylight.yangtools.yang.binding.ChildOf; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class AbstractCompositeWriter implements Writer { private static final Logger LOG = LoggerFactory.getLogger(AbstractCompositeWriter.class); private final Map, ChildWriter>> childWriters; private final Map, ChildWriter>> augWriters; private final InstanceIdentifier instanceIdentifier; private TraversalType traversalType; public AbstractCompositeWriter(final Class type, final List>> childWriters, final List>> augWriters, final TraversalType traversalType) { this.traversalType = traversalType; this.instanceIdentifier = InstanceIdentifier.create(type); this.childWriters = RWUtils.uniqueLinkedIndex(childWriters, RWUtils.MANAGER_CLASS_FUNCTION); this.augWriters = RWUtils.uniqueLinkedIndex(augWriters, RWUtils.MANAGER_CLASS_AUG_FUNCTION); } protected void writeCurrent(final InstanceIdentifier id, final D data, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Writing current: {} data: {}", this, id, data); switch (traversalType) { case PREORDER: { LOG.trace("{}: Writing current attributes", this); writeCurrentAttributes(id, data, ctx); writeChildren(id, data, ctx); break; } case POSTORDER: { writeChildren(id, data, ctx); LOG.trace("{}: Writing current attributes", this); writeCurrentAttributes(id, data, ctx); break; } } LOG.debug("{}: Current node written successfully", this); } private void writeChildren(final InstanceIdentifier id, final D data, final WriteContext ctx) throws WriteFailedException { for (ChildWriter> child : childWriters.values()) { LOG.debug("{}: Writing child in: {}", this, child); child.writeChild(id, data, ctx); } for (ChildWriter> child : augWriters.values()) { LOG.debug("{}: Writing augment in: {}", this, child); child.writeChild(id, data, ctx); } } protected void updateCurrent(final InstanceIdentifier id, final D dataBefore, final D dataAfter, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Updating current: {} dataBefore: {}, datAfter: {}", this, id, dataBefore, dataAfter); if (dataBefore.equals(dataAfter)) { LOG.debug("{}: Skipping current(no update): {}", this, id); // No change, ignore return; } switch (traversalType) { case PREORDER: { LOG.trace("{}: Updating current attributes", this); updateCurrentAttributes(id, dataBefore, dataAfter, ctx); updateChildren(id, dataBefore, dataAfter, ctx); break; } case POSTORDER: { updateChildren(id, dataBefore, dataAfter, ctx); LOG.trace("{}: Updating current attributes", this); updateCurrentAttributes(id, dataBefore, dataAfter, ctx); break; } } LOG.debug("{}: Current node updated successfully", this); } private void updateChildren(final InstanceIdentifier id, final D dataBefore, final D dataAfter, final WriteContext ctx) throws WriteFailedException { for (ChildWriter> child : childWriters.values()) { LOG.debug("{}: Updating child in: {}", this, child); child.updateChild(id, dataBefore, dataAfter, ctx); } for (ChildWriter> child : augWriters.values()) { LOG.debug("{}: Updating augment in: {}", this, child); child.updateChild(id, dataBefore, dataAfter, ctx); } } protected void deleteCurrent(final InstanceIdentifier id, final D dataBefore, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Deleting current: {} dataBefore: {}", this, id, dataBefore); switch (traversalType) { case PREORDER: { deleteChildren(id, dataBefore, ctx); LOG.trace("{}: Deleting current attributes", this); deleteCurrentAttributes(id, dataBefore, ctx); break; } case POSTORDER: { LOG.trace("{}: Deleting current attributes", this); deleteCurrentAttributes(id, dataBefore, ctx); deleteChildren(id, dataBefore, ctx); break; } } } private void deleteChildren(final InstanceIdentifier id, final D dataBefore, final WriteContext ctx) throws WriteFailedException { for (ChildWriter> child : reverseCollection(augWriters.values())) { LOG.debug("{}: Deleting augment in: {}", this, child); child.deleteChild(id, dataBefore, ctx); } for (ChildWriter> child : reverseCollection(childWriters.values())) { LOG.debug("{}: Deleting child in: {}", this, child); child.deleteChild(id, dataBefore, ctx); } } @SuppressWarnings("unchecked") @Override public void update(@Nonnull final InstanceIdentifier id, @Nullable final DataObject dataBefore, @Nullable final DataObject dataAfter, @Nonnull final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Updating : {}", this, id); LOG.trace("{}: Updating : {}, from: {} to: {}", this, id, dataBefore, dataAfter); if (idPointsToCurrent(id)) { if(isWrite(dataBefore, dataAfter)) { writeCurrent((InstanceIdentifier) id, castToManaged(dataAfter), ctx); } else if(isDelete(dataBefore, dataAfter)) { deleteCurrent((InstanceIdentifier) id, castToManaged(dataBefore), ctx); } else { checkArgument(dataBefore != null && dataAfter != null, "No data to process"); updateCurrent((InstanceIdentifier) id, castToManaged(dataBefore), castToManaged(dataAfter), ctx); } } else { if (isWrite(dataBefore, dataAfter)) { writeSubtree(id, dataAfter, ctx); } else if (isDelete(dataBefore, dataAfter)) { deleteSubtree(id, dataBefore, ctx); } else { checkArgument(dataBefore != null && dataAfter != null, "No data to process"); updateSubtree(id, dataBefore, dataAfter, ctx); } } } private void checkDataType(final @Nullable DataObject dataAfter) { checkArgument(getManagedDataObjectType().getTargetType().isAssignableFrom(dataAfter.getClass())); } private D castToManaged(final DataObject data) { checkDataType(data); return getManagedDataObjectType().getTargetType().cast(data); } private static boolean isWrite(final DataObject dataBefore, final DataObject dataAfter) { return dataBefore == null && dataAfter != null; } private static boolean isDelete(final DataObject dataBefore, final DataObject dataAfter) { return dataAfter == null && dataBefore != null; } private void writeSubtree(final InstanceIdentifier id, final DataObject dataAfter, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Writing subtree: {}", this, id); final Writer> writer = getNextWriter(id); if (writer != null) { LOG.debug("{}: Writing subtree: {} in: {}", this, id, writer); writer.update(id, null, dataAfter, ctx); } else { // If there's no dedicated writer, use write current // But we need current data after to do so final InstanceIdentifier currentId = RWUtils.cutId(id, getManagedDataObjectType()); Optional currentDataAfter = ctx.readAfter(currentId); LOG.debug("{}: Dedicated subtree writer missing for: {}. Writing current.", this, RWUtils.getNextId(id, getManagedDataObjectType()).getType(), currentDataAfter); writeCurrent(currentId, castToManaged(currentDataAfter.get()), ctx); } } private boolean idPointsToCurrent(final @Nonnull InstanceIdentifier id) { return id.getTargetType().equals(getManagedDataObjectType().getTargetType()); } private void deleteSubtree(final InstanceIdentifier id, final DataObject dataBefore, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Deleting subtree: {}", this, id); final Writer> writer = getNextWriter(id); if (writer != null) { LOG.debug("{}: Deleting subtree: {} in: {}", this, id, writer); writer.update(id, dataBefore, null, ctx); } else { updateSubtreeFromCurrent(id, ctx); } } @SuppressWarnings("unchecked") private void updateSubtreeFromCurrent(final InstanceIdentifier id, final WriteContext ctx) throws WriteFailedException { final InstanceIdentifier currentId = RWUtils.cutId(id, getManagedDataObjectType()); Optional currentDataBefore = ctx.readBefore(currentId); Optional currentDataAfter = ctx.readAfter(currentId); LOG.debug("{}: Dedicated subtree writer missing for: {}. Updating current without subtree", this, RWUtils.getNextId(id, getManagedDataObjectType()).getType(), currentDataAfter); updateCurrent((InstanceIdentifier) id, castToManaged(currentDataBefore.orNull()), castToManaged(currentDataAfter.orNull()), ctx); } private void updateSubtree(final InstanceIdentifier id, final DataObject dataBefore, final DataObject dataAfter, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Updating subtree: {}", this, id); final Writer> writer = getNextWriter(id); if (writer != null) { LOG.debug("{}: Updating subtree: {} in: {}", this, id, writer); writer.update(id, dataBefore, dataAfter, ctx); } else { updateSubtreeFromCurrent(id, ctx); } } private Writer> getNextWriter(final InstanceIdentifier id) { final Class next = RWUtils.getNextId(id, getManagedDataObjectType()).getType(); return childWriters.get(next); } private static List reverseCollection(final Collection original) { // TODO find a better reverse mechanism (probably a different collection for child writers is necessary) final ArrayList list = Lists.newArrayList(original); Collections.reverse(list); return list; } protected abstract void writeCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D data, @Nonnull final WriteContext ctx) throws WriteFailedException; protected abstract void deleteCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, @Nonnull final WriteContext ctx) throws WriteFailedException; protected abstract void updateCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, @Nonnull final D dataAfter, @Nonnull final WriteContext ctx) throws WriteFailedException; @Nonnull @Override public InstanceIdentifier getManagedDataObjectType() { return instanceIdentifier; } @Override public String toString() { return String.format("Writer[%s]", getManagedDataObjectType().getTargetType().getSimpleName()); } }