/** * Copyright 2016 Hortonworks. * * 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. **/ package com.hortonworks.registries.schemaregistry.client; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; import java.nio.channels.FileLock; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; /** * */ public class ClassLoaderCache { private static final Logger LOG = LoggerFactory.getLogger(ClassLoaderCache.class); public static final String CACHE_SIZE_KEY = SchemaRegistryClient.Configuration.CLASSLOADER_CACHE_SIZE.name(); public static final String CACHE_EXPIRY_INTERVAL_KEY = SchemaRegistryClient.Configuration.CLASSLOADER_CACHE_EXPIRY_INTERVAL_SECS.name(); private final LoadingCache<String, ClassLoader> loadingCache; private final SchemaRegistryClient schemaRegistryClient; private final File localJarsDir; public ClassLoaderCache(final SchemaRegistryClient schemaRegistryClient) { this.schemaRegistryClient = schemaRegistryClient; CacheLoader<String, ClassLoader> cacheLoader = new CacheLoader<String, ClassLoader>() { @Override public ClassLoader load(String fileId) throws Exception { File file = getFile(fileId); return new URLClassLoader(new URL[]{file.toURI().toURL()}); } }; SchemaRegistryClient.Configuration configuration = schemaRegistryClient.getConfiguration(); loadingCache = CacheBuilder.newBuilder() .maximumSize(((Number) configuration.getValue(CACHE_SIZE_KEY)).longValue()) .expireAfterAccess(((Number) configuration.getValue(CACHE_EXPIRY_INTERVAL_KEY)).longValue(), TimeUnit.SECONDS) .build(cacheLoader); localJarsDir = new File((String) schemaRegistryClient.getConfiguration().getValue(SchemaRegistryClient.Configuration.LOCAL_JAR_PATH.name())); if (!localJarsDir.exists()) { if (!localJarsDir.mkdirs()) { throw new RuntimeException("Could not create given local jar storage dir: [" + localJarsDir.getAbsolutePath() + "]"); } } } private File getFile(String fileId) throws IOException { File file = new File(localJarsDir, fileId); boolean created = file.createNewFile(); if (created) { LOG.debug("File [{}] is created successfully", file); } else { LOG.debug("File [{}] already exists", file); } try (FileOutputStream fos = new FileOutputStream(file); FileLock fileLock = fos.getChannel().lock(); FileInputStream fileInputStream = new FileInputStream(file)) { LOG.debug("Took file lock: [{}] for file: [{}]", fileLock, file); if (fileInputStream.read() != -1) { LOG.debug("File [{}] is already written with content and returning the existing file", file); return file; } else { LOG.debug("File [{}] does not have content, downloading and storing started..", file); try (InputStream inputStream = schemaRegistryClient.downloadFile(fileId)) { IOUtils.copy(inputStream, fos); LOG.debug("Finished storing file [{}]", file); } catch (Exception e) { // truncate it now, so that next time it would be fetched again. fos.getChannel().truncate(0); throw e; } } } return file; } public ClassLoader getClassLoader(String fileId) { try { return loadingCache.get(fileId); } catch (ExecutionException e) { throw new RuntimeException(e); } } }