/* * Copyright (c) 2012 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * HUMBOLDT EU Integrated Project #030962 * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.common.cache; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.Proxy; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.HashMap; import java.util.Map; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.config.SocketConfig; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import com.google.common.io.ByteStreams; import de.fhg.igd.osgi.util.OsgiUtils; import de.fhg.igd.osgi.util.configuration.IConfigurationService; import de.fhg.igd.osgi.util.configuration.JavaPreferencesConfigurationService; import de.fhg.igd.osgi.util.configuration.NamespaceConfigurationServiceDecorator; import de.fhg.igd.slf4jplus.ALogger; import de.fhg.igd.slf4jplus.ALoggerFactory; import eu.esdihumboldt.util.PlatformUtil; import eu.esdihumboldt.util.http.ProxyUtil; import eu.esdihumboldt.util.http.client.ClientUtil; import eu.esdihumboldt.util.http.client.ClientProxyUtil; import eu.esdihumboldt.util.io.InputStreamDecorator; import groovy.text.SimpleTemplateEngine; import groovy.text.Template; import net.sf.ehcache.Cache; import net.sf.ehcache.Element; import net.sf.ehcache.store.MemoryStoreEvictionPolicy; /** * This class manages requests and caching for remote files. * * @author Andreas Burchert * @partner 01 / Fraunhofer Institute for Computer Graphics Research */ public class Request { private static final ALogger log = ALoggerFactory.getLogger(Request.class); private static final String CACHE_NAME = "haleResourceCache"; private boolean cacheEnabled; private static final String DELIMITER = "/"; //$NON-NLS-1$ private final IConfigurationService configService; private final Map<Proxy, CloseableHttpClient> clients = new HashMap<Proxy, CloseableHttpClient>(); /** * The instance of {@link Request} */ private static Request instance; /** * Constructor. */ private Request() { // FIXME right way to aquire configuration service? IConfigurationService org = OsgiUtils.getService(IConfigurationService.class); if (org == null) { // if no configuration service is present, fall back to new instance // 1. use user prefs, may not have rights to access system prefs // 2. no default properties // 3. default to system properties org = new JavaPreferencesConfigurationService(false, null, true); } configService = new NamespaceConfigurationServiceDecorator(org, Request.class.getPackage().getName().replace(".", DELIMITER), //$NON-NLS-1$ DELIMITER); // get saved seeting cacheEnabled = configService.getBoolean("hale.cache.enabled", true); //$NON-NLS-1$ // initialize the cache init(); } /** * Initialize the cache. */ private void init() { if (cacheEnabled) { File cacheDir = PlatformUtil.getInstanceLocation(); if (cacheDir == null) { cacheDir = new File(System.getProperty("java.io.tmpdir")); } try { // create the configuration from the template SimpleTemplateEngine engine = new SimpleTemplateEngine( Request.class.getClassLoader()); Template template = engine.createTemplate(Request.class.getResource("ehcache.xml")); Map<String, Object> binding = new HashMap<>(); // replace the cache directory binding.put("cache_dir", cacheDir.getAbsolutePath()); ByteArrayOutputStream data = new ByteArrayOutputStream(); try (Writer writer = new OutputStreamWriter(data, "UTF-8")) { template.make(binding).writeTo(writer); } // initialize the cache manager if (HaleCacheManager.create(new ByteArrayInputStream(data.toByteArray())) .getCache(CACHE_NAME) != null) { return; } // create a Cache instance - providing cachePath has no effect Cache cache = new Cache(CACHE_NAME, 300, MemoryStoreEvictionPolicy.LRU, true, null, true, 0, 0, true, 0, null); // add it to CacheManger HaleCacheManager.getInstance().addCache(cache); } catch (Exception e) { log.error("Cache initialization failed", e); } } } /** * Returns the instance of this class * * @return instance */ public synchronized static Request getInstance() { if (instance == null) { instance = new Request(); } return instance; } /** * @param uri to load from * * @return {@link InputStream} * * @throws URISyntaxException if the URI is malformed * @throws Exception may contain IOException * * @see Request#get(URI) */ public InputStream get(String uri) throws URISyntaxException, Exception { return get(new URI(uri)); } /** * This function handles all Request and does the caching. * * @param uri to file * * @return an {@link InputStream} to uri * * @throws Exception if something goes wrong */ public InputStream get(URI uri) throws Exception { String scheme = uri.getScheme(); if (!scheme.equals("http") && !scheme.equals("https")) { //$NON-NLS-1$ // get non-HTTP(S) resources through URL.openStream return getLocal(uri.toURL()); } // no caching activated if (!cacheEnabled) { return openStream(uri); } // get the current cache for web requests Cache cache = HaleCacheManager.getInstance().getCache(CACHE_NAME); String key = uri.toString(); // removeSpecialChars(uri.toString()); Element element = cache.get(key); if (element == null) { // if the entry does not exist fetch it from the web InputStream in = openStream(uri); byte[] data; try { data = ByteStreams.toByteArray(in); } finally { in.close(); } // and add it to the cache cache.put(new Element(key, data)); return new ByteArrayInputStream(data); } else { byte[] data = (byte[]) element.getObjectValue(); return new ByteArrayInputStream(data); } } /** * Open a stream for the given URI. * * @param uri the URI * @return the opened input stream, the caller is responsible to close it * @throws IOException if opening the input stream fails */ private InputStream openStream(URI uri) throws IOException { Proxy proxy = ProxyUtil.findProxy(uri); CloseableHttpClient client = getClient(proxy); HttpGet httpget = new HttpGet(uri); final CloseableHttpResponse response = client.execute(httpget); InputStream in; if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { // fall back to URL.openStream in = uri.toURL().openStream(); } else { HttpEntity entity = response.getEntity(); // create InputStream in = new InputStreamDecorator(entity.getContent()) { @Override public void close() throws IOException { super.close(); // ensure the response is closed response.close(); } @Override protected void finalize() throws Throwable { // not sure if this actually has any effect close(); super.finalize(); } }; } return in; } /** * Get the HTTP client for a given proxy * * @param proxy the proxy * @return the client configured for the proxy */ private synchronized CloseableHttpClient getClient(Proxy proxy) { CloseableHttpClient client = clients.get(proxy); if (client == null) { HttpClientBuilder builder = ClientUtil.threadSafeHttpClientBuilder(); builder = ClientProxyUtil.applyProxy(builder, proxy); // set timeouts // determine from Oracle VM specific system properties, see // http://docs.oracle.com/javase/7/docs/technotes/guides/net/properties.html int connectTimeout; String cts = System.getProperty("sun.net.client.defaultConnectTimeout"); try { connectTimeout = Integer.parseInt(cts); } catch (Exception e) { // fall back to default connectTimeout = 10000; } int socketTimeout; String sts = System.getProperty("sun.net.client.defaultReadTimeout"); try { socketTimeout = Integer.parseInt(sts); } catch (Exception e) { // fall back to default socketTimeout = 20000; } // socket timeout /* * Unclear when this setting would apply (doc says for non-blocking * I/O operations), it does not seem to be applied for requests as * done in openStream (instead the value in * RequestConfig.socketTimeout is used) */ SocketConfig socketconfig = SocketConfig.custom().setSoTimeout(socketTimeout).build(); // connection and socket timeout RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout) .setConnectTimeout(connectTimeout).build(); client = builder.setDefaultRequestConfig(requestConfig) .setDefaultSocketConfig(socketconfig).build(); clients.put(proxy, client); } return client; } /** * This function is used if the url is a local file. * * @param file path to file * * @return {@link InputStream} * * @throws IOException if the file could not be read */ private InputStream getLocal(URL file) throws IOException { return file.openStream(); } /** * @see HaleCacheManager#flush(String) */ public void flush() { if (cacheEnabled) HaleCacheManager.flush(CACHE_NAME); } /** * @see HaleCacheManager#shutdown() */ public void shutdown() { HaleCacheManager.getInstance().shutdown(); } /** * @see HaleCacheManager#removalAll() */ public void clear() { HaleCacheManager.getInstance().getCache(CACHE_NAME).removeAll(); } /** * Is true if caching is enabled. * * @return boolean */ public boolean isCacheEnabled() { return cacheEnabled; } /** * * @param enabled enabled */ public void setCacheEnabled(boolean enabled) { this.cacheEnabled = enabled; init(); } }