/* * Copyright (c) 2014 Cisco Systems, Inc. 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/eplv10.html */ package org.opendaylight.yangtools.yang.model.repo.util; import com.google.common.annotations.Beta; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureFallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.annotation.concurrent.GuardedBy; import org.opendaylight.yangtools.util.concurrent.ExceptionMapper; import org.opendaylight.yangtools.util.concurrent.ReflectiveExceptionMapper; import org.opendaylight.yangtools.yang.model.repo.api.MissingSchemaSourceException; import org.opendaylight.yangtools.yang.model.repo.api.SchemaRepository; import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceException; import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceFilter; import org.opendaylight.yangtools.yang.model.repo.api.SchemaSourceRepresentation; import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier; import org.opendaylight.yangtools.yang.model.repo.spi.PotentialSchemaSource; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaListenerRegistration; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceListener; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceProvider; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistration; import org.opendaylight.yangtools.yang.model.repo.spi.SchemaSourceRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract base class for {@link SchemaRepository} implementations. It handles registration * and lookup of schema sources, subclasses need only to provide their own * {@link #createSchemaContextFactory(SchemaSourceFilter)} implementation. */ @Beta public abstract class AbstractSchemaRepository implements SchemaRepository, SchemaSourceRegistry { private static final Logger LOG = LoggerFactory.getLogger(AbstractSchemaRepository.class); private static final ExceptionMapper FETCH_MAPPER = ReflectiveExceptionMapper.create("Schema source fetch", SchemaSourceException.class); /* * Source identifier -> representation -> provider map. We usually are looking for * a specific representation of a source. */ @GuardedBy("this") private final Map, AbstractSchemaSourceRegistration>> sources = new HashMap<>(); /* * Schema source listeners. */ @GuardedBy("this") private final Collection listeners = new ArrayList<>(); private static final CheckedFuture fetchSource(final SourceIdentifier id, final Iterator> it) { final AbstractSchemaSourceRegistration reg = it.next(); @SuppressWarnings("unchecked") final CheckedFuture f = ((SchemaSourceProvider)reg.getProvider()).getSource(id); return Futures.makeChecked(Futures.withFallback(f, new FutureFallback() { @Override public ListenableFuture create(final Throwable t) throws SchemaSourceException { LOG.debug("Failed to acquire source from {}", reg, t); if (it.hasNext()) { return fetchSource(id, it); } throw new MissingSchemaSourceException("All available providers exhausted", id, t); } }), FETCH_MAPPER); } @Override public CheckedFuture getSchemaSource(final SourceIdentifier id, final Class representation) { final ListMultimap, AbstractSchemaSourceRegistration> srcs = sources.get(id); if (srcs == null) { return Futures.immediateFailedCheckedFuture(new MissingSchemaSourceException("No providers registered for source" + id, id)); } // TODO, remove and make sources keep sorted multimap (e.g. ArrayListMultimap with SortedLists) final ArrayList> sortedSchemaSourceRegistrations = Lists.newArrayList(srcs.get(representation)); Collections.sort(sortedSchemaSourceRegistrations, SchemaProviderCostComparator.INSTANCE); final Iterator> regs = sortedSchemaSourceRegistrations.iterator(); if (!regs.hasNext()) { return Futures.immediateFailedCheckedFuture( new MissingSchemaSourceException("No providers for source " + id + " representation " + representation + " available", id)); } CheckedFuture fetchSourceFuture = fetchSource(id, regs); // Add callback to notify cache listeners about encountered schema Futures.addCallback(fetchSourceFuture, new FutureCallback() { @Override public void onSuccess(final T result) { for (final SchemaListenerRegistration listener : listeners) { listener.getInstance().schemaSourceEncountered(result); } } @Override public void onFailure(final Throwable t) { LOG.trace("Skipping notification for encountered source {}, fetching source failed", id, t); } }); return fetchSourceFuture; } private synchronized void addSource(final PotentialSchemaSource source, final AbstractSchemaSourceRegistration reg) { ListMultimap, AbstractSchemaSourceRegistration> m = sources.get(source.getSourceIdentifier()); if (m == null) { m = ArrayListMultimap.create(); sources.put(source.getSourceIdentifier(), m); } m.put(source.getRepresentation(), reg); final Collection> reps = Collections.>singleton(source); for (SchemaListenerRegistration l : listeners) { l.getInstance().schemaSourceRegistered(reps); } } private synchronized void removeSource(final PotentialSchemaSource source, final SchemaSourceRegistration reg) { final Multimap, AbstractSchemaSourceRegistration> m = sources.get(source.getSourceIdentifier()); if (m != null) { m.remove(source.getRepresentation(), reg); for (SchemaListenerRegistration l : listeners) { l.getInstance().schemaSourceUnregistered(source); } if (m.isEmpty()) { sources.remove(source.getSourceIdentifier()); } } } @Override public SchemaSourceRegistration registerSchemaSource(final SchemaSourceProvider provider, final PotentialSchemaSource source) { final AbstractSchemaSourceRegistration ret = new AbstractSchemaSourceRegistration(provider, source) { @Override protected void removeRegistration() { removeSource(source, this); } }; addSource(source, ret); return ret; } @Override public SchemaListenerRegistration registerSchemaSourceListener(final SchemaSourceListener listener) { final SchemaListenerRegistration ret = new AbstractSchemaListenerRegistration(listener) { @Override protected void removeRegistration() { listeners.remove(this); } }; synchronized (this) { final Collection> col = new ArrayList<>(); for (Multimap, AbstractSchemaSourceRegistration> m : sources.values()) { for (AbstractSchemaSourceRegistration r : m.values()) { col.add(r.getInstance()); } } // Notify first, so translator-type listeners, who react by registering a source // do not cause infinite loop. listener.schemaSourceRegistered(col); listeners.add(ret); } return ret; } private static class SchemaProviderCostComparator implements Comparator> { public static final SchemaProviderCostComparator INSTANCE = new SchemaProviderCostComparator(); @Override public int compare(final AbstractSchemaSourceRegistration o1, final AbstractSchemaSourceRegistration o2) { return o1.getInstance().getCost() - o2.getInstance().getCost(); } } }