/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package com.eas.client;
import com.eas.client.cache.PlatypusFiles;
import com.eas.client.threetier.PlatypusConnection;
import com.eas.client.threetier.requests.ModuleStructureRequest;
import com.eas.client.threetier.requests.ResourceRequest;
import com.eas.concurrent.CallableConsumer;
import com.eas.script.Scripts;
import com.eas.util.FileUtils;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import jdk.nashorn.api.scripting.JSObject;
import jdk.nashorn.internal.runtime.JSType;
/**
*
* @author mg
*/
public class RemoteModulesProxy implements ModulesProxy {
public static final String SERVER_DEPENDENCIES_PROP_NAME = "serverDependencies";
public static final String QUERY_DEPENDENCIES_PROP_NAME = "queryDependencies";
public static final String CLIENT_DEPENDENCIES_PROP_NAME = "clientDependencies";
public static final String STRUCTURE_PROP_NAME = "structure";
public static final String LENGTH_PROP_NAME = "length";
protected PlatypusConnection conn;
protected Path basePath;
protected Map<String, File> id2files = new ConcurrentHashMap<>();
public RemoteModulesProxy(PlatypusConnection aConn) {
super();
conn = aConn;
basePath = makePathInUserProfile(Math.abs(conn.getUrl().hashCode()) + "");
}
@Override
public Path getLocalPath() {
return basePath;
}
@Override
public ModuleStructure getModule(String aName, Scripts.Space aSpace, Consumer<ModuleStructure> onSuccess, Consumer<Exception> onFailure) throws Exception {
if (onSuccess != null) {
requestModuleStructure(aName, aSpace, (ModuleStructureRequest.Response structureResp) -> {
try {
ModuleStructure structure = new ModuleStructure();
JSObject jsStructure = (JSObject) aSpace.parseJson(structureResp.getJson());
readCommons(jsStructure, structure);
JSObject jsParts = (JSObject) jsStructure.getMember(STRUCTURE_PROP_NAME);
int partsLength = JSType.toInteger(jsParts.getMember(LENGTH_PROP_NAME));
for (int i = 0; i < partsLength; i++) {
String resourceName = JSType.toString(jsParts.getSlot(i));
getResource(resourceName, aSpace, (File aSynced) -> {
structure.getParts().addFile(aSynced);
if (structure.getParts().getFiles().size() == partsLength) {
id2files.put(aName, structure.getParts().findFileByExtension(PlatypusFiles.JAVASCRIPT_EXTENSION));
onSuccess.accept(structure);
}
}, onFailure);
}
} catch (Exception ex) {
if (onFailure != null) {
onFailure.accept(ex);
}
}
}, onFailure);
return null;
} else {
ModuleStructureRequest.Response structureResp = requestModuleStructure(aName, null, null, null);
ModuleStructure structure = new ModuleStructure();
JSObject jsStructure = (JSObject) aSpace.parseJson(structureResp.getJson());
readCommons(jsStructure, structure);
JSObject jsParts = (JSObject) jsStructure.getMember(STRUCTURE_PROP_NAME);
int partsLength = JSType.toInteger(jsParts.getMember(LENGTH_PROP_NAME));
for (int i = 0; i < partsLength; i++) {
String resourceName = JSType.toString(jsParts.getSlot(i));
File synced = getResource(resourceName, aSpace, null, null);
structure.getParts().addFile(synced);
}
id2files.put(aName, structure.getParts().findFileByExtension(PlatypusFiles.JAVASCRIPT_EXTENSION));
return structure;
}
}
@Override
public File getResource(String aResourceName, Scripts.Space aSpace, Consumer<File> onSuccess, Consumer<Exception> onFailure) throws Exception {
if (onSuccess != null) {
String cachePathName = constructResourcePath(aResourceName);
File cachePath = new File(cachePathName);
syncResource(cachePath, aResourceName, aSpace, (Void aVoid) -> {
onSuccess.accept(cachePath);
}, onFailure);
return null;
} else {
String cachePathName = constructResourcePath(aResourceName);
File cachePath = new File(cachePathName);
syncResource(cachePath, aResourceName, null, null, null);
return cachePath;
}
}
private void readCommons(JSObject jsStructure, ModuleStructure structure) {
JSObject jsClientDependencies = (JSObject) jsStructure.getMember(CLIENT_DEPENDENCIES_PROP_NAME);
int clientDepsLength = JSType.toInteger(jsClientDependencies.getMember(LENGTH_PROP_NAME));
for (int i = 0; i < clientDepsLength; i++) {
String dep = JSType.toString(jsClientDependencies.getSlot(i));
structure.getClientDependencies().add(dep);
}
JSObject jsQueryDependencies = (JSObject) jsStructure.getMember(QUERY_DEPENDENCIES_PROP_NAME);
int queryDepsLength = JSType.toInteger(jsQueryDependencies.getMember(LENGTH_PROP_NAME));
for (int i = 0; i < queryDepsLength; i++) {
String dep = JSType.toString(jsQueryDependencies.getSlot(i));
structure.getQueryDependencies().add(dep);
}
JSObject jsServerDependencies = (JSObject) jsStructure.getMember(SERVER_DEPENDENCIES_PROP_NAME);
int serverDepsLength = JSType.toInteger(jsServerDependencies.getMember(LENGTH_PROP_NAME));
for (int i = 0; i < serverDepsLength; i++) {
String dep = JSType.toString(jsServerDependencies.getSlot(i));
structure.getServerDependencies().add(dep);
}
}
private void syncResource(File cachePath, String aName, Scripts.Space aSpace, Consumer<Void> onSuccess, Consumer<Exception> onFailure) throws Exception {
Date localTimeStamp = null;
if (cachePath.exists() && cachePath.isFile()) {
localTimeStamp = new Date(cachePath.lastModified());
}
CallableConsumer<Void, ResourceRequest.Response> doWork = (ResourceRequest.Response resourceResp) -> {
if (resourceResp.getContent() != null) {
cachePath.getParentFile().mkdirs();
boolean deleted = cachePath.delete();
if (!deleted) {
if (cachePath.isDirectory()) {
FileUtils.clearDirectory(cachePath, false);
cachePath.delete();
}
}
cachePath.createNewFile();
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(cachePath))) {
out.write(resourceResp.getContent());
out.flush();
}
cachePath.setLastModified(resourceResp.getTimeStamp().getTime());
}
return null;
};
if (onSuccess != null) {
requestResource(localTimeStamp, aName, aSpace, (ResourceRequest.Response resourceResp) -> {
try {
doWork.call(resourceResp);
try {
onSuccess.accept(null);
} catch (Exception ex) {
Logger.getLogger(RemoteModulesProxy.class.getName()).log(Level.SEVERE, null, ex);
}
} catch (Exception ex) {
if (onFailure != null) {
onFailure.accept(ex);
}
}
}, onFailure);
} else {
ResourceRequest.Response resourceResp = requestResource(localTimeStamp, aName, null, null, null);
doWork.call(resourceResp);
}
}
private ModuleStructureRequest.Response requestModuleStructure(String aName, Scripts.Space aSpace, Consumer<ModuleStructureRequest.Response> onSuccess, Consumer<Exception> onFailure) throws Exception {
ModuleStructureRequest req = new ModuleStructureRequest(aName);
if (onSuccess != null) {
conn.enqueueRequest(req, aSpace, onSuccess, onFailure);
return null;
} else {
return conn.executeRequest(req);
}
}
private ResourceRequest.Response requestResource(Date aTimeStamp, String aResourceName, Scripts.Space aSpace, Consumer<ResourceRequest.Response> onSuccess, Consumer<Exception> onFailure) throws Exception {
ResourceRequest req = new ResourceRequest(aTimeStamp, aResourceName);
if (onSuccess != null) {
conn.enqueueRequest(req, aSpace, onSuccess, onFailure);
return null;
} else {
return conn.executeRequest(req);
}
}
private Path makePathInUserProfile(String aAppNameHash) {
//Make file cache directories
String path = System.getProperty(ClientConstants.USER_HOME_PROP_NAME);
if (!path.endsWith(File.separator)) {
path += File.separator;
}
path += ClientConstants.USER_HOME_PLATYPUS_DIRECTORY_NAME;
File newDir = new File(path);
if (!newDir.exists()) {
newDir.mkdir();
}
path += File.separator + ClientConstants.ENTITIES_CACHE_DIRECTORY_NAME;
newDir = new File(path);
if (!newDir.exists()) {
newDir.mkdir();
}
path += File.separator + aAppNameHash;
newDir = new File(path);
if (!newDir.exists()) {
newDir.mkdir();
}
return Paths.get(newDir.toURI());
}
/**
* Generates path for some platypus resource.
*
* @param aResourceName A resource name.
* @return Generated path name.
*/
protected String constructResourcePath(String aResourceName) {
String pathName = basePath + File.separator + aResourceName;
return pathName.replace('/', File.separatorChar);
}
@Override
public File nameToFile(String aName) throws Exception {
return id2files.get(aName);
}
@Override
public String getDefaultModuleName(File aFile) {
String defaultModuleName = basePath.relativize(Paths.get(aFile.toURI())).toString().replace(File.separator, "/");
defaultModuleName = defaultModuleName.substring(0, defaultModuleName.length() - PlatypusFiles.JAVASCRIPT_FILE_END.length());
return defaultModuleName;
}
}