All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.disney.uriparcel.URIParcel Maven / Gradle / Ivy

/*******************************************************************************
 * © 2018 Disney | ABC Television Group
 *
 * Licensed under the Apache License, Version 2.0 (the "Apache License")
 * with the following modification; you may not use this file except in
 * compliance with the Apache License and the following modification to it:
 * Section 6. Trademarks. is deleted and replaced with:
 *
 * 6. Trademarks. This License does not grant permission to use the trade
 *     names, trademarks, service marks, or product names of the Licensor
 *     and its affiliates, except as required to comply with Section 4(c) of
 *     the License and to reproduce the content of the NOTICE file.
 *
 * You may obtain a copy of the Apache License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Apache License with the above modification is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the Apache License for the specific
 * language governing permissions and limitations under the Apache License.
 *******************************************************************************/
package com.disney.uriparcel;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Maintains a local representation of state loaded from some underlying URI that might change
 * over time, but that is meant to be consumed in a consistent fashion.  
 * 

* A synchronous load from the origin is performed on first call(), subsequently only asynchronous * loads will be performed if a refresh interval has been specified and has passed. *

* Having no refresh is essentially a read-once and forget parsing of the remote source, whereas * a refresh keeps the object up-to-date over time in case it changes on the origin *

* URIParcel relies on ContentStreamStores to provide underlying storage and retrieval of binary streams, * and ContentValueHandlers to provide serialization and deserialization of those streams to specific java classes. * Both of these components are discovered using the Java ServiceLoader mechanism to support custom plugins. * * @author Alex Vigdor * */ public class URIParcel implements Callable { private static ExecutorService backgroundRefresh = Executors.newSingleThreadExecutor(new ThreadFactory() { public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName("URI Parcel Refresh "+t.getName()); return t; } }); private static ServiceLoader streamStores = ServiceLoader.load(ContentStreamStore.class); private static ServiceLoader valueHandlers = ServiceLoader.load(ContentValueHandler.class); private static List sortedStreamLoaders; private static List sortedValueLoaders; private static Logger log = Logger.getLogger(URIParcel.class.getName()); private Class type; private Callable source; volatile V value; boolean loaded = false; volatile long loadtime = 0; volatile boolean refreshing=false; private Callable refresh = null; private Callable> config; public URIParcel(Class type){ this.type=type; } public URIParcel(Class type, Callable source){ this.type=type; this.source=source; } public URIParcel(Class type, Callable source, Map config){ this.type=type; this.source=source; this.config = new SimpleCallable>(config); } public URIParcel(Class type, Callable source, Callable> config){ this.type=type; this.source=source; this.config = config; } public URIParcel(Class type, Callable source, long refresh){ this.type=type; this.source=source; this.refresh=new SimpleCallable(refresh); } public URIParcel(Class type, Callable source, Callable refresh, Callable> config){ this.type=type; this.source=source; this.refresh=refresh; this.config=config; } public URIParcel(Class type, Callable source, long refresh, Map config){ this.type=type; this.source=source; this.refresh=new SimpleCallable(refresh); this.config = new SimpleCallable>(config); } public URIParcel(Class type, URI source){ this.type=type; setSource(source); } public URIParcel(Class type, URI source, Map config){ this.type=type; setSource(source); this.config = new SimpleCallable>(config); } public URIParcel(Class type, URI source, long refresh){ this.type=type; setSource(source); this.refresh=new SimpleCallable(refresh);; } public URIParcel(Class type, URI source, long refresh, Map config){ this.type=type; setSource(source); this.refresh=new SimpleCallable(refresh); this.config = new SimpleCallable>(config); } public static T get(URI uri, Class type) throws Exception{ return new URIParcel(type,uri).call(); } public static void put(URI uri, Object obj) throws Exception{ put(uri,obj,null); } public static void put(URI uri, T obj, String contentType) throws Exception{ @SuppressWarnings("unchecked") Class tC = (Class) obj.getClass(); new URIParcel(tC, uri).put(obj,contentType); } protected V doLoad() throws Exception { try{ ContentStream stream = getContentStream(source.call(),config!=null?config.call():null); if(stream==null){ throw new FileNotFoundException("No stream found for "+source.call()); } value = getContentValue(stream); loaded = true; } finally{ loadtime = System.currentTimeMillis(); refreshing=false; } return value; } public Callable getSource() { return source; } public V call() throws Exception{ if(!loaded){ //first-time synchronous load return doLoad(); } else if(refresh!=null){ long rf = refresh.call(); if(rf>0 && !refreshing){ long curTime = System.currentTimeMillis(); if(curTime > (loadtime+rf)){ loadtime=curTime; refreshing=true; //perform async load backgroundRefresh.submit(new Runnable() { public void run() { try{ doLoad(); } catch(Exception e){ log.log(Level.SEVERE, "Error refreshing URIParcel "+source, e); } } }); } } } return value; } public void setSource(Callable source) { this.source = source; } public void setSource(final URI source) { this.source = new Callable() { public URI call() throws Exception { return source; } };; } public Callable getRefresh() { return refresh; } public void setRefresh(Callable refresh) { this.refresh = refresh; } public void setRefresh(long refresh){ this.refresh=new SimpleCallable(refresh); } public Class getType(){ return type; } protected static List getStreamLoaders(){ if(sortedStreamLoaders==null){ List csl = new ArrayList(); for(ContentStreamStore loader: streamStores){ csl.add(loader); } Collections.sort(csl); sortedStreamLoaders = csl; } return sortedStreamLoaders; } protected static List getValueLoaders(){ if(sortedValueLoaders==null){ List csl = new ArrayList(); for(ContentValueHandler loader: valueHandlers){ csl.add(loader); } Collections.sort(csl); sortedValueLoaders = csl; } return sortedValueLoaders; } protected static ContentStream getContentStream(URI uri, Map config) throws IOException{ ContentStream content = new ContentStream(); content.setUri(uri); for(ContentStreamStore loader: getStreamLoaders()){ if(log.isLoggable(Level.FINE)){ log.fine("Trying stream loader "+loader); } if(loader.load(content,config)){ return content; } } throw new IllegalArgumentException("No ContentStreamLoader found for "+uri); } protected V getContentValue(ContentStream stream) throws Exception{ Map conf = config!=null?config.call():null; for(ContentValueHandler loader: getValueLoaders()){ if(log.isLoggable(Level.FINE)){ log.fine("Trying value loader "+loader); } try{ if(loader.isSupported(type, stream.getContentType())){ InputStream is = stream.getContent().call(); try{ V val = (V) loader.load(is, stream.getContentType(), type, conf); if(val!=null){ return val; } } finally{ is.close(); } } } catch(Exception e){ StringBuilder msgBuilder = new StringBuilder(e.getClass().getName()).append("; "); if(e.getMessage()!=null){ msgBuilder.append(e.getMessage()); } for(StackTraceElement elem: e.getStackTrace()){ if(elem.getLineNumber()>=0 && elem.getClassName().equals(loader.getClass().getName())){ msgBuilder.append("; ").append(elem.toString()); } } String msg = msgBuilder.toString(); if(log.isLoggable(Level.FINE)){ log.log(Level.FINE, "Skipping value handler for GET "+loader+" due to error "+msg, e); } else{ log.log(Level.WARNING, "Skipping value loader for GET "+loader+" due to error "+msg); } } } throw new IllegalArgumentException("No ContentValueLoader found for "+type.getName()); } public Callable> getConfig() { return config; } public void setConfig(Callable> config) { this.config = config; } public void setConfig(Map config) { this.config = new SimpleCallable>(config); } public void put(V value) throws Exception{ put(value,null); } public void put(V value, String contentType) throws Exception{ //TODO overflow to local disk if size is too big Map conf = config!=null?config.call():null; boolean foundhandler = false; ByteArrayOutputStream tempOut = new ByteArrayOutputStream(); for(ContentValueHandler loader: getValueLoaders()){ if(log.isLoggable(Level.FINE)){ log.fine("Trying value loader "+loader); } try{ if(loader.isSupported(type, contentType)){ if(loader.store(tempOut, contentType, value, conf)){ foundhandler=true; break; } tempOut.reset(); } } catch(Exception e){ StringBuilder msgBuilder = new StringBuilder(e.getClass().getName()).append("; "); if(e.getMessage()!=null){ msgBuilder.append(e.getMessage()); } for(StackTraceElement elem: e.getStackTrace()){ if(elem.getLineNumber()>=0 && elem.getClassName().equals(loader.getClass().getName())){ msgBuilder.append("; ").append(elem.toString()); } } String msg = msgBuilder.toString(); if(log.isLoggable(Level.FINE)){ log.log(Level.FINE, "Skipping value handler for PUT "+loader+" due to error "+msg, e); } else{ log.log(Level.WARNING, "Skipping value handler for PUT "+loader+" due to error "+msg); } } } if(!foundhandler){ throw new Exception("No handler found for "+value.getClass().getName()+" / "+contentType); } final byte[] bytes = tempOut.toByteArray(); ContentStream toStore = new ContentStream(this.source.call()); toStore.setContent(new Callable() { public InputStream call() throws Exception { return new ByteArrayInputStream(bytes); } }); toStore.setContentLength(bytes.length); toStore.setContentType(contentType); toStore.setLastModified(System.currentTimeMillis()); for(ContentStreamStore loader: getStreamLoaders()){ if(log.isLoggable(Level.FINE)){ log.fine("Trying stream loader "+loader); } if(loader.store(toStore,conf)){ return; } } throw new Exception("No store found for "+toStore.getUri()); } private static class SimpleCallable implements Callable{ final V val; public SimpleCallable(V val){ this.val=val; } public V call() throws Exception { return val; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy