package org.dcache.boot; import com.google.common.base.Joiner; import java.io.IOException; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.Reader; import java.io.StringReader; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import diskCacheV111.util.FsPath; import org.dcache.util.ConfigurationProperties; import org.dcache.util.ConfigurationProperties.ProblemConsumer; import org.dcache.util.NetworkUtils; import static org.dcache.boot.Properties.PROPERTY_DOMAINS; import static org.dcache.boot.Properties.PROPERTY_DOMAIN_CELLS; /** * Layout encapsulates the configuration of a set of domains. */ public class Layout { private static final int READ_AHEAD_LIMIT = 256; private static final Pattern SECTION_HEADER = Pattern.compile("^\\s*\\[([^\\]/]+)(/([^\\]/]+))?\\]\\s*$"); private final ConfigurationProperties _properties; private final Map<String,Domain> _domains = new LinkedHashMap<>(); private String _source = "<unknown>"; public Layout(ConfigurationProperties config) { _properties = new ConfigurationProperties(config, new DcacheConfigurationUsageChecker()); } public ConfigurationProperties properties() { return _properties; } public Collection<Domain> getDomains() { return Collections.unmodifiableCollection(_domains.values()); } public Domain getDomain(String name) { return _domains.get(name); } public Domain createDomain(String name) { Domain domain = _domains.get(name); if (domain == null) { domain = new Domain(name, _properties); _domains.put(name, domain); _properties.put(PROPERTY_DOMAINS, Joiner.on(" ").join(_domains.keySet())); } return domain; } /** * Reads a layout definition from the URI. * * @param uri The URI of the layout definition. */ public void load(URI uri) throws URISyntaxException, IOException { URL url = NetworkUtils.toURL(uri); _source = FsPath.create(url.getPath()).name(); try (Reader reader = new InputStreamReader(url.openStream())) { load(reader); } } /** * Reads a layout definition from the input character stream. * * @param in the input character stream. */ public void load(Reader in) throws IOException { LineNumberReader reader = new LineNumberReader(in); _properties.load(_source, loadSection(reader)); String s; while ((s = reader.readLine()) != null) { Matcher matcher = SECTION_HEADER.matcher(s); if (!matcher.matches()) { throw new RuntimeException("Bug detected: Section header expected: " + s); } String domainName = _properties.replaceKeywords(matcher.group(1)); String serviceType = matcher.group(3); if (serviceType == null) { Domain domain = createDomain(domainName); domain.properties().load(_source, loadSection(reader)); } else { Domain domain = getDomain(domainName); if (domain == null) { String message = String.format("Service declaration " + "%s/%s lacks definition of domain %s", domainName, serviceType, domainName); discardSection(reader, message); } else { LineNumberReader sectionReader = loadSection(reader); domain.createService(_source, sectionReader, serviceType); } } } // Cannot do this until all services have been defined for (Domain domain : _domains.values()) { domain.properties().put(PROPERTY_DOMAIN_CELLS, Joiner.on(" ").join(domain.getCellNames())); } } /** * Reads properties until the next section header, returning the result as a * reader. The position is advanced until the next section header or the end * of file. * * @param reader The reader to read from * @return LineNumberReader for the section */ private LineNumberReader loadSection(LineNumberReader reader) throws IOException { int lineNumber = reader.getLineNumber(); StringBuilder section = new StringBuilder(); reader.mark(READ_AHEAD_LIMIT); String line; while ( (line = reader.readLine()) != null && !SECTION_HEADER.matcher(line).matches()) { section.append(line).append('\n'); reader.mark(READ_AHEAD_LIMIT); } reader.reset(); LineNumberReader sectionReader = new LineNumberReader(new StringReader(section.toString())); sectionReader.setLineNumber(lineNumber); return sectionReader; } private void discardSection(LineNumberReader reader, String message) throws IOException { ProblemConsumer consumer = _properties.getProblemConsumer(); consumer.setFilename(_source); consumer.setLineNumberReader(reader); consumer.error(message); loadSection(reader); // discard any configuration for this section } }