diff options
Diffstat (limited to 'v3po/translate-utils/src/main/java/io')
10 files changed, 886 insertions, 0 deletions
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java new file mode 100644 index 000000000..027d9bbb7 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java @@ -0,0 +1,178 @@ +/* + * 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.util; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import com.google.common.collect.Maps; +import io.fd.honeycomb.v3po.translate.read.ChildReader; +import io.fd.honeycomb.v3po.translate.write.ChildWriter; +import io.fd.honeycomb.v3po.translate.SubtreeManager; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +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.Identifiable; +import org.opendaylight.yangtools.yang.binding.Identifier; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public final class RWUtils { + + private RWUtils() {} + + /** + * Find next item in ID after provided type + */ + @Nonnull + public static InstanceIdentifier.PathArgument getNextId(@Nonnull final InstanceIdentifier<? extends DataObject> id, + @Nonnull final InstanceIdentifier<? extends DataObject> type) { + // TODO this is inefficient(maybe, depending on actual Iterable type) + final Iterable<InstanceIdentifier.PathArgument> pathArguments = id.getPathArguments(); + final int i = Iterables.indexOf(pathArguments, new Predicate<InstanceIdentifier.PathArgument>() { + @Override + public boolean apply(final InstanceIdentifier.PathArgument input) { + return input.getType().isAssignableFrom(type.getTargetType()); + } + }); + Preconditions.checkArgument(i >= 0, "Unable to find %s type in %s", type.getTargetType(), id); + return Iterables.get(pathArguments, i + 1); + } + + public static <T> List<ChildReader<? extends ChildOf<T>>> emptyChildReaderList() { + return Collections.emptyList(); + } + + public static <T> List<ChildWriter<? extends ChildOf<T>>> emptyChildWriterList() { + return Collections.emptyList(); + } + + public static <T> List<ChildReader<? extends Augmentation<T>>> emptyAugReaderList() { + return Collections.emptyList(); + } + + public static <T> List<ChildWriter<? extends Augmentation<T>>> emptyAugWriterList() { + return Collections.emptyList(); + } + + public static <T> List<ChildReader<? extends Augmentation<T>>> singletonAugReaderList( + ChildReader<? extends Augmentation<T>> item) { + return Collections.<ChildReader<? extends Augmentation<T>>>singletonList(item); + } + + public static <T> List<ChildReader<? extends ChildOf<T>>> singletonChildReaderList( + ChildReader<? extends ChildOf<T>> item) { + return Collections.<ChildReader<? extends ChildOf<T>>>singletonList(item); + } + + public static <T> List<ChildWriter<? extends ChildOf<T>>> singletonChildWriterList( + ChildWriter<? extends ChildOf<T>> item) { + return Collections.<ChildWriter<? extends ChildOf<T>>>singletonList(item); + } + + /** + * Replace last item in ID with a provided IdentifiableItem of the same type + */ + @SuppressWarnings("unchecked") + @Nonnull + public static <D extends DataObject & Identifiable<K>, K extends Identifier<D>> InstanceIdentifier<D> replaceLastInId( + @Nonnull final InstanceIdentifier<D> id, final InstanceIdentifier.IdentifiableItem<D, K> currentBdItem) { + + final Iterable<InstanceIdentifier.PathArgument> pathArguments = id.getPathArguments(); + final Iterable<InstanceIdentifier.PathArgument> withoutCurrent = + Iterables.limit(pathArguments, Iterables.size(pathArguments) - 1); + final Iterable<InstanceIdentifier.PathArgument> concat = + Iterables.concat(withoutCurrent, Collections.singleton(currentBdItem)); + return (InstanceIdentifier<D>) InstanceIdentifier.create(concat); + } + + /** + * Create IdentifiableItem from target type of provided ID with provided key + */ + @Nonnull + public static <D extends DataObject & Identifiable<K>, K extends Identifier<D>> InstanceIdentifier.IdentifiableItem<D, K> getCurrentIdItem( + @Nonnull final InstanceIdentifier<D> id, final K key) { + return new InstanceIdentifier.IdentifiableItem<>(id.getTargetType(), key); + } + + /** + * Trim InstanceIdentifier at indexOf(type) + */ + @SuppressWarnings("unchecked") + @Nonnull + public static <D extends DataObject> InstanceIdentifier<D> cutId(@Nonnull final InstanceIdentifier<? extends DataObject> id, + @Nonnull final InstanceIdentifier<D> type) { + final Iterable<InstanceIdentifier.PathArgument> pathArguments = id.getPathArguments(); + final int i = Iterables.indexOf(pathArguments, new Predicate<InstanceIdentifier.PathArgument>() { + @Override + public boolean apply(final InstanceIdentifier.PathArgument input) { + return input.getType().equals(type.getTargetType()); + } + }); + Preconditions.checkArgument(i >= 0, "ID %s does not contain %s", id, type); + return (InstanceIdentifier<D>) InstanceIdentifier.create(Iterables.limit(pathArguments, i + 1)); + } + + /** + * Create a map from a collection, checking for duplicity in the process + */ + @Nonnull + public static <K, V> Map<K, V> uniqueLinkedIndex(@Nonnull final Collection<V> values, @Nonnull final Function<? super V, K> keyFunction) { + final Map<K, V> objectObjectLinkedHashMap = Maps.newLinkedHashMap(); + for (V value : values) { + final K key = keyFunction.apply(value); + Preconditions.checkArgument(objectObjectLinkedHashMap.put(key, value) == null, + "Duplicate key detected : %s", key); + } + return objectObjectLinkedHashMap; + } + + public static final Function<SubtreeManager<? extends DataObject>, Class<? extends DataObject>> + MANAGER_CLASS_FUNCTION = new Function<SubtreeManager<? extends DataObject>, Class<? extends DataObject>>() { + @Override + public Class<? extends DataObject> apply(final SubtreeManager<? extends DataObject> input) { + return input.getManagedDataObjectType().getTargetType(); + } + }; + + public static final Function<SubtreeManager<? extends Augmentation<?>>, Class<? extends DataObject>> + MANAGER_CLASS_AUG_FUNCTION = new Function<SubtreeManager<? extends Augmentation<?>>, Class<? extends DataObject>>() { + + @Override + @SuppressWarnings("unchecked") + public Class<? extends DataObject> apply(final SubtreeManager<? extends Augmentation<?>> input) { + final Class<? extends Augmentation<?>> targetType = input.getManagedDataObjectType().getTargetType(); + Preconditions.checkArgument(DataObject.class.isAssignableFrom(targetType)); + return (Class<? extends DataObject>) targetType; + } + }; + + @SuppressWarnings("unchecked") + public static <D extends DataObject> InstanceIdentifier<D> appendTypeToId( + final InstanceIdentifier<? extends DataObject> parentId, final InstanceIdentifier<D> type) { + Preconditions.checkArgument(!parentId.contains(type), + "Unexpected InstanceIdentifier %s, already contains %s", parentId, type); + final InstanceIdentifier.PathArgument t = Iterables.getOnlyElement(type.getPathArguments()); + return (InstanceIdentifier<D>) InstanceIdentifier.create(Iterables.concat( + parentId.getPathArguments(), Collections.singleton(t))); + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/ReflectionUtils.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/ReflectionUtils.java new file mode 100644 index 000000000..ea0b3b2c4 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/ReflectionUtils.java @@ -0,0 +1,79 @@ +/* + * 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.util; + +import com.google.common.base.Optional; +import java.lang.reflect.Method; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Reflection based utilities + */ +public final class ReflectionUtils { + + private ReflectionUtils() {} + + /** + * Find a specific method using reflection + * + * @param managedType Class object to find method in + * @param prefix Method name prefix used when finding the method. Case does not matter. + * @param paramTypes List of input argument types + * @param retType Return type + * + * @return Found method or Optional.absent() if there's no such method + */ + @Nonnull + public static Optional<Method> findMethodReflex(@Nonnull final Class<?> managedType, + @Nonnull final String prefix, + @Nonnull final List<Class<?>> paramTypes, + @Nonnull final Class<?> retType) { + for (Method method : managedType.getMethods()) { + if(isMethodMatch(prefix, paramTypes, retType, method)) { + return Optional.of(method); + } + } + + return Optional.absent(); + } + + private static boolean isMethodMatch(final @Nonnull String prefix, + final @Nonnull List<Class<?>> paramTypes, + final @Nonnull Class<?> retType, final Method method) { + if (!method.getName().toLowerCase().startsWith(prefix.toLowerCase())) { + return false; + } + + final Class<?>[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != paramTypes.size()) { + return false; + } + + for (int i = 0; i < parameterTypes.length; i++) { + if (!parameterTypes[i].isAssignableFrom(paramTypes.get(i))) { + return false; + } + } + + if (!method.getReturnType().equals(retType)) { + return false; + } + + return true; + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/DelegatingReaderRegistry.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/DelegatingReaderRegistry.java new file mode 100644 index 000000000..387f3cc7c --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/DelegatingReaderRegistry.java @@ -0,0 +1,113 @@ +/* + * 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.util.read; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Optional; +import com.google.common.collect.Iterables; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; +import io.fd.honeycomb.v3po.translate.read.ListReader; +import io.fd.honeycomb.v3po.translate.read.ReadContext; +import io.fd.honeycomb.v3po.translate.read.ReadFailedException; +import io.fd.honeycomb.v3po.translate.read.ReaderRegistry; +import io.fd.honeycomb.v3po.translate.util.RWUtils; +import io.fd.honeycomb.v3po.translate.read.Reader; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple reader registry able to perform and aggregated read (ROOT read) on top of all provided readers. Also able to + * delegate a specific read to one of the delegate readers. + * + * This could serve as a utility to hold & hide all available readers in upper layers. + */ +public final class DelegatingReaderRegistry implements ReaderRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(DelegatingReaderRegistry.class); + + private final Map<Class<? extends DataObject>, Reader<? extends DataObject>> rootReaders; + + /** + * Create new {@link DelegatingReaderRegistry} + * + * @param rootReaders List of delegate readers + */ + public DelegatingReaderRegistry(@Nonnull final List<Reader<? extends DataObject>> rootReaders) { + this.rootReaders = RWUtils.uniqueLinkedIndex(checkNotNull(rootReaders), RWUtils.MANAGER_CLASS_FUNCTION); + } + + @Override + @Nonnull + public Multimap<InstanceIdentifier<? extends DataObject>, ? extends DataObject> readAll( + @Nonnull final ReadContext ctx) throws ReadFailedException { + + LOG.debug("Reading from all delegates: {}", this); + LOG.trace("Reading from all delegates: {}", rootReaders.values()); + + final Multimap<InstanceIdentifier<? extends DataObject>, DataObject> objects = LinkedListMultimap.create(); + for (Reader<? extends DataObject> rootReader : rootReaders.values()) { + LOG.debug("Reading from delegate: {}", rootReader); + + if (rootReader instanceof ListReader) { + final List<? extends DataObject> listEntries = + ((ListReader) rootReader).readList(rootReader.getManagedDataObjectType(), ctx); + if (!listEntries.isEmpty()) { + objects.putAll(rootReader.getManagedDataObjectType(), listEntries); + } + } else { + final Optional<? extends DataObject> read = rootReader.read(rootReader.getManagedDataObjectType(), ctx); + if (read.isPresent()) { + objects.putAll(rootReader.getManagedDataObjectType(), Collections.singletonList(read.get())); + } + } + } + + return objects; + } + + @Nonnull + @Override + public Optional<? extends DataObject> read(@Nonnull final InstanceIdentifier<? extends DataObject> id, + @Nonnull final ReadContext ctx) + throws ReadFailedException { + final InstanceIdentifier.PathArgument first = checkNotNull( + Iterables.getFirst(id.getPathArguments(), null), "Empty id"); + final Reader<? extends DataObject> reader = rootReaders.get(first.getType()); + checkNotNull(reader, + "Unable to read %s. Missing reader. Current readers for: %s", id, rootReaders.keySet()); + LOG.debug("Reading from delegate: {}", reader); + return reader.read(id, ctx); + } + + /** + * @throws UnsupportedOperationException This getter is not supported for reader registry since it does not manage a + * specific node type + */ + @Nonnull + @Override + public InstanceIdentifier<DataObject> getManagedDataObjectType() { + throw new UnsupportedOperationException("Root registry has no type"); + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/NoopReaderCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/NoopReaderCustomizer.java new file mode 100644 index 000000000..5ed033755 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/NoopReaderCustomizer.java @@ -0,0 +1,32 @@ +/* + * 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.util.read; + +import io.fd.honeycomb.v3po.translate.Context; +import io.fd.honeycomb.v3po.translate.spi.read.RootReaderCustomizer; +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public abstract class NoopReaderCustomizer<C extends DataObject, B extends Builder<C>> implements + RootReaderCustomizer<C, B> { + + @Override + public void readCurrentAttributes(InstanceIdentifier<C> id, final B builder, final Context context) { + // Noop + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveChildReaderCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveChildReaderCustomizer.java new file mode 100644 index 000000000..3d5f9f4e8 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveChildReaderCustomizer.java @@ -0,0 +1,57 @@ +/* + * 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.util.read; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import io.fd.honeycomb.v3po.translate.util.ReflectionUtils; +import io.fd.honeycomb.v3po.translate.spi.read.ChildReaderCustomizer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Might be slow ! + */ +public class ReflexiveChildReaderCustomizer<C extends DataObject, B extends Builder<C>> + extends ReflexiveRootReaderCustomizer<C, B> + implements ChildReaderCustomizer<C,B> { + + public ReflexiveChildReaderCustomizer(final Class<B> builderClass) { + super(builderClass); + } + + // TODO Could be just a default implementation in interface (making this a mixin) + + @Override + public void merge(final Builder<? extends DataObject> parentBuilder, final C readValue) { + final Optional<Method> method = + ReflectionUtils.findMethodReflex(parentBuilder.getClass(), "set", + Collections.<Class<?>>singletonList(readValue.getClass()), parentBuilder.getClass()); + + Preconditions.checkArgument(method.isPresent(), "Unable to set %s to %s", readValue, parentBuilder); + + try { + method.get().invoke(parentBuilder, readValue); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new IllegalArgumentException("Unable to set " + readValue + " to " + parentBuilder, e); + } + } + +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveRootReaderCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveRootReaderCustomizer.java new file mode 100644 index 000000000..029f359bb --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/read/ReflexiveRootReaderCustomizer.java @@ -0,0 +1,42 @@ +/* + * 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.util.read; + +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Might be slow ! + */ +public class ReflexiveRootReaderCustomizer<C extends DataObject, B extends Builder<C>> extends NoopReaderCustomizer<C, B> { + + private final Class<B> builderClass; + + public ReflexiveRootReaderCustomizer(final Class<B> builderClass) { + this.builderClass = builderClass; + } + + @Override + public B getBuilder(InstanceIdentifier<C> id) { + try { + return builderClass.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException("Unable to instantiate " + builderClass, e); + } + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java new file mode 100644 index 000000000..fda289e2b --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java @@ -0,0 +1,176 @@ +/* + * 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.util.write; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import io.fd.honeycomb.v3po.translate.TranslationException; +import io.fd.honeycomb.v3po.translate.util.RWUtils; +import io.fd.honeycomb.v3po.translate.write.WriteContext; +import io.fd.honeycomb.v3po.translate.write.Writer; +import io.fd.honeycomb.v3po.translate.write.WriterRegistry; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple writer registry able to perform and aggregated read (ROOT write) on top of all provided writers. Also able to + * delegate a specific read to one of the delegate writers. + * + * This could serve as a utility to hold & hide all available writers in upper layers. + */ +public final class DelegatingWriterRegistry implements WriterRegistry { + + private static final Logger LOG = LoggerFactory.getLogger(DelegatingWriterRegistry.class); + + private static final Function<InstanceIdentifier<?>, Class<? extends DataObject>> ID_TO_CLASS = + new Function<InstanceIdentifier<?>, Class<? extends DataObject>>() { + @Override + public Class<? extends DataObject> apply(final InstanceIdentifier<?> input) { + return input.getTargetType(); + } + }; + + private final Map<Class<? extends DataObject>, Writer<? extends DataObject>> rootWriters; + + /** + * Create new {@link DelegatingWriterRegistry} + * + * @param rootWriters List of delegate writers + */ + public DelegatingWriterRegistry(@Nonnull final List<Writer<? extends DataObject>> rootWriters) { + this.rootWriters = RWUtils.uniqueLinkedIndex(checkNotNull(rootWriters), RWUtils.MANAGER_CLASS_FUNCTION); + } + + /** + * @throws UnsupportedOperationException This getter is not supported for writer registry since it does not manage a + * specific node type + */ + @Nonnull + @Override + public InstanceIdentifier<DataObject> getManagedDataObjectType() { + throw new UnsupportedOperationException("Root registry has no type"); + } + + @Override + public void update(@Nonnull final InstanceIdentifier<? extends DataObject> id, + @Nullable final DataObject dataBefore, + @Nullable final DataObject dataAfter, + @Nonnull final WriteContext ctx) throws TranslationException { + final InstanceIdentifier.PathArgument first = checkNotNull( + Iterables.getFirst(id.getPathArguments(), null), "Empty id"); + final Writer<? extends DataObject> writer = rootWriters.get(first.getType()); + checkNotNull(writer, + "Unable to write %s. Missing writer. Current writers for: %s", id, rootWriters.keySet()); + writer.update(id, dataBefore, dataAfter, ctx); + } + + @Override + public void update(@Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesBefore, + @Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesAfter, + @Nonnull final WriteContext ctx) throws TranslationException { + checkAllWritersPresent(nodesBefore); + checkAllWritersPresent(nodesAfter); + + final List<InstanceIdentifier<?>> processedNodes = Lists.newArrayList(); + + for (Map.Entry<Class<? extends DataObject>, Writer<? extends DataObject>> rootWriterEntry : rootWriters + .entrySet()) { + + final InstanceIdentifier<? extends DataObject> id = rootWriterEntry.getValue().getManagedDataObjectType(); + + final DataObject dataBefore = nodesBefore.get(id); + final DataObject dataAfter = nodesAfter.get(id); + + // No change to current writer + if (dataBefore == null && dataAfter == null) { + continue; + } + + LOG.debug("ChangesProcessor.applyChanges() processing dataBefore={}, dataAfter={}", dataBefore, dataAfter); + + try { + update(id, dataBefore, dataAfter, ctx); + processedNodes.add(id); + } catch (Exception e) { + LOG.error("Error while processing data change of: {} (before={}, after={})", + id, dataBefore, dataAfter, e); + throw new BulkUpdateException( + id, new ReverterImpl(this, processedNodes, nodesBefore, nodesAfter, ctx), e); + } + } + } + + private void checkAllWritersPresent(final @Nonnull Map<InstanceIdentifier<?>, DataObject> nodesBefore) { + checkArgument(rootWriters.keySet().containsAll(Collections2.transform(nodesBefore.keySet(), ID_TO_CLASS)), + "Unable to handle all changes. Missing dedicated writers for: %s", + Sets.difference(nodesBefore.keySet(), rootWriters.keySet())); + } + + private static final class ReverterImpl implements Reverter { + private final WriterRegistry delegatingWriterRegistry; + private final List<InstanceIdentifier<?>> processedNodes; + private final Map<InstanceIdentifier<?>, DataObject> nodesBefore; + private final Map<InstanceIdentifier<?>, DataObject> nodesAfter; + private final WriteContext ctx; + + ReverterImpl(final WriterRegistry delegatingWriterRegistry, + final List<InstanceIdentifier<?>> processedNodes, + final Map<InstanceIdentifier<?>, DataObject> nodesBefore, + final Map<InstanceIdentifier<?>, DataObject> nodesAfter, final WriteContext ctx) { + this.delegatingWriterRegistry = delegatingWriterRegistry; + this.processedNodes = processedNodes; + this.nodesBefore = nodesBefore; + this.nodesAfter = nodesAfter; + this.ctx = ctx; + } + + @Override + public void revert() throws RevertFailedException { + final LinkedList<InstanceIdentifier<?>> notReverted = new LinkedList<>(processedNodes); + + while (notReverted.size() > 0) { + final InstanceIdentifier<?> node = notReverted.peekLast(); + LOG.debug("ChangesProcessor.revertChanges() processing node={}", node); + + final DataObject dataBefore = nodesBefore.get(node); + final DataObject dataAfter = nodesAfter.get(node); + + // revert a change by invoking writer with reordered arguments + try { + delegatingWriterRegistry.update(node, dataAfter, dataBefore, ctx); + notReverted.removeLast(); // change was successfully reverted + } catch (Exception e) { + throw new RevertFailedException(notReverted, e); + } + + } + } + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java new file mode 100644 index 000000000..266325815 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java @@ -0,0 +1,49 @@ +/* + * 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.util.write; + +import io.fd.honeycomb.v3po.translate.spi.write.RootWriterCustomizer; +import io.fd.honeycomb.v3po.translate.Context; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Customizer not performing any changes on current level. Suitable for nodes that don't have any leaves and all of + * its child nodes are managed by dedicated writers + */ +public class NoopWriterCustomizer<D extends DataObject> implements RootWriterCustomizer<D> { + + @Override + public void writeCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataAfter, + @Nonnull final Context ctx) { + + } + + @Override + public void updateCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore, + @Nonnull final D dataAfter, + @Nonnull final Context ctx) { + + } + + @Override + public void deleteCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore, + @Nonnull final Context ctx) { + + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java new file mode 100644 index 000000000..ba67e560c --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java @@ -0,0 +1,59 @@ +/* + * 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.util.write; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +import io.fd.honeycomb.v3po.translate.util.ReflectionUtils; +import io.fd.honeycomb.v3po.translate.spi.write.ChildWriterCustomizer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Might be slow ! + */ +public class ReflexiveChildWriterCustomizer<C extends DataObject> extends NoopWriterCustomizer<C> implements + ChildWriterCustomizer<C> { + + @Nonnull + @Override + @SuppressWarnings("unchecked") + public Optional<C> extract(@Nonnull final InstanceIdentifier<C> currentId, @Nonnull final DataObject parentData) { + final Class<C> currentType = currentId.getTargetType(); + final Optional<Method> method = ReflectionUtils.findMethodReflex(getParentType(currentId), + "get" + currentType.getSimpleName(), Collections.<Class<?>>emptyList(), currentType); + + Preconditions.checkArgument(method.isPresent(), "Unable to get %s from %s", currentType, parentData); + + try { + return method.isPresent() + ? Optional.of((C) method.get().invoke(parentData)) + : Optional.<C>absent(); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new IllegalArgumentException("Unable to get " + currentType + " from " + parentData, e); + } + } + + private Class<? extends DataObject> getParentType(final @Nonnull InstanceIdentifier<C> currentId) { + return Iterables.get(currentId.getPathArguments(), Iterables.size(currentId.getPathArguments()) - 2).getType(); + } +} diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/TransactionWriteContext.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/TransactionWriteContext.java new file mode 100644 index 000000000..0bb68e3b2 --- /dev/null +++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/TransactionWriteContext.java @@ -0,0 +1,101 @@ +/* + * 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.util.write; + +import com.google.common.base.Optional; +import com.google.common.util.concurrent.CheckedFuture; +import io.fd.honeycomb.v3po.translate.write.WriteContext; +import io.fd.honeycomb.v3po.translate.Context; +import java.util.Map; +import javax.annotation.Nonnull; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; +import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +/** + * Transaction based WriteContext + */ +public final class TransactionWriteContext implements WriteContext { + + private final DOMDataReadOnlyTransaction beforeTx; + private final DOMDataReadOnlyTransaction afterTx; + private final Context ctx; + private final BindingNormalizedNodeSerializer serializer; + + public TransactionWriteContext(final BindingNormalizedNodeSerializer serializer, + final DOMDataReadOnlyTransaction beforeTx, + final DOMDataReadOnlyTransaction afterTx) { + this.serializer = serializer; + this.beforeTx = beforeTx; + this.afterTx = afterTx; + this.ctx = new Context(); + } + + // TODO make this asynchronous + + @Override + public Optional<DataObject> readBefore(@Nonnull final InstanceIdentifier<? extends DataObject> currentId) { + return read(currentId, beforeTx); + } + + private Optional<DataObject> read(final InstanceIdentifier<? extends DataObject> currentId, + final DOMDataReadOnlyTransaction tx) { + final YangInstanceIdentifier path = serializer.toYangInstanceIdentifier(currentId); + + final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read = + tx.read(LogicalDatastoreType.CONFIGURATION, path); + + try { + // TODO once the APIs are asynchronous use just Futures.transform + final Optional<NormalizedNode<?, ?>> optional = read.checkedGet(); + + if (!optional.isPresent()) { + return Optional.absent(); + } + + final NormalizedNode<?, ?> data = optional.get(); + final Map.Entry<InstanceIdentifier<?>, DataObject> entry = serializer.fromNormalizedNode(path, data); + + return Optional.of(entry.getValue()); + } catch (ReadFailedException e) { + throw new IllegalStateException("Unable to perform read", e); + } + } + + @Override + public Optional<DataObject> readAfter(@Nonnull final InstanceIdentifier<? extends DataObject> currentId) { + return read(currentId, afterTx); + } + + @Override + public Context getContext() { + return ctx; + } + + /** + * Does not close the transactions + */ + @Override + public void close() { + ctx.close(); + } +} |