package com.linkedin.databus2.schemas; /* * * Copyright 2013 LinkedIn Corp. All rights reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ import java.io.File; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; import com.linkedin.databus.core.DatabusRuntimeException; import com.linkedin.databus.core.util.ConfigBuilder; import com.linkedin.databus.core.util.InvalidConfigException; import com.linkedin.databus2.core.DatabusException; /** * Implements a simple {@link SchemaRegistryService} by reading schemas from a directory. * @author cbotev */ public class FileSystemSchemaRegistryService extends VersionedSchemaSetBackedRegistryService { public static final String MODULE = FileSystemSchemaRegistryService.class.getName(); public static final Logger LOG = Logger.getLogger(MODULE); private final FileSystemVersionedSchemaSetProvider _fsSchemaSetProvider; private final ResourceVersionedSchemaSetProvider _resourceSchemaSetProvider; private final StaticConfig _config; private Thread _schemaSetRefreshThread; private final AtomicBoolean _stopRefreshThread = new AtomicBoolean(); private long _lastRefreshTs = -1; public static FileSystemSchemaRegistryService build(Config config) throws InvalidConfigException { return build(config.build()); } public static FileSystemSchemaRegistryService build(StaticConfig config) { FileSystemSchemaRegistryService service = new FileSystemSchemaRegistryService(config); service.initializeSchemaSet(); if (0 == service.getCurSchemaSet().size()) { throw new DatabusRuntimeException("no schemas loaded; please check the schemas directory: " + config.getSchemaDir().getAbsolutePath() ); } if (config.getRefreshPeriodMs() > 0) { service.startSchemasRefreshThread(); } return service; } private FileSystemSchemaRegistryService(StaticConfig config) { super(); _curSchemaSet = new VersionedSchemaSet(); _fsSchemaSetProvider = new FileSystemVersionedSchemaSetProvider(Arrays.asList(config.getSchemaDir())); _resourceSchemaSetProvider = new ResourceVersionedSchemaSetProvider(this.getClass().getClassLoader()); _schemaSetRefreshThread = null; _config = config; } /** * Starts the thread that periodically ({@link Config#getRefreshPeriodMs()} refreshes the schema set. * @return true if started */ public boolean startSchemasRefreshThread() { if (_config.getRefreshPeriodMs() <= 0 || null != _schemaSetRefreshThread) { return false; } LOG.info("Starting schema refresh thread"); _stopRefreshThread.set(false); _schemaSetRefreshThread = new Thread(new SchemaSetRefreshThread(), "SchemaRefreshThread"); _schemaSetRefreshThread.setDaemon(true); _schemaSetRefreshThread.start(); return true; } public void stopSchemasRefreshThread() { if (null == _schemaSetRefreshThread) return; LOG.info("Stopping schema refresh thread"); _stopRefreshThread.set(true); _schemaSetRefreshThread.interrupt(); while (_schemaSetRefreshThread.isAlive()) { try { _schemaSetRefreshThread.join(); } catch (InterruptedException ie) {} } } /** * Registers a schema represented by its JSON schema definition. Note that the schema will not be * persisted on disk. If there is a schema registered with the same name, it's version will be * increased by one. * * IMPORTANT: If you don't {@link FileSystemSchemaRegistryService#stopSchemasRefreshThread()}, * the schema will be overwritten. */ @Override public void registerSchema(VersionedSchema schema) throws DatabusException { super.registerSchema(schema); } private void initializeSchemaSet() { LOG.info("initializing schema registry"); refreshSchemaSet(); if (0 > _lastRefreshTs) { LOG.info("loading schemas from resources "); _curSchemaSet = _resourceSchemaSetProvider.loadSchemas(); LOG.info("schemas from resources loaded"); } } private void refreshSchemaSet() { LOG.info("refreshing schema registry"); File schemaDir = _config.getSchemaDir(); if (!_config.isFallbackToResources() && ! schemaDir.exists()) { LOG.warn("schema dir not found:" + _config.getSchemaDir()); return; } if (schemaDir.exists()) { _curSchemaSet = _fsSchemaSetProvider.loadSchemas(); _lastRefreshTs = System.currentTimeMillis(); } else LOG.info("skipping not existant schema directory: " + schemaDir.getAbsolutePath()); LOG.info("schema registry refreshed"); } private class SchemaSetRefreshThread implements Runnable { public SchemaSetRefreshThread() { } @Override public void run() { while (! _stopRefreshThread.get()) { try { Thread.sleep(_config.getRefreshPeriodMs()); } catch (InterruptedException ie) {//do nothing } refreshSchemaSet(); } LOG.info("Quitting schema refresh thread"); } } public static class StaticConfig { private final File _schemaDir; private final long _refreshPeriodMs; private final boolean _enabled; private final boolean _fallbackToResources; public StaticConfig(File schemaDir, long schemasRefreshPeriodMs, boolean enabled, boolean fallbackToResources) { super(); _schemaDir = schemaDir; _refreshPeriodMs = schemasRefreshPeriodMs; _enabled = enabled; _fallbackToResources = fallbackToResources; } public File getSchemaDir() { return _schemaDir; } public long getRefreshPeriodMs() { return _refreshPeriodMs; } public boolean isEnabled() { return _enabled; } /** Try to load schemas from resources if directory is not available. */ public boolean isFallbackToResources() { return _fallbackToResources; } } public static class Config implements ConfigBuilder<StaticConfig> { public static final String DEFAULT_FS_SCHEMA_REGISTRY_PATH = "schemas_registry"; public static final long DEFAULT_FS_SCHEMA_REGISTRY_REFRESH_MS = 3600000; private String _schemaDir; private long _refreshPeriodMs; private boolean _enabled; private boolean _fallbackToResources; /** Default constructor. Uses the system properties for initialization. */ public Config() { _schemaDir = DEFAULT_FS_SCHEMA_REGISTRY_PATH; _refreshPeriodMs = DEFAULT_FS_SCHEMA_REGISTRY_REFRESH_MS; _enabled = true; _fallbackToResources = true; } /** * Obtains the directory where the schemas are stored. * @return the absolute path of the directory */ public String getSchemaDir() { return _schemaDir; } /** * Changes setting with the directory where the schemas are stored. * @param schemaDir the new setting value */ public void setSchemaDir(String schemaDir) { _schemaDir = schemaDir; } /** * Obtains the interval at which the schema registry will be synced with the file system. * @return the interval in ms */ public long getRefreshPeriodMs() { return _refreshPeriodMs; } /** * Changes the interval at which the schema registry will be synced with the file system * @param schemasRefreshPeriodMs the interval in ms; a value <= 0 disables the refresh; */ public void setRefreshPeriodMs(long schemasRefreshPeriodMs) { _refreshPeriodMs = schemasRefreshPeriodMs; } @Override public StaticConfig build() throws InvalidConfigException { File schemaDirFile = new File(_schemaDir); if (_enabled && !_fallbackToResources && ! schemaDirFile.exists()) { throw new InvalidConfigException("Schemas dir not found: " + _schemaDir); } return new StaticConfig(schemaDirFile, _refreshPeriodMs, _enabled, _fallbackToResources); } public boolean isEnabled() { return _enabled; } public void setEnabled(boolean enabled) { _enabled = enabled; } public boolean isFallbackToResources() { return _fallbackToResources; } public void setFallbackToResources(boolean fallbackToResources) { _fallbackToResources = fallbackToResources; } } }