package dmg.cells.nucleus; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.SetMultimap; import com.google.common.math.IntMath; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Random; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import org.dcache.util.ColumnWriter; import static java.util.stream.Collectors.toList; public class CellRoutingTable implements Serializable { private static final long serialVersionUID = -1456280129622980563L; private final ListMultimap<String, CellRoute> _queue = ArrayListMultimap.create(); private final SetMultimap<String, CellRoute> _domain = LinkedHashMultimap.create(); private final SetMultimap<String, CellRoute> _exact = LinkedHashMultimap.create(); private final Map<String, CopyOnWriteArraySet<CellRoute>> _topic = new HashMap<>(); private final AtomicReference<CellRoute> _dumpster = new AtomicReference<>(); private final List<CellRoute> _default = new ArrayList<>(); private final Random _random = new Random(); public void add(CellRoute route) throws IllegalArgumentException { String dest; switch (route.getRouteType()) { case CellRoute.EXACT: case CellRoute.ALIAS: dest = route.getCellName() + '@' + route.getDomainName(); synchronized (_exact) { if (!_exact.put(dest, route)) { throw new IllegalArgumentException("Duplicated route entry for : " + dest); } } break; case CellRoute.QUEUE: dest = route.getCellName(); synchronized (_queue) { if (_queue.containsEntry(dest, route)) { throw new IllegalArgumentException("Duplicated route entry for : " + dest); } _queue.put(dest, route); } break; case CellRoute.TOPIC: dest = route.getCellName(); synchronized (_topic) { if (!_topic.computeIfAbsent(dest, key -> new CopyOnWriteArraySet()).add(route)) { throw new IllegalArgumentException("Duplicated route entry for : " + dest); } } break; case CellRoute.DOMAIN: dest = route.getDomainName(); synchronized (_domain) { if (!_domain.put(dest, route)) { throw new IllegalArgumentException("Duplicated route entry for : " + dest); } } break; case CellRoute.DEFAULT: synchronized (_default) { if (!_default.contains(route)) { _default.add(route); } } break; case CellRoute.DUMPSTER: if (!_dumpster.compareAndSet(null, route)) { throw new IllegalArgumentException("Duplicated route entry for dumpster"); } break; } } public void delete(CellRoute route) throws IllegalArgumentException { String dest; switch (route.getRouteType()) { case CellRoute.EXACT: case CellRoute.ALIAS: dest = route.getCellName() + '@' + route.getDomainName(); synchronized (_exact) { if (!_exact.remove(dest, route)) { throw new IllegalArgumentException("Route entry not found for : " + dest); } } break; case CellRoute.QUEUE: dest = route.getCellName(); synchronized (_queue) { if (!_queue.remove(dest, route)) { throw new IllegalArgumentException("Route entry not found for : " + dest); } } break; case CellRoute.TOPIC: dest = route.getCellName(); synchronized (_topic) { Set<CellRoute> routes = _topic.get(dest); if (!routes.remove(route)) { throw new IllegalArgumentException("Route entry not found for : " + dest); } if (routes.isEmpty()) { _topic.remove(dest); } } break; case CellRoute.DOMAIN: dest = route.getDomainName(); synchronized (_domain) { if (!_domain.remove(dest, route)) { throw new IllegalArgumentException("Route entry not found for : " + dest); } } break; case CellRoute.DEFAULT: synchronized (_default) { if (!_default.remove(route)) { throw new IllegalArgumentException("Route entry not found for default"); } } break; case CellRoute.DUMPSTER: CellRoute currentDumpster = _dumpster.get(); if (!Objects.equals(currentDumpster, route) || !_dumpster.compareAndSet(currentDumpster, null)) { throw new IllegalArgumentException("Route entry not found dumpster"); } break; } } public Collection<CellRoute> delete(CellAddressCore target) { Collection<CellRoute> deleted = new ArrayList<>(); String addr = target.toString(); synchronized (_exact) { delete(_exact.values(), addr, deleted); } synchronized (_queue) { delete(_queue.values(), addr, deleted); } synchronized (_domain) { delete(_domain.values(), addr, deleted); } synchronized (_topic) { /* We cannot use the regular delete method because a CopyOnWriteArraySet iterator * doesn't allow manipulating operations. We trade expensive deletion for not having * to copy the set of topic routes in findTopicRoutes. */ Iterator<CopyOnWriteArraySet<CellRoute>> iterator = _topic.values().iterator(); while (iterator.hasNext()) { Set<CellRoute> routes = iterator.next(); List<CellRoute> toRemove = routes.stream().filter(route -> route.getTargetName().equals(addr)).collect(toList()); routes.removeAll(toRemove); if (routes.isEmpty()) { iterator.remove(); } deleted.addAll(toRemove); } } synchronized (_default) { delete(_default, addr, deleted); } return deleted; } private void delete(Collection<CellRoute> values, String addr, Collection<CellRoute> deleted) { Iterator<CellRoute> iterator = values.iterator(); while (iterator.hasNext()) { CellRoute route = iterator.next(); if (route.getTargetName().equals(addr)) { iterator.remove(); deleted.add(route); } } } public CellRoute find(CellAddressCore addr, boolean allowRemote) { String cellName = addr.getCellName(); String domainName = addr.getCellDomainName(); Optional<CellRoute> route; synchronized (_exact) { route = _exact.get(cellName + '@' + domainName).stream().findFirst(); } if (route.isPresent()) { return route.get(); } if (domainName.equals("local")) { // // this is not really local but wellknown // we checked for local before we called this. // synchronized (_queue) { List<CellRoute> routes = _queue.get(cellName); if (!allowRemote) { CellRoute[] localRoutes = routes.stream().filter(r -> !r.getTarget().isDomainAddress()).toArray(CellRoute[]::new); return (localRoutes.length > 0) ? localRoutes[_random.nextInt(localRoutes.length)] : null; } else if (!routes.isEmpty()) { return routes.get(_random.nextInt(routes.size())); } } } else { synchronized (_domain) { route = _domain.get(domainName).stream().findFirst(); } if (route.isPresent()) { return route.get(); } } synchronized (_default) { return _default.isEmpty() ? null : _default.get(IntMath.mod(addr.hashCode(), _default.size())); } } public Set<CellRoute> findTopicRoutes(CellAddressCore addr) { String cellName = addr.getCellName(); String domainName = addr.getCellDomainName(); if (!domainName.equals("local")) { return Collections.emptySet(); } CopyOnWriteArraySet<CellRoute> routes; synchronized (_topic) { routes = _topic.get(cellName); } return (routes != null) ? routes : Collections.emptySet(); } public String toString() { ColumnWriter writer = new ColumnWriter() .header("CELL").left("cell").space() .header("DOMAIN").left("domain").space() .header("GATEWAY").left("gateway").space() .header("TYPE").left("type"); Consumer<CellRoute> append = route -> writer.row() .value("cell", route.getCellName()) .value("domain", route.getDomainName()) .value("gateway", route.getTargetName()) .value("type", route.getRouteTypeName()); synchronized (_topic) { _topic.values().forEach(routes -> routes.forEach(append)); } synchronized (_exact) { _exact.values().forEach(append); } synchronized (_queue) { _queue.values().forEach(append); } synchronized (_domain) { _domain.values().forEach(append); } synchronized (_default) { _default.forEach(append); } CellRoute dumpsterRoute = _dumpster.get(); if (dumpsterRoute != null) { append.accept(dumpsterRoute); } return writer.toString(); } public CellRoute[] getRoutingList() { List<CellRoute> routes = new ArrayList<>(); synchronized (_topic) { _topic.values().forEach(routes::addAll); } synchronized (_exact) { routes.addAll(_exact.values()); } synchronized (_queue) { routes.addAll(_queue.values()); } synchronized (_domain) { routes.addAll(_domain.values()); } synchronized (_default) { routes.addAll(_default); } CellRoute dumpsterRoute = _dumpster.get(); if (dumpsterRoute != null) { routes.add(dumpsterRoute); } return routes.toArray(new CellRoute[routes.size()]); } public boolean hasDefaultRoute() { synchronized (_default) { return !_default.isEmpty(); } } }