org.opendaylight.mdsal.binding.dom.adapter.CurrentAdapterSerializer Maven / Gradle / Ivy
/*
* Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.mdsal.binding.dom.adapter;
import static com.google.common.base.Verify.verify;
import static com.google.common.base.Verify.verifyNotNull;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.VerifyException;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.binding.api.ActionSpec;
import org.opendaylight.mdsal.binding.api.InstanceNotificationSpec;
import org.opendaylight.yangtools.binding.BindingContract;
import org.opendaylight.yangtools.binding.BindingInstanceIdentifier;
import org.opendaylight.yangtools.binding.DataObject;
import org.opendaylight.yangtools.binding.DataObjectReference;
import org.opendaylight.yangtools.binding.data.codec.spi.BindingDOMCodecServices;
import org.opendaylight.yangtools.binding.data.codec.spi.ForwardingBindingDOMCodecServices;
import org.opendaylight.yangtools.binding.model.api.JavaTypeName;
import org.opendaylight.yangtools.binding.runtime.api.ActionRuntimeType;
import org.opendaylight.yangtools.binding.runtime.api.InputRuntimeType;
import org.opendaylight.yangtools.binding.runtime.api.NotificationRuntimeType;
import org.opendaylight.yangtools.binding.runtime.api.RuntimeType;
import org.opendaylight.yangtools.yang.common.QNameModule;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.model.api.stmt.ActionEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.ListEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.NotificationEffectiveStatement;
import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement;
import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
@Beta
@VisibleForTesting
public final class CurrentAdapterSerializer extends ForwardingBindingDOMCodecServices {
private final LoadingCache cache = CacheBuilder.newBuilder()
.softValues().build(new CacheLoader() {
@Override
public YangInstanceIdentifier load(final BindingInstanceIdentifier key) {
return getInstanceIdentifierCodec().fromBinding(key);
}
});
private final ConcurrentMap extractors = new ConcurrentHashMap<>();
private final @NonNull BindingDOMCodecServices delegate;
public CurrentAdapterSerializer(final BindingDOMCodecServices delegate) {
this.delegate = requireNonNull(delegate);
}
@Override
protected BindingDOMCodecServices delegate() {
return delegate;
}
@NonNull YangInstanceIdentifier toCachedYangInstanceIdentifier(final @NonNull BindingInstanceIdentifier path) {
return cache.getUnchecked(path);
}
@NonNull DataObjectReference coerceInstanceIdentifier(final YangInstanceIdentifier dom) {
return verifyNotNull(fromYangInstanceIdentifier(dom));
}
@NonNull Absolute getActionPath(final @NonNull ActionSpec spec) {
return getSchemaNodeIdentifier(spec.path(), spec.type(), ActionRuntimeType.class,
ActionEffectiveStatement.class);
}
@NonNull Absolute getNotificationPath(final @NonNull InstanceNotificationSpec spec) {
return getSchemaNodeIdentifier(spec.path(), spec.type(), NotificationRuntimeType.class,
NotificationEffectiveStatement.class);
}
private @NonNull Absolute getSchemaNodeIdentifier(
final @NonNull DataObjectReference path, final @NonNull Class> type,
final @NonNull Class expectedRuntime,
final @NonNull Class> expectedStatement) {
final var typeName = JavaTypeName.create(type);
final var runtimeType = getRuntimeContext().getTypes().findSchema(typeName)
.orElseThrow(() -> new IllegalArgumentException(typeName + " is not known"));
final T casted;
try {
casted = expectedRuntime.cast(runtimeType);
} catch (ClassCastException e) {
throw new IllegalArgumentException(typeName + " resolved to unexpected " + runtimeType, e);
}
final var qname = expectedStatement.cast(casted.statement()).argument();
final var entry = resolvePath(path);
final var stack = entry.getKey();
final var stmt = stack.enterSchemaTree(qname.bindTo(entry.getValue()));
if (expectedStatement.isInstance(stmt)) {
return stack.toSchemaNodeIdentifier();
}
throw new VerifyException(path + " child " + typeName + " resolved to unexpected statement" + stmt);
}
@Nullable ContextReferenceExtractor findExtractor(final @NonNull InputRuntimeType inputType) {
final var inputName = inputType.getIdentifier();
final var cached = extractors.get(inputName);
if (cached != null) {
return cached;
}
// Load the class
final Class inputClass;
try {
inputClass = getRuntimeContext().loadClass(inputName);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Failed to load class for " + inputType, e);
}
// Check if there is an extractor at all
final var created = ContextReferenceExtractor.of(inputClass);
if (created == null) {
return null;
}
// Reconcile with cache
final var raced = extractors.putIfAbsent(inputName, created);
return raced != null ? raced : created;
}
private @NonNull Entry resolvePath(final @NonNull DataObjectReference path) {
final var stack = SchemaInferenceStack.of(getRuntimeContext().modelContext());
final var it = toYangInstanceIdentifier(path).getPathArguments().iterator();
verify(it.hasNext(), "Unexpected empty instance identifier for %s", path);
QNameModule lastNamespace;
do {
final var arg = it.next();
final var qname = arg.getNodeType();
final var stmt = stack.enterDataTree(qname);
lastNamespace = qname.getModule();
if (stmt instanceof ListEffectiveStatement) {
// Lists have two steps
verify(it.hasNext(), "Unexpected list termination at %s in %s", stmt, path);
// Verify just to make sure we are doing the right thing
final var skipped = it.next();
verify(skipped instanceof NodeIdentifier, "Unexpected skipped list entry item %s in %s", skipped, path);
verify(stmt.argument().equals(skipped.getNodeType()), "Mismatched list entry item %s in %s", skipped,
path);
}
} while (it.hasNext());
return Map.entry(stack, lastNamespace);
}
}