Java tutorial
/***************************************************************************** * Shapeways, Inc Copyright (c) 2015 * Java Source * * This source is licensed under the GNU LGPL v2.1 * Please read http://www.gnu.org/copyleft/lgpl.html for more information * * This software comes with the standard NO WARRANTY disclaimer for any * purpose. Use it at your own risk. If there's a problem you get to fix it. * ****************************************************************************/ package abfab3d.shapejs; import abfab3d.core.Material; import abfab3d.io.input.URIMapper; import abfab3d.util.URIUtils; import abfab3d.param.Parameter; import abfab3d.param.ParameterType; import abfab3d.param.Parameterizable; import abfab3d.param.URIParameter; import abfab3d.core.Initializable; import com.google.common.cache.*; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.nio.file.Files; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import static abfab3d.core.Output.printf; import static abfab3d.core.Output.time; /** * Manages scripts and their resources. This class is thread safe. * * @author Alan Hudson */ public class ScriptManager { private static final boolean DEBUG = false; private static final boolean STOP_CACHING = false; private static final int JOB_RETAIN_MS = 60 * 60 * 1000; // File type that contains base64 data to be saved to disk as mime-type specified in the data private static final String BASE64_FILE_EXTENSION = ".base64"; private LoadingCache<String, ScriptResources> cache; private static ScriptManager instance; private static final String TMP_DIR = "/tmp"; private static final String IMAGES_DIR = "/stock/media/images"; private static final String MODELS_DIR = "/stock/media/models"; private static final Map<String, String> media; private static MaterialMapper matMapper; private static URIMapper uriMapper = null; static { Map<String, String> aMap = new HashMap<String, String>(); aMap.put("urn:shapeways:stockImage:shapeways_logo", IMAGES_DIR + "/shapeways_logo.png"); aMap.put("urn:shapeways:stockImage:allblack", IMAGES_DIR + "/allblack.jpg"); aMap.put("urn:shapeways:stockImage:allwhite", IMAGES_DIR + "/allwhite.jpg"); aMap.put("urn:shapeways:stockImage:gradient", IMAGES_DIR + "/gradient.jpg"); aMap.put("urn:shapeways:stockImage:envmap_rays", IMAGES_DIR + "/envmap_rays.png"); aMap.put("urn:shapeways:stockModel:smallbox", MODELS_DIR + "/box_10mm.x3db"); aMap.put("urn:shapeways:stockModel:box", MODELS_DIR + "/box_20mm.x3db"); aMap.put("urn:shapeways:stockModel:smallsphere", MODELS_DIR + "/sphere_10mm.x3db"); aMap.put("urn:shapeways:stockModel:sphere", MODELS_DIR + "/sphere_20mm.x3db"); media = Collections.unmodifiableMap(aMap); if (STOP_CACHING) { printf("**** Caching disabled on ScriptManager ***\n"); new Exception().printStackTrace(); } } private ScriptManager() { cache = CacheBuilder.newBuilder().softValues().expireAfterAccess(JOB_RETAIN_MS, TimeUnit.MILLISECONDS) .removalListener(new RemovalListener<String, ScriptResources>() { @Override public void onRemoval(RemovalNotification<String, ScriptResources> removal) { // ignore replacements if (removal.getCause() == RemovalCause.REPLACED) return; ScriptResources ce = removal.getValue(); if (ce != null) ce.clear(); } }).build(new CacheLoader<String, ScriptResources>() { public ScriptResources load(String key) throws ExecutionException { throw new ExecutionException(new IllegalArgumentException("Can't load key: " + key)); } }); } public static ScriptManager getInstance() { if (instance == null) { instance = new ScriptManager(); } return instance; } public static void setURIMapper(URIMapper mapper) { uriMapper = mapper; } public static void setMaterialMapper(MaterialMapper mm) { matMapper = mm; } public void clear() { cache.invalidateAll(); } public void cleanupJob(String jobID) { cache.invalidate(jobID); } /** * Update a script and its params * * @param jobID UniqueID for caching results * @param script The script or null if its not changed * @param params The params, delta encoded, null to remove * @return */ public ScriptResources update(String jobID, String script, Map<String, Object> params) { try { return update(jobID, false, script, params); } catch (NotCachedException nce) { // should never happen nce.printStackTrace(); } return null; } /** * Update a script and its params * * @param jobID UniqueID for caching results * @param script The script or null if its not changed * @param params The params, delta encoded, null to remove * @return */ public ScriptResources update(String jobID, Script script, Map<String, Object> params) { try { String code = script.getCode(); return update(jobID, false, code, params); } catch (NotCachedException nce) { // should never happen nce.printStackTrace(); } return null; } /** * Update a script * * @param jobID UniqueID for caching results * @param script The script or null if its not changed * @return */ public ScriptResources update(String jobID, String script) { try { return update(jobID, false, script, new HashMap<String, Object>()); } catch (NotCachedException nce) { // should never happen nce.printStackTrace(); } return null; } /** * Update a script * * @param jobID UniqueID for caching results * @param script The script or null if its not changed * @return */ public ScriptResources update(String jobID, Script script) { try { String code = script.getCode(); return update(jobID, false, code, new HashMap<String, Object>()); } catch (NotCachedException nce) { // should never happen nce.printStackTrace(); } return null; } /** * Update a script and its params * * @param jobID UniqueID for caching results * @param script The script or null if its not changed * @param params The params, delta encoded, null to remove * @return */ public ScriptResources update(String jobID, boolean delta, String script, Map<String, Object> params) throws NotCachedException { ScriptResources sr = null; long t0 = time(); if (params == null) { params = new HashMap<String, Object>(); } if (delta == true) { try { sr = cache.get(jobID); if (sr != null) { // update existing values boolean opsChanged = false; if (params.size() > 0) { updateParams(params, sr); opsChanged = true; } if (script != null) { // new script sr.script = script; opsChanged = true; } if (opsChanged) { // clear our op based caches /* sr.ops.clear(); sr.vscene.clear(); */ } if (!sr.result.isSuccess()) { printf("Script in a bad state, trying to reparse\n"); if (script != null) { sr.eval.parseScript(script); sr.script = script; } else { sr.eval.parseScript(sr.script); } sr.result = sr.eval.getResult(); } } } catch (ExecutionException ee) { // ignore if (delta) throw new NotCachedException(); } } // Create a new entry boolean first_create = false; if (sr == null) { sr = new ScriptResources(); sr.jobID = jobID; sr.eval = new ShapeJSEvaluator(); sr.eval.parseScript(script); sr.result = sr.eval.getResult(); sr.script = script; if (!sr.result.isSuccess()) { return sr; } first_create = true; } if (DEBUG) printf("SM.update parse: %d ms\n", time() - t0); t0 = time(); // convert JSON to objects try { sr.eval.mungeParams(params, first_create); if (DEBUG) printf("SM.update munge: %d ms\n", time() - t0); } catch (IllegalArgumentException iae) { sr.result = new EvaluatedScript(ShapeJSErrors.ErrorType.INVALID_PARAMETER_VALUE, iae.getMessage(), null, time() - t0); return sr; } t0 = time(); // download URLs downloadURI(sr.result.getParamMap(), params); sr.params.putAll(params); if (DEBUG) printf("SM.update download: %d ms\n", time() - t0); // lost the difference between eval and reval if (first_create) { t0 = time(); if (DEBUG) printf("SM Execute script. params: %s\n", params); sr.result = sr.eval.executeScript("main", params); if (DEBUG) printf("SM Done eval time: %d ms\n", time() - t0); } else { t0 = time(); if (DEBUG) printf("SM Reeval script. params: %s\n", params); sr.result = sr.eval.reevalScript(sr.script, params); if (DEBUG) printf("SM Done Reeval script. %d ms\n", time() - t0); } if (sr.result.isSuccess()) { Object material = params.get("material"); if (material == null) { material = sr.eval.getParameter("material"); } if (material != null) { Parameter mat = (Parameter) material; String matSt = (String) mat.getValue(); EvaluatedScript result = sr.result; if (result != null && result.getScene() != null) { if (matSt.equals("None")) { result.getScene().setMaterial(0, DefaultMaterial.getInstance()); result.getScene().setLightingRig(Scene.LightingRig.THREE_POINT_COLORED); } else if (matMapper != null) { Material rm = matMapper.getImplementation(matSt); if (rm == null) rm = sr.eval.getImplementation(matSt); if (rm != null) { result.getScene().setMaterial(0, rm); result.getScene().setLightingRig(Scene.LightingRig.THREE_POINT); } } } } } // Cache the job only if script eval is a success if (sr.result.isSuccess()) { // I think this is the correct place to call initialize. Might call it too often? List<Parameterizable> list = sr.result.getScene().getSource(); for (Parameterizable ds : list) { if (ds instanceof Initializable) { ((Initializable) ds).initialize(); } } if (!STOP_CACHING) { cache.put(jobID, sr); } } return sr; } /** * Download any uri parameters containing a fully qualified url. * * @param evalParams * @param namedParams */ private void downloadURI(Map<String, Parameter> evalParams, Map<String, Object> namedParams) { String workingDirName = null; String workingDirPath = null; String urlStr = null; for (Map.Entry<String, Object> entry : namedParams.entrySet()) { String key = entry.getKey(); Parameter param = evalParams.get(key); if (param == null) { printf("Cannot find definition for: %s, ignoring.\n", key); continue; } try { if (param.getType() == ParameterType.URI) { URIParameter up = (URIParameter) param; urlStr = up.getValue(); // Null value indicates removal of param from scene if (urlStr == null) continue; if (uriMapper != null) { urlStr = uriMapper.mapURI(urlStr); } String file = ShapeJSGlobal.getURL(urlStr); // If urlStr is in cache, make sure cached file exists if (file != null && (new File(file)).exists()) { up.setValue(file); continue; } String localPath = null; boolean cache = false; // TODO: We should really be parsing the URI into components instead of using starts and ends with // System.out.println("*** uri, " + key + " : " + urlStr); if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) { if (workingDirName == null) { workingDirPath = Files.createTempDirectory("downloaduri").toAbsolutePath().toString(); } long t0 = System.currentTimeMillis(); localPath = URIUtils.writeUrlToFile(key, urlStr, workingDirPath); printf("Downloaded file: %s. time: %d ms\n", urlStr, (System.currentTimeMillis() - t0)); if (localPath == null) { printf("Could not save url. key: %s url: %s dir: %s\n", key, urlStr, workingDirPath); throw new IllegalArgumentException("Could not resolve uri: %s to disk: " + urlStr); } // TODO: This handles a case with portal needing to write base64 file data to a // .base64 file. Will want to rethink this in the future. if (localPath.endsWith(BASE64_FILE_EXTENSION)) { String base64 = FileUtils.readFileToString(new File(localPath), "UTF-8"); if (base64 == null || base64.length() == 0) { printf("Failed to parse base64: %s from file: %s\n", base64, localPath); } localPath = URIUtils.writeDataURIToFile(key, base64, workingDirPath); } else { cache = true; } up.setValue(localPath); // System.out.println("*** uri, " + key + " : " + up.getValue()); } else if (urlStr.startsWith("data:")) { if (workingDirName == null) { workingDirPath = Files.createTempDirectory("downloaddata").toAbsolutePath().toString(); } localPath = URIUtils.writeDataURIToFile(key, urlStr, workingDirPath); up.setValue(localPath); } else if (urlStr.startsWith("urn:shapeways:")) { if (media.get(urlStr) == null) { throw new Exception("Invalid media resource: " + urlStr); } localPath = TMP_DIR + media.get(urlStr); File f = new File(localPath); if (!f.exists()) { exportMediaResources(); if (!f.exists()) { throw new Exception("Error exporting media resource: " + urlStr); } } up.setValue(localPath); cache = true; } // Do not cache data URI if (localPath != null && !urlStr.startsWith("data:") && !localPath.endsWith(BASE64_FILE_EXTENSION)) { up.setValue(localPath); } if (cache) { localPath = ShapeJSGlobal.putURL(urlStr, localPath); up.setValue(localPath); } } else if (param.getType() == ParameterType.URI_LIST) { // TODO: Handle uri list } } catch (Exception e) { printf("Error resolving uri: %s msg: %s\n", urlStr, e.getMessage()); e.printStackTrace(); } } } /** * Update parameters from an incoming delta * @param params * @param sr */ private void updateParams(Map<String, Object> params, ScriptResources sr) { for (Map.Entry<String, Object> entry : params.entrySet()) { String name = entry.getKey(); Object val = entry.getValue(); // null value indicates removal of param if (val != null) { sr.params.put(name, val); } else { sr.params.remove(name); } } } public ScriptResources getResources(String jobID) throws NotCachedException { try { return cache.get(jobID); } catch (ExecutionException ee) { throw new NotCachedException(); } } private void exportMediaResources() throws Exception { File imagesDir = new File(TMP_DIR + IMAGES_DIR); File modelsDir = new File(TMP_DIR + MODELS_DIR); if (!imagesDir.exists()) { imagesDir.mkdirs(); } if (!modelsDir.exists()) { modelsDir.mkdirs(); } for (Map.Entry<String, String> entry : media.entrySet()) { // System.out.printf("Key : %s and Value: %s %n", entry.getKey(), entry.getValue()); String file = entry.getValue(); String name = FilenameUtils.getName(file); if (file.contains("media/images")) { exportResource(entry.getValue(), new File(imagesDir.getAbsolutePath() + "/" + name)); } else if (file.contains("media/models")) { exportResource(entry.getValue(), new File(modelsDir.getAbsolutePath() + "/" + name)); } } } /** * Export a resource embedded into a Jar file to the local file path. * * @param resourceFile ie.: "/SmartLibrary.dll" * @return The path to the exported resource * @throws Exception */ public void exportResource(String resourceFile, File destFile) throws Exception { InputStream is = null; try { is = ScriptManager.class.getResourceAsStream(resourceFile); if (is == null) { File file = new File("classes/" + resourceFile); if (!file.exists()) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); is = classLoader.getResourceAsStream(resourceFile); } else { is = new FileInputStream(file); } } if (is == null) { throw new Exception("Cannot load resource: " + resourceFile); } FileUtils.copyInputStreamToFile(is, destFile); } finally { if (is != null) is.close(); } } }