2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.bgpcep.config.loader.impl;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.base.Stopwatch;
14 import java.io.FileInputStream;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.RandomAccessFile;
18 import java.net.URISyntaxException;
19 import java.nio.channels.FileChannel;
20 import java.nio.channels.FileLock;
21 import java.nio.file.ClosedWatchServiceException;
22 import java.nio.file.WatchKey;
23 import java.nio.file.WatchService;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
28 import java.util.concurrent.TimeUnit;
29 import java.util.regex.Pattern;
30 import java.util.stream.Collectors;
31 import javax.xml.stream.XMLInputFactory;
32 import javax.xml.stream.XMLStreamException;
33 import javax.xml.stream.XMLStreamReader;
34 import org.checkerframework.checker.lock.qual.GuardedBy;
35 import org.opendaylight.bgpcep.config.loader.spi.ConfigFileProcessor;
36 import org.opendaylight.bgpcep.config.loader.spi.ConfigLoader;
37 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
38 import org.opendaylight.yangtools.concepts.AbstractRegistration;
39 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
41 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
42 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
43 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
44 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
45 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
46 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49 import org.xml.sax.SAXException;
51 public final class ConfigLoaderImpl implements ConfigLoader, AutoCloseable {
52 private static final Logger LOG = LoggerFactory.getLogger(ConfigLoaderImpl.class);
53 private static final String INTERRUPTED = "InterruptedException";
54 private static final String EXTENSION = "-.*\\.xml";
55 private static final String INITIAL = "^";
56 private static final String READ = "rw";
57 private static final long TIMEOUT_SECONDS = 5;
59 private final Map<String, ConfigFileProcessor> configServices = new HashMap<>();
60 private final EffectiveModelContext schemaContext;
61 private final BindingNormalizedNodeSerializer bindingSerializer;
62 private final String path;
63 private final Thread watcherThread;
64 private final File file;
66 private boolean closed = false;
68 public ConfigLoaderImpl(final EffectiveModelContext schemaContext,
69 final BindingNormalizedNodeSerializer bindingSerializer, final FileWatcher fileWatcher) {
70 this.schemaContext = requireNonNull(schemaContext);
71 this.bindingSerializer = requireNonNull(bindingSerializer);
72 this.path = requireNonNull(fileWatcher.getPathFile());
73 this.file = new File(this.path);
74 this.watcherThread = new Thread(new ConfigLoaderImplRunnable(requireNonNull(fileWatcher.getWatchService())));
78 this.watcherThread.start();
79 LOG.info("Config Loader service initiated");
82 private synchronized void handleConfigFile(final ConfigFileProcessor config, final String filename) {
83 final NormalizedNode<?, ?> dto;
85 dto = parseDefaultConfigFile(config, filename);
86 } catch (final IOException | XMLStreamException e) {
87 LOG.warn("Failed to parse config file {}", filename, e);
90 LOG.info("Loading initial config {}", filename);
91 config.loadConfiguration(dto);
94 private NormalizedNode<?, ?> parseDefaultConfigFile(final ConfigFileProcessor config, final String filename)
95 throws IOException, XMLStreamException {
96 final NormalizedNodeResult result = new NormalizedNodeResult();
97 final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
99 final File newFile = new File(this.path, filename);
100 FileChannel channel = new RandomAccessFile(newFile, READ).getChannel();
102 FileLock lock = null;
103 final Stopwatch stopwatch = Stopwatch.createStarted();
104 while (lock == null || stopwatch.elapsed(TimeUnit.SECONDS) > TIMEOUT_SECONDS) {
106 lock = channel.tryLock();
107 } catch (final IllegalStateException e) {
113 } catch (InterruptedException e) {
114 LOG.warn("Failed to lock xml", e);
119 try (InputStream resourceAsStream = new FileInputStream(newFile)) {
120 final XMLInputFactory factory = XMLInputFactory.newInstance();
121 final XMLStreamReader reader = factory.createXMLStreamReader(resourceAsStream);
123 final SchemaNode schemaNode = SchemaContextUtil
124 .findDataSchemaNode(this.schemaContext, config.getSchemaPath());
125 try (XmlParserStream xmlParser = XmlParserStream.create(streamWriter, this.schemaContext, schemaNode)) {
126 xmlParser.parse(reader);
127 } catch (final URISyntaxException | XMLStreamException | IOException | SAXException e) {
128 LOG.warn("Failed to parse xml", e);
135 return result.getResult();
139 public synchronized AbstractRegistration registerConfigFile(final ConfigFileProcessor config) {
140 final String pattern = INITIAL + config.getSchemaPath().getLastComponent().getLocalName() + EXTENSION;
141 this.configServices.put(pattern, config);
143 final File[] fList = this.file.listFiles();
144 final List<String> newFiles = new ArrayList<>();
146 for (final File newFile : fList) {
147 if (newFile.isFile()) {
148 final String filename = newFile.getName();
149 if (Pattern.matches(pattern, filename)) {
150 newFiles.add(filename);
155 for (final String filename : newFiles) {
156 handleConfigFile(config, filename);
158 return new AbstractRegistration() {
160 protected void removeRegistration() {
161 synchronized (ConfigLoaderImpl.this) {
162 ConfigLoaderImpl.this.configServices.remove(pattern);
169 public BindingNormalizedNodeSerializer getBindingNormalizedNodeSerializer() {
170 return this.bindingSerializer;
175 public synchronized void close() {
176 LOG.info("Config Loader service closed");
178 this.watcherThread.interrupt();
181 private class ConfigLoaderImplRunnable implements Runnable {
183 private final WatchService watchService;
185 ConfigLoaderImplRunnable(final WatchService watchService) {
186 this.watchService = watchService;
191 while (!Thread.currentThread().isInterrupted()) {
196 private synchronized void handleChanges() {
199 key = this.watchService.take();
200 } catch (final InterruptedException | ClosedWatchServiceException e) {
201 if (!ConfigLoaderImpl.this.closed) {
202 LOG.warn(INTERRUPTED, e);
203 Thread.currentThread().interrupt();
209 final List<String> fileNames = key.pollEvents()
210 .stream().map(event -> event.context().toString())
211 .collect(Collectors.toList());
212 fileNames.forEach(this::handleEvent);
214 final boolean reset = key.reset();
216 LOG.warn("Could not reset the watch key.");
221 private synchronized void handleEvent(final String filename) {
222 ConfigLoaderImpl.this.configServices.entrySet().stream()
223 .filter(entry -> Pattern.matches(entry.getKey(), filename))
224 .forEach(entry -> handleConfigFile(entry.getValue(), filename));