From b6145512385234bb27025e3d9254447c9a417ec9 Mon Sep 17 00:00:00 2001 From: Will Lehman Date: Thu, 25 Jul 2024 15:28:18 -0400 Subject: [PATCH 1/6] all kc changes except centralized output store implemented --- src/main/java/usace/cc/plugin/Action.java | 29 ++-- src/main/java/usace/cc/plugin/CcStoreS3.java | 7 +- .../usace/cc/plugin/ConnectionDataStore.java | 6 + src/main/java/usace/cc/plugin/DataSource.java | 2 +- .../usace/cc/plugin/DataSourceIOType.java | 7 + src/main/java/usace/cc/plugin/DataStore.java | 6 +- .../cc/plugin/DataStoreTypeRegistry.java | 27 ++++ .../java/usace/cc/plugin/FileDataStoreS3.java | 19 ++- .../usace/cc/plugin/GetDataSourceInput.java | 16 ++ src/main/java/usace/cc/plugin/IOManager.java | 137 ++++++++++++++++++ src/main/java/usace/cc/plugin/Payload.java | 28 +--- .../usace/cc/plugin/PayloadAttributes.java | 104 +++++++++++++ .../java/usace/cc/plugin/PluginManager.java | 109 ++------------ 13 files changed, 350 insertions(+), 147 deletions(-) create mode 100644 src/main/java/usace/cc/plugin/ConnectionDataStore.java create mode 100644 src/main/java/usace/cc/plugin/DataSourceIOType.java create mode 100644 src/main/java/usace/cc/plugin/DataStoreTypeRegistry.java create mode 100644 src/main/java/usace/cc/plugin/GetDataSourceInput.java create mode 100644 src/main/java/usace/cc/plugin/IOManager.java create mode 100644 src/main/java/usace/cc/plugin/PayloadAttributes.java diff --git a/src/main/java/usace/cc/plugin/Action.java b/src/main/java/usace/cc/plugin/Action.java index a5754fe..e46805a 100644 --- a/src/main/java/usace/cc/plugin/Action.java +++ b/src/main/java/usace/cc/plugin/Action.java @@ -1,36 +1,27 @@ package usace.cc.plugin; -import java.util.Map; - import com.fasterxml.jackson.annotation.JsonProperty; public class Action { @JsonProperty - private String name; + private String type; @JsonProperty private String desc; @JsonProperty - private Map params; - public String getName(){ - return name; + private IOManager ioManager; + public String getType(){ + return type; + } + public IOManager getIOManager(){ + return ioManager; } - public String getDescription(){ return desc; } - - public Map getParameters(){ - return params; - } - public void UpdateActionPaths(){ + public void UpdateActionPaths()throws Exception{ PluginManager pm = PluginManager.getInstance(); - this.name = pm.SubstitutePath(this.name); + this.type = pm.SubstitutePath(this.type); this.desc = pm.SubstitutePath(this.desc); - if(params!=null){ - for(Map.Entry apb : params.entrySet()){ - params.replace(apb.getKey(),apb.getValue().UpdatePaths()); - } - } - + this.ioManager = ioManager.UpdateIOManagerPaths(); } } diff --git a/src/main/java/usace/cc/plugin/CcStoreS3.java b/src/main/java/usace/cc/plugin/CcStoreS3.java index 54087f7..4f124f6 100644 --- a/src/main/java/usace/cc/plugin/CcStoreS3.java +++ b/src/main/java/usace/cc/plugin/CcStoreS3.java @@ -156,8 +156,11 @@ private void writeInputStreamToDisk(InputStream input, String outputDestination) f.mkdirs(); } byte[] bytes = input.readAllBytes(); - OutputStream os = new FileOutputStream(new File(outputDestination)); - os.write(bytes); + try (OutputStream os = new FileOutputStream(new File(outputDestination))) { + os.write(bytes); + } catch (Exception e) { + e.printStackTrace(); + }; } @Override public byte[] GetObject(GetObjectInput input) throws AmazonS3Exception { diff --git a/src/main/java/usace/cc/plugin/ConnectionDataStore.java b/src/main/java/usace/cc/plugin/ConnectionDataStore.java new file mode 100644 index 0000000..6237d03 --- /dev/null +++ b/src/main/java/usace/cc/plugin/ConnectionDataStore.java @@ -0,0 +1,6 @@ +package usace.cc.plugin; + +public interface ConnectionDataStore { + public ConnectionDataStore Connect(DataStore ds); + public Object RawSession(); +} diff --git a/src/main/java/usace/cc/plugin/DataSource.java b/src/main/java/usace/cc/plugin/DataSource.java index 99359a4..97c4f0e 100644 --- a/src/main/java/usace/cc/plugin/DataSource.java +++ b/src/main/java/usace/cc/plugin/DataSource.java @@ -26,7 +26,7 @@ public String[] getDataPaths(){ public String getStoreName(){ return StoreName; } - public DataSource UpdatePaths(){ + public DataSource UpdatePaths()throws Exception{ DataSource dest = this; PluginManager pm = PluginManager.getInstance(); dest.Name = pm.SubstitutePath(this.getName()); diff --git a/src/main/java/usace/cc/plugin/DataSourceIOType.java b/src/main/java/usace/cc/plugin/DataSourceIOType.java new file mode 100644 index 0000000..faab2b0 --- /dev/null +++ b/src/main/java/usace/cc/plugin/DataSourceIOType.java @@ -0,0 +1,7 @@ +package usace.cc.plugin; + +public enum DataSourceIOType { + INPUT, + OUTPUT, + ANY, +} diff --git a/src/main/java/usace/cc/plugin/DataStore.java b/src/main/java/usace/cc/plugin/DataStore.java index 2c482e7..b1c9852 100644 --- a/src/main/java/usace/cc/plugin/DataStore.java +++ b/src/main/java/usace/cc/plugin/DataStore.java @@ -1,5 +1,4 @@ package usace.cc.plugin; -import java.util.Map; import com.fasterxml.jackson.annotation.JsonProperty; public class DataStore { @@ -8,12 +7,11 @@ public class DataStore { @JsonProperty private String ID; @JsonProperty - private Map Parameters; + private PayloadAttributes Parameters; @JsonProperty private StoreType StoreType; @JsonProperty private String DsProfile; - @JsonProperty private Object Session; public String getName(){ @@ -25,7 +23,7 @@ public String getId(){ public StoreType getStoreType(){ return StoreType; } - public Map getParameters(){ + public PayloadAttributes getParameters(){ return Parameters; } public String getDsProfile(){ diff --git a/src/main/java/usace/cc/plugin/DataStoreTypeRegistry.java b/src/main/java/usace/cc/plugin/DataStoreTypeRegistry.java new file mode 100644 index 0000000..9c91abb --- /dev/null +++ b/src/main/java/usace/cc/plugin/DataStoreTypeRegistry.java @@ -0,0 +1,27 @@ +package usace.cc.plugin; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +public final class DataStoreTypeRegistry { + private Map registry; + private static DataStoreTypeRegistry _instance = null; + public static DataStoreTypeRegistry getInstance(){ + if (_instance==null){ + _instance = new DataStoreTypeRegistry(); + } + return _instance; + } + private DataStoreTypeRegistry(){ + registry = new HashMap(); + registry.put(StoreType.S3, FileDataStoreS3.class); + } + public static void Register(StoreType storeType, Object storeInstance){ + DataStoreTypeRegistry.getInstance().registry.put(storeType, storeInstance.getClass()); + } + public Object New(StoreType s)throws Exception{ + Type type = DataStoreTypeRegistry.getInstance().registry.get(s); + return type.getClass().getDeclaredConstructor().newInstance(); + } +} diff --git a/src/main/java/usace/cc/plugin/FileDataStoreS3.java b/src/main/java/usace/cc/plugin/FileDataStoreS3.java index b9d0bf4..20826f4 100644 --- a/src/main/java/usace/cc/plugin/FileDataStoreS3.java +++ b/src/main/java/usace/cc/plugin/FileDataStoreS3.java @@ -26,7 +26,7 @@ import com.amazonaws.services.s3.model.S3Object; -public class FileDataStoreS3 implements FileDataStore { +public class FileDataStoreS3 implements FileDataStore, ConnectionDataStore { String bucket; String postFix; StoreType storeType; @@ -89,8 +89,15 @@ public Boolean Delete(String path) { return false; } } + public FileDataStoreS3(){ - public FileDataStoreS3(DataStore ds){ + } + @Override + public Object RawSession(){ + return awsS3; + } + @Override + public ConnectionDataStore Connect(DataStore ds) { AWSConfig acfg = new AWSConfig(); acfg.aws_access_key_id = System.getenv(ds.getDsProfile() + "_" + EnvironmentVariables.AWS_ACCESS_KEY_ID); acfg.aws_secret_access_key_id = System.getenv(ds.getDsProfile() + "_" + EnvironmentVariables.AWS_SECRET_ACCESS_KEY); @@ -141,7 +148,12 @@ public FileDataStoreS3(DataStore ds){ e.printStackTrace(); } storeType = StoreType.S3; - String tmpRoot = ds.getParameters().get(FileDataStoreS3.S3ROOT); + String tmpRoot=""; + try { + tmpRoot = ds.getParameters().getString(FileDataStoreS3.S3ROOT); + } catch (Exception e) { + e.printStackTrace(); + } if (tmpRoot == ""){ //error out? System.out.print("Missing S3 Root Paramter. Cannot create the store."); @@ -149,6 +161,7 @@ public FileDataStoreS3(DataStore ds){ this.bucket = config.aws_bucket; tmpRoot = tmpRoot.replaceFirst("^/+", ""); this.postFix = tmpRoot; + return this; } private byte[] GetObject(String path) throws RemoteException { byte[] data; diff --git a/src/main/java/usace/cc/plugin/GetDataSourceInput.java b/src/main/java/usace/cc/plugin/GetDataSourceInput.java new file mode 100644 index 0000000..cbb78af --- /dev/null +++ b/src/main/java/usace/cc/plugin/GetDataSourceInput.java @@ -0,0 +1,16 @@ +package usace.cc.plugin; + +public class GetDataSourceInput { + private DataSourceIOType dataSourceType; + private String dataSourceName; + public DataSourceIOType getDataSourceIOType(){ + return dataSourceType; + } + public String getDataSourceName(){ + return dataSourceName; + } + public GetDataSourceInput(String name, DataSourceIOType type){ + dataSourceType = type; + dataSourceName = name; + } +} diff --git a/src/main/java/usace/cc/plugin/IOManager.java b/src/main/java/usace/cc/plugin/IOManager.java new file mode 100644 index 0000000..c201d72 --- /dev/null +++ b/src/main/java/usace/cc/plugin/IOManager.java @@ -0,0 +1,137 @@ +package usace.cc.plugin; + +import java.util.Arrays; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class IOManager { + @JsonProperty + private PayloadAttributes attributes; + @JsonProperty + private DataStore[] stores; + @JsonProperty + private DataSource[] inputs; + @JsonProperty + private DataSource[] outputs; + public PayloadAttributes getAttributes(){ + return attributes; + } + public DataStore[] getStores(){ + return stores; + } + public DataSource getDataSource(GetDataSourceInput gdsi)throws Exception{ + DataSource[] sources; + switch (gdsi.getDataSourceIOType()) { + case INPUT: + sources = inputs; + break; + case OUTPUT: + sources = outputs; + break; + case ANY: + sources = Arrays.copyOf(inputs,inputs.length+ outputs.length); + System.arraycopy(outputs, 0, sources, inputs.length, outputs.length); + break; + default: + throw new Exception("input type not recognized"); + } + for(DataSource ds : sources){ + if(ds.getName() == gdsi.getDataSourceName()){ + return ds; + } + } + throw new Exception("input type not recognized"); + } + public IOManager UpdateIOManagerPaths()throws Exception{ + DataSource[] updatedInputs = new DataSource[inputs.length]; + for(int i=0;iT GetStoreSession(String name) throws Exception { + for(DataStore ds : stores){ + if (ds.getName()==name){ + T session = (T)ds.getSession(); + if (session==null){ + throw new Exception("unable to cast to type t"); + }else{ + return session; + } + } + } + throw new Exception("unable to find session by name of " + name); + } +} diff --git a/src/main/java/usace/cc/plugin/Payload.java b/src/main/java/usace/cc/plugin/Payload.java index 3febdb9..b3e7c64 100644 --- a/src/main/java/usace/cc/plugin/Payload.java +++ b/src/main/java/usace/cc/plugin/Payload.java @@ -1,35 +1,19 @@ package usace.cc.plugin; import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Map; public class Payload { @JsonProperty - private Map attributes; - @JsonProperty - private DataStore[] stores; - @JsonProperty - private DataSource[] inputs; - @JsonProperty - private DataSource[] outputs; + private IOManager ioManager; @JsonProperty private Action[] actions; - public Map getAttributes(){ - return attributes; - } - public DataStore[] getStores(){ - return stores; - } - public void setStore(int index, DataStore store){ - stores[index] = store; - } - public DataSource[] getInputs(){ - return inputs; - } - public DataSource[] getOutputs(){ - return outputs; + public IOManager getIOManager(){ + return ioManager; } public Action[] getActions(){ return actions; } + public void setIOManager(IOManager inIOManager){ + ioManager = inIOManager; + } } diff --git a/src/main/java/usace/cc/plugin/PayloadAttributes.java b/src/main/java/usace/cc/plugin/PayloadAttributes.java new file mode 100644 index 0000000..da4a9d6 --- /dev/null +++ b/src/main/java/usace/cc/plugin/PayloadAttributes.java @@ -0,0 +1,104 @@ +package usace.cc.plugin; + +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class PayloadAttributes { + @JsonProperty + private Map attributes; + + public Map getParameters(){ + return attributes; + } + public int getInt(String name) throws Exception{ + Object val = attributes.get(name); + if (val==null){ + throw new Exception(name + " not found"); + }else{ + if(val instanceof Number){ + return ((Number)val).intValue(); + }else{ + return Integer.parseInt(val.toString()); + } + } + } + public int getIntOrDefault(String name, int defaultValue){ + try{ + int value = getInt(name); + return value; + }catch(Exception ex){ + System.out.println(ex.getMessage()); + return defaultValue; + } + } + public long getInt64(String name) throws Exception{ + Object val = attributes.get(name); + if (val==null){ + throw new Exception(name + " not found"); + }else{ + if(val instanceof Number){ + return ((Number)val).longValue(); + }else{ + return Long.parseLong(val.toString()); + } + } + } + public long getInt64OrDefault(String name, long defaultValue){ + try{ + long value = getInt64(name); + return value; + }catch(Exception ex){ + System.out.println(ex.getMessage()); + return defaultValue; + } + } + public double getDouble(String name) throws Exception{ + Object val = attributes.get(name); + if (val==null){ + throw new Exception(name + " not found"); + }else{ + if(val instanceof Number){ + return ((Number)val).doubleValue(); + }else{ + return Double.parseDouble(val.toString()); + } + } + } + public double getDoubleOrDefault(String name, double defaultValue){ + try{ + double value = getDouble(name); + return value; + }catch(Exception ex){ + System.out.println(ex.getMessage()); + return defaultValue; + } + } + public String getString(String name) throws Exception{ + Object val = attributes.get(name); + if (val==null){ + throw new Exception(name + " not found"); + }else{ + return String.valueOf(val); + } + } + public String getStringOrDefault(String name, String defaultValue){ + try{ + String value = getString(name); + return value; + }catch(Exception ex){ + System.out.println(ex.getMessage()); + return defaultValue; + } + } + public void UpdatePayloadAttributes()throws Exception{ + PluginManager pm = PluginManager.getInstance(); + for(Map.Entry me : attributes.entrySet()){ + Object val = me.getValue(); + if(val instanceof String){ + String strval = String.valueOf(val); + strval = pm.SubstitutePath(strval); + attributes.replace(me.getKey(),strval); + }//TODO: what if it is an array of string? + } + } +} \ No newline at end of file diff --git a/src/main/java/usace/cc/plugin/PluginManager.java b/src/main/java/usace/cc/plugin/PluginManager.java index 3a5a95a..31c757e 100644 --- a/src/main/java/usace/cc/plugin/PluginManager.java +++ b/src/main/java/usace/cc/plugin/PluginManager.java @@ -3,7 +3,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -29,22 +28,9 @@ private PluginManager(){ cs = new CcStoreS3(); try { _payload = cs.GetPayload(); - int i = 0; - for (DataStore store : _payload.getStores()) { - switch (store.getStoreType()){ - case S3: - store.setSession(new FileDataStoreS3(store)); - _payload.setStore(i, store); - break; - case WS://does java work with fallthrough? - case RDBMS: - System.out.println("WS and RDBMS session instantiation is the responsibility of the plugin."); - break; - default: - System.out.println("Invalid Store type");//what type was provided? - break; - } - i ++; + _payload.getIOManager().ConnectStores(); + for (Action action : _payload.getActions()) { + action.getIOManager().ConnectStores(); } //substitutePathVariables(); } catch (Exception e) { @@ -52,25 +38,15 @@ private PluginManager(){ } } - private void substitutePathVariables() { - if (_payload.getInputs()!=null){ - for (int i= 0; i<_payload.getInputs().length; i++){ - _payload.getInputs()[i] = _payload.getInputs()[i].UpdatePaths(); - } - } - if (_payload.getOutputs()!=null){ - for (int i= 0; i<_payload.getOutputs().length; i++){ - _payload.getOutputs()[i] = _payload.getOutputs()[i].UpdatePaths(); - } - } + private void substitutePathVariables() throws Exception{ + _payload.setIOManager(_payload.getIOManager().UpdateIOManagerPaths()); if(_payload.getActions()!=null){ for (int i= 0; i<_payload.getActions().length; i++){ _payload.getActions()[i].UpdateActionPaths(); } } - } - public String SubstitutePath(String path) { + public String SubstitutePath(String path) throws Exception { Matcher m = p.matcher(path); while(m.find()){ String result = m.group(); @@ -83,7 +59,7 @@ public String SubstitutePath(String path) { m = p.matcher(path); break; case "ATTR": - String valattr = _payload.getAttributes().get(parts[1]).toString(); + String valattr = _payload.getIOManager().getAttributes().getString(parts[1]); path = path.replaceFirst("\\{"+result+"\\}", valattr);//? m = p.matcher(path); break; @@ -95,57 +71,16 @@ public String SubstitutePath(String path) { } public Payload getPayload(){ if (!_hasUpdatedPaths){ - substitutePathVariables(); + try{ + substitutePathVariables(); + }catch(Exception e){ + e.printStackTrace(); + System.exit(1); + } _hasUpdatedPaths = true; } return _payload; } - public FileDataStore getFileStore(String storeName){ - return (FileDataStore) findDataStore(storeName).getSession();//check for nil? - } - public DataStore getStore(String storeName){ - return findDataStore(storeName); - } - public DataSource getInputDataSource(String name){ - return findDataSource(name, getInputDataSources()); - } - public DataSource getOutputDataSource(String name){ - return findDataSource(name, getOutputDataSources()); - } - public DataSource[] getInputDataSources(){ - return _payload.getInputs(); - } - public DataSource[] getOutputDataSources(){ - return _payload.getOutputs(); - } - public byte[] getFile(DataSource ds, int path){ - FileDataStore store = getFileStore(ds.getStoreName()); - InputStream reader = store.Get(ds.getPaths()[path]); - byte[] data; - try { - data = reader.readAllBytes(); - return data; - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - public boolean putFile(byte[] data, DataSource ds, int path){ - FileDataStore store = getFileStore(ds.getStoreName()); - return store.Put(new ByteArrayInputStream(data), ds.getPaths()[path]); - } - public boolean fileWriter(InputStream inputstream, DataSource destDs, int destPath){ - FileDataStore store = getFileStore(destDs.getStoreName()); - return store.Put(inputstream, destDs.getPaths()[destPath]); - } - public InputStream fileReader(DataSource ds, int path){ - FileDataStore store = getFileStore(ds.getStoreName()); - return store.Get(ds.getPaths()[path]); - } - public InputStream fileReaderByName(String dataSourceName, int path){ - DataSource ds = findDataSource(dataSourceName, getInputDataSources()); - return fileReader(ds, path); - } public void setLogLevel(ErrorLevel level){ _logger.setErrorLevel(level); } @@ -159,26 +94,8 @@ public void ReportProgress(Status report){ _logger.ReportStatus(report); } public int EventNumber(){ - //Object result = _payload.getAttributes().get(EnvironmentVariables.CC_EVENT_NUMBER); String val = System.getenv(EnvironmentVariables.CC_EVENT_NUMBER); int eventNumber = Integer.parseInt(val); return eventNumber; } - private DataSource findDataSource(String name, DataSource[] dataSources){ - for (DataSource dataSource : dataSources) { - if (dataSource.getName().equalsIgnoreCase(name)){ - return dataSource; - } - } - return null; - } - private DataStore findDataStore(String name){ - for (DataStore dataStore : _payload.getStores()) { - if (dataStore.getName().equalsIgnoreCase(name)){ - return dataStore; - } - } - System.out.println(name + " store not found."); - return null; - } } \ No newline at end of file From 47261d3f12dc55fd2b9a05424197d25215832005 Mon Sep 17 00:00:00 2001 From: Randal Date: Tue, 15 Apr 2025 16:17:52 -0400 Subject: [PATCH 2/6] begin sync of API with golang API --- Dockerfile | 19 +++-- build.gradle | 4 +- src/main/java/usace/cc/plugin/Action.java | 4 +- src/main/java/usace/cc/plugin/DataSource.java | 10 +-- .../usace/cc/plugin/EnvironmentVariables.java | 6 +- .../java/usace/cc/plugin/FileDataStoreS3.java | 73 +++++++--------- .../usace/cc/plugin/PayloadAttributes.java | 2 +- .../java/usace/cc/plugin/PluginManager.java | 85 +++++++++++-------- 8 files changed, 109 insertions(+), 94 deletions(-) diff --git a/Dockerfile b/Dockerfile index b85590b..cc0814d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,13 @@ -FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu as dev -ENV TZ=America/New_York -RUN apt update -RUN apt -y install wget -RUN apt -y install git -#FROM ubuntu:20.04 as prod +FROM --platform=linux/amd64 ubuntu:24.04 + +ENV TZ=America/New_York + +RUN echo 'Acquire::http::Pipeline-Depth 0;\nAcquire::http::No-Cache true; \nAcquire::BrokenProxy true;\n' > /etc/apt/apt.conf.d/99fixbadproxy + +RUN apt update &&\ + apt -y install wget &&\ + apt -y install git &&\ + apt -y install curl &&\ + apt -y install vim &&\ + apt -y install unzip &&\ + apt -y install openjdk-17-jdk diff --git a/build.gradle b/build.gradle index 1a91eab..b9c404d 100644 --- a/build.gradle +++ b/build.gradle @@ -18,8 +18,8 @@ dependencies { implementation 'com.amazonaws:aws-java-sdk-s3' } jar { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } publishing { repositories { diff --git a/src/main/java/usace/cc/plugin/Action.java b/src/main/java/usace/cc/plugin/Action.java index e46805a..356add9 100644 --- a/src/main/java/usace/cc/plugin/Action.java +++ b/src/main/java/usace/cc/plugin/Action.java @@ -20,8 +20,8 @@ public String getDescription(){ } public void UpdateActionPaths()throws Exception{ PluginManager pm = PluginManager.getInstance(); - this.type = pm.SubstitutePath(this.type); - this.desc = pm.SubstitutePath(this.desc); + this.type = pm.substitutePath(this.type); + this.desc = pm.substitutePath(this.desc); this.ioManager = ioManager.UpdateIOManagerPaths(); } } diff --git a/src/main/java/usace/cc/plugin/DataSource.java b/src/main/java/usace/cc/plugin/DataSource.java index 97c4f0e..443904c 100644 --- a/src/main/java/usace/cc/plugin/DataSource.java +++ b/src/main/java/usace/cc/plugin/DataSource.java @@ -29,18 +29,18 @@ public String getStoreName(){ public DataSource UpdatePaths()throws Exception{ DataSource dest = this; PluginManager pm = PluginManager.getInstance(); - dest.Name = pm.SubstitutePath(this.getName()); + dest.Name = pm.substitutePath(this.getName()); if(this.getPaths()!=null){ for(int j=0; j Date: Mon, 28 Apr 2025 15:18:42 -0400 Subject: [PATCH 3/6] IOManager and Payload updates --- .../compileJava/previous-compilation-data.bin | Bin 45716 -> 0 bytes build/tmp/jar/MANIFEST.MF | 2 - src/main/java/usace/cc/plugin/AWSConfig.java | 11 + src/main/java/usace/cc/plugin/Action.java | 25 +- src/main/java/usace/cc/plugin/Config.java | 4 + src/main/java/usace/cc/plugin/DataSource.java | 82 ++-- .../java/usace/cc/plugin/FileDataStore.java | 2 +- .../java/usace/cc/plugin/FileDataStoreS3.java | 11 +- .../usace/cc/plugin/GetDataSourceInput.java | 4 + src/main/java/usace/cc/plugin/IOManager.java | 389 ++++++++++++++---- src/main/java/usace/cc/plugin/Message.java | 4 + src/main/java/usace/cc/plugin/Payload.java | 23 ++ .../usace/cc/plugin/PayloadAttributes.java | 40 +- .../java/usace/cc/plugin/PluginManager.java | 213 ++++++++-- 14 files changed, 650 insertions(+), 160 deletions(-) delete mode 100644 build/tmp/compileJava/previous-compilation-data.bin delete mode 100644 build/tmp/jar/MANIFEST.MF diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin deleted file mode 100644 index 568a1501ae2eda5e0d59fee5cc263ec81d5dcfea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45716 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 V^*0t}VoClnBh`1+<2d2)??1ADU1b0O diff --git a/build/tmp/jar/MANIFEST.MF b/build/tmp/jar/MANIFEST.MF deleted file mode 100644 index 58630c0..0000000 --- a/build/tmp/jar/MANIFEST.MF +++ /dev/null @@ -1,2 +0,0 @@ -Manifest-Version: 1.0 - diff --git a/src/main/java/usace/cc/plugin/AWSConfig.java b/src/main/java/usace/cc/plugin/AWSConfig.java index f2ecd48..df6726d 100644 --- a/src/main/java/usace/cc/plugin/AWSConfig.java +++ b/src/main/java/usace/cc/plugin/AWSConfig.java @@ -1,22 +1,33 @@ package usace.cc.plugin; + import com.fasterxml.jackson.annotation.JsonProperty; + public class AWSConfig { @JsonProperty public String aws_config_name; + @JsonProperty public String aws_access_key_id; + @JsonProperty public String aws_secret_access_key_id; + @JsonProperty public String aws_region; + @JsonProperty public String aws_bucket; + @JsonProperty public Boolean aws_mock; + @JsonProperty public String aws_endpoint; + @JsonProperty public Boolean aws_disable_ssl; + @JsonProperty public Boolean aws_force_path_style; + } diff --git a/src/main/java/usace/cc/plugin/Action.java b/src/main/java/usace/cc/plugin/Action.java index 356add9..567fc5e 100644 --- a/src/main/java/usace/cc/plugin/Action.java +++ b/src/main/java/usace/cc/plugin/Action.java @@ -3,12 +3,16 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class Action { + @JsonProperty private String type; + @JsonProperty private String desc; + @JsonProperty private IOManager ioManager; + public String getType(){ return type; } @@ -18,10 +22,21 @@ public IOManager getIOManager(){ public String getDescription(){ return desc; } - public void UpdateActionPaths()throws Exception{ - PluginManager pm = PluginManager.getInstance(); - this.type = pm.substitutePath(this.type); - this.desc = pm.substitutePath(this.desc); - this.ioManager = ioManager.UpdateIOManagerPaths(); + + public DataStore[] getStores(){ + return this.ioManager.getStores(); } + + public DataSource[] getInputs() { + return this.ioManager.getInputs(); + } + + public DataSource[] getOutputs(){ + return this.ioManager.getOutputs(); + } + + public PayloadAttributes getAttributes(){ + return this.ioManager.getAttributes(); + } + } diff --git a/src/main/java/usace/cc/plugin/Config.java b/src/main/java/usace/cc/plugin/Config.java index 892d996..749ac27 100644 --- a/src/main/java/usace/cc/plugin/Config.java +++ b/src/main/java/usace/cc/plugin/Config.java @@ -1,6 +1,10 @@ package usace.cc.plugin; + import com.fasterxml.jackson.annotation.JsonProperty; + public class Config { + @JsonProperty public AWSConfig[] aws_configs; + } \ No newline at end of file diff --git a/src/main/java/usace/cc/plugin/DataSource.java b/src/main/java/usace/cc/plugin/DataSource.java index 443904c..57b3fd1 100644 --- a/src/main/java/usace/cc/plugin/DataSource.java +++ b/src/main/java/usace/cc/plugin/DataSource.java @@ -1,49 +1,73 @@ package usace.cc.plugin; import com.fasterxml.jackson.annotation.JsonProperty; public class DataSource { + @JsonProperty - private String Name; + private String name; + @JsonProperty - private String ID; + private String id; + @JsonProperty - private String StoreName; + private String storeName; + @JsonProperty - private String[] Paths; + private String[] paths; + @JsonProperty - private String[] DataPaths; + private String[] dataPaths; + public String getId(){ - return ID; + return id; } + public String getName(){ - return Name; + return name; + } + + public void setName(String name){ + this.name=name; } + public String[] getPaths(){ - return Paths; + return this.paths; + } + + public void setPaths(String[] paths){ + this.paths=paths; } + public String[] getDataPaths(){ - return DataPaths; + return dataPaths; } - public String getStoreName(){ - return StoreName; - } - public DataSource UpdatePaths()throws Exception{ - DataSource dest = this; - PluginManager pm = PluginManager.getInstance(); - dest.Name = pm.substitutePath(this.getName()); - if(this.getPaths()!=null){ - for(int j=0; j optParam = ds.getParameters().get(FileDataStoreS3.S3ROOT); + if (optParam.isPresent()){ + tmpRoot = optParam.get(); + } } catch (Exception e) { e.printStackTrace(); + //@TODO UGH....NOT HANDLING THE ERRORS!!!!!!!!!!!!!!!! } if (tmpRoot == ""){ //error out? @@ -202,4 +208,5 @@ private boolean UploadToS3(String bucketName, String objectKey, byte[] fileBytes return false; } } + } diff --git a/src/main/java/usace/cc/plugin/GetDataSourceInput.java b/src/main/java/usace/cc/plugin/GetDataSourceInput.java index cbb78af..39c18a9 100644 --- a/src/main/java/usace/cc/plugin/GetDataSourceInput.java +++ b/src/main/java/usace/cc/plugin/GetDataSourceInput.java @@ -1,14 +1,18 @@ package usace.cc.plugin; public class GetDataSourceInput { + private DataSourceIOType dataSourceType; private String dataSourceName; + public DataSourceIOType getDataSourceIOType(){ return dataSourceType; } + public String getDataSourceName(){ return dataSourceName; } + public GetDataSourceInput(String name, DataSourceIOType type){ dataSourceType = type; dataSourceName = name; diff --git a/src/main/java/usace/cc/plugin/IOManager.java b/src/main/java/usace/cc/plugin/IOManager.java index c201d72..9e18a62 100644 --- a/src/main/java/usace/cc/plugin/IOManager.java +++ b/src/main/java/usace/cc/plugin/IOManager.java @@ -1,137 +1,362 @@ package usace.cc.plugin; import java.util.Arrays; +import java.util.Optional; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import com.fasterxml.jackson.annotation.JsonProperty; public class IOManager { + + //IO Manager Error Types + static class InvalidDataSourceException extends Exception { + public InvalidDataSourceException(String message) { + super(message); + } + + public InvalidDataSourceException(Exception ex){ + super(ex); + } + } + + static class InvalidDataStoreException extends Exception { + public InvalidDataStoreException(String message) { + super(message); + } + + public InvalidDataStoreException(Exception ex){ + super(ex); + } + } + @JsonProperty private PayloadAttributes attributes; + @JsonProperty private DataStore[] stores; + @JsonProperty private DataSource[] inputs; + @JsonProperty private DataSource[] outputs; + + private IOManager parent; + public PayloadAttributes getAttributes(){ return attributes; } + public DataStore[] getStores(){ - return stores; + return this.stores; } - public DataSource getDataSource(GetDataSourceInput gdsi)throws Exception{ + + public DataSource[] getInputs(){ + return this.inputs; + } + + public DataSource[] getOutputs(){ + return this.outputs; + } + + public void setParent(IOManager parent){ + this.parent=parent; + } + + + /** + * Retrieves a {@link DataStore} by its name. + *

+ * This method searches the current list of data stores for one with a matching name. + * If not found and a parent is defined, it will recursively search the parent. + *

+ * + * @param name the name of the data store to retrieve + * @return an {@code Optional} containing the matching {@code DataStore}, or + * {@code Optional.empty()} if no matching store is found in this instance + * or its parent chain + */ + public Optional getStore(String name){ + for (DataStore store:this.stores){ + if (name.equals(store.getName())){ + return Optional.of(store); + } + } + if (this.parent!=null){ + return parent.getStore(name); + } + return Optional.empty(); + } + + + /** + * Retrieves a {@link DataSource} based on the provided input criteria. + *

+ * This method selects from input sources, output sources, or both depending on the + * {@link GetDataSourceInput#getDataSourceIOType()} value. It then searches for a + * {@code DataSource} with a matching name. If no match is found locally and a parent + * exists, the search will continue recursively in the parent. + *

+ * + * @param gdsi an object encapsulating the data source name and the I/O type to search + * @return an {@code Optional} containing the matching {@code DataSource}, or {@code Optional.empty()} + * if no such data source exists in this instance or its parent chain + * @throws InvalidDataSourceException if the {@code DataSourceIOType} in the input is not recognized + */ + public Optional getDataSource(GetDataSourceInput gdsi) throws InvalidDataSourceException{ DataSource[] sources; switch (gdsi.getDataSourceIOType()) { case INPUT: - sources = inputs; + sources = this.inputs; break; - case OUTPUT: - sources = outputs; + case OUTPUT: + sources = this.outputs; break; - case ANY: - sources = Arrays.copyOf(inputs,inputs.length+ outputs.length); - System.arraycopy(outputs, 0, sources, inputs.length, outputs.length); + case ANY: + sources = Arrays.copyOf(this.inputs,this.inputs.length + this.outputs.length); + System.arraycopy(this.outputs, 0, sources, this.inputs.length, this.outputs.length); break; default: - throw new Exception("input type not recognized"); + throw new InvalidDataSourceException("datan source input type not recognized"); } + for(DataSource ds : sources){ if(ds.getName() == gdsi.getDataSourceName()){ - return ds; + return Optional.of(ds); } } - throw new Exception("input type not recognized"); - } - public IOManager UpdateIOManagerPaths()throws Exception{ - DataSource[] updatedInputs = new DataSource[inputs.length]; - for(int i=0;i + * This is a convenience method that constructs a {@link GetDataSourceInput} with the specified + * name and an I/O type of {@code INPUT}, then delegates to {@link #getDataSource(GetDataSourceInput)}. + *

+ * + * @param name the name of the input data source to retrieve + * @return an {@code Optional} containing the matching input {@code DataSource}, or + * {@code Optional.empty()} if no matching source is found locally or in a parent + * @throws InvalidDataSourceException if the underlying data source lookup encounters an error + */ + public Optional getInputDataSource(String name) throws InvalidDataSourceException { + var gdsi = new GetDataSourceInput(name, DataSourceIOType.INPUT); + return getDataSource(gdsi); + } + + /** + * Retrieves an output {@link DataSource} by name. + *

+ * This is a convenience method that constructs a {@link GetDataSourceInput} with the specified + * name and an I/O type of {@code OUTPUT}, then delegates to {@link #getDataSource(GetDataSourceInput)}. + *

+ * + * @param name the name of the output data source to retrieve + * @return an {@code Optional} containing the matching output {@code DataSource}, or + * {@code Optional.empty()} if no matching source is found locally or in a parent + * @throws InvalidDataSourceException if the underlying data source lookup encounters an error + */ + public Optional getOutputDataSource(String name) throws InvalidDataSourceException { + var gdsi = new GetDataSourceInput(name, DataSourceIOType.OUTPUT); + return getDataSource(gdsi); + } + + //@TODO....I include a data path here + /** + * Copies a file from an input {@link DataSource} to a local file path. + *

+ * This method locates the input data source by name, retrieves the input stream + * for the specified path key, reads the file's contents, and writes them to the + * specified local file path. + *

+ * + * @param dataSourceName the name of the input data source + * @param pathkey the key or path identifying the file within the data source + * @param localPath the path on the local filesystem where the file will be written + * @throws InvalidDataSourceException if the data source is not found or if an error occurs while accessing it + * @throws IOException if an I/O error occurs during reading from the source or writing to the local file + */ + public void copyFileToLocal(String dataSourceName, String pathkey, String localPath) throws IOException, InvalidDataSourceException{ + Optional indsOpt = getDataSource(new GetDataSourceInput(dataSourceName, DataSourceIOType.INPUT)); + if (!indsOpt.isPresent()){ + throw new InvalidDataSourceException("Data source not found"); } - IOManager updatedIOManager = new IOManager(); - updatedIOManager.inputs = updatedInputs; - updatedIOManager.outputs = updatedOutputs; - this.attributes.UpdatePayloadAttributes(); - updatedIOManager.attributes = this.attributes; - updatedIOManager.stores = this.stores; - return updatedIOManager; - } - public void CopyFileToLocal(String dataSourceName, String pathkey, String localPath) throws Exception{ - DataSource inds = getDataSource(new GetDataSourceInput(dataSourceName, DataSourceIOType.INPUT)); - InputStream is = GetReader(inds, pathkey); - try (is) { + + DataSource inds = indsOpt.get(); + + try (InputStream is = getInputStream(inds, pathkey)) { byte[] bytes = is.readAllBytes(); File outfile = new File(localPath); - OutputStream writer = new FileOutputStream(outfile); - try(writer){ + try(OutputStream writer = new FileOutputStream(outfile)){ writer.write(bytes); - }catch (Exception e){ - e.printStackTrace(); - } - } catch (Exception e) { - // TODO: handle exception - e.printStackTrace(); + } } } - public void Write(InputStream writer, String datasourcename, String pathName, String datapathName) throws Exception{ - DataSource ds = this.getDataSource(new GetDataSourceInput(datasourcename, DataSourceIOType.OUTPUT)); - FileDataStore fds = GetStoreSession(ds.getStoreName()); - fds.Put(writer, pathName); - } - public byte[] Get(String dataSourceName, String pathName, String DataPathName)throws Exception{ - DataSource ds = this.getDataSource(new GetDataSourceInput(dataSourceName, DataSourceIOType.INPUT)); - return GetReader(ds, pathName).readAllBytes(); - } - public void Put(byte[] data, String dataSourceName, String pathName, String DataPathName) throws Exception{ - DataSource ds = this.getDataSource(new GetDataSourceInput(dataSourceName, DataSourceIOType.OUTPUT)); - FileDataStore fds = GetStoreSession(ds.getStoreName()); - ByteArrayInputStream bais = new ByteArrayInputStream(data); - fds.Put(bais, pathName); - } - public InputStream GetReader(DataSource dataSource, String pathKey) throws Exception{ - FileDataStore fds = GetStoreSession(dataSource.getStoreName()); - InputStream s = fds.Get(pathKey); - return s; - } - public void CopyFileToRemote(String destinationName, String pathKey, String localPath) throws Exception{ - DataSource ds = this.getDataSource(new GetDataSourceInput(destinationName, DataSourceIOType.OUTPUT)); - FileDataStore fdstore = GetStoreSession(ds.getStoreName()); - File localFile = new File(localPath); - - InputStream reader = new FileInputStream(localFile); - fdstore.Put(reader, pathKey); - } - public void ConnectStores() throws Exception{ - DataStoreTypeRegistry registry = DataStoreTypeRegistry.getInstance(); - //int i = 0; - for (int i = 0; i + * This method locates the data source by name and attempts to read the contents of the file + * identified by the given {@code pathName}. If the data source is not found, or an error occurs + * while accessing it, an exception is thrown. The {@code dataPathName} is included for error + * context but not used directly in the lookup. + *

+ * + * @param dataSourceName the name of the input data source + * @param pathName the key or identifier of the file within the data source + * @param dataPathName an additional path descriptor for context in error reporting + * @return a byte array containing the contents of the specified file + * @throws InvalidDataSourceException if the data source is not found or cannot be accessed + * @throws IOException if an I/O error occurs while reading from the data source + */ + public byte[] get(String dataSourceName, String pathName, String dataPathName) throws IOException, InvalidDataSourceException{ + Optional dsOpt = this.getDataSource(new GetDataSourceInput(dataSourceName, DataSourceIOType.INPUT)); + if (dsOpt.isPresent()){ + var ds = dsOpt.get(); + return getInputStream(ds, pathName).readAllBytes(); + } + throw new InvalidDataSourceException(String.format("invalid data source. Name: %s, Path: %s, DataPath: %s",dataSourceName,pathName,dataPathName)); + } + + /** + * Writes the provided byte array to a specified path in a named output data source. + * + *

This method looks up the data source by name and attempts to retrieve a corresponding + * {@code FileDataStore} session. If both are found, it writes the data to the given path + * within that data store. If either the data source or the data store is not found, + * an appropriate checked exception is thrown.

+ * + * @param data the data to write + * @param dataSourceName the name of the output data source to target + * @param pathName the path within the data store to write the data to + * @param dataPathName currently unused, reserved for future expansion or routing + * + * @throws IOException if an I/O error occurs during the write + * @throws InvalidDataSourceException if the specified data source cannot be found + * @throws InvalidDataStoreException if the data store session for the data source cannot be obtained + */ + public void put(byte[] data, String dataSourceName, String pathName, String dataPathName) throws IOException, InvalidDataSourceException, InvalidDataStoreException{ + Optional dsOpt = this.getDataSource(new GetDataSourceInput(dataSourceName, DataSourceIOType.OUTPUT)); + if (dsOpt.isPresent()){ + var ds = dsOpt.get(); + Optional fdsOpt = getStoreSession(ds.getStoreName()); + if (fdsOpt.isPresent()){ + var fds = fdsOpt.get(); + ByteArrayInputStream bais = new ByteArrayInputStream(data); + fds.Put(bais, pathName); + } else { + throw new InvalidDataStoreException("Datastore not found"); } + } else { + throw new InvalidDataSourceException("Datasource not found"); } } - public T GetStoreSession(String name) throws Exception { + + /** + * Retrieves an {@link InputStream} from the given {@link DataSource} using the specified path key. + *

+ * This method attempts to obtain a {@code FileDataStore} session from the data source's store name, + * and then retrieves the corresponding input stream using the provided {@code pathKey}. + *

+ * + * @param dataSource the data source from which to retrieve the input stream + * @param pathKey the key or identifier used to locate the desired file within the store + * @return an {@code InputStream} for reading the data associated with the given key + * @throws InvalidDataSourceException if the store session cannot be obtained, if the store is not + * present, or if an underlying {@code InvalidDataStoreException} occurs + */ + public InputStream getInputStream(DataSource dataSource, String pathKey) throws InvalidDataSourceException{ + try{ + Optional fdsOpt = getStoreSession(dataSource.getStoreName()); + if(fdsOpt.isPresent()){ + var fds = fdsOpt.get(); + return fds.get(pathKey); + } + throw new InvalidDataSourceException("Unable to get input stream from the data source"); + } catch(InvalidDataStoreException ex) { + throw new InvalidDataSourceException(ex); + } + } + + /** + * Copies a local file to a remote data store. + * + *

This method retrieves a {@link DataSource} configured for output using the given + * destination name, then locates the associated {@link FileDataStore} session. + * It reads the local file from the specified {@code localPath} and uploads its contents + * to the remote store under the provided {@code pathKey}.

+ * + * @param destinationName the name of the remote destination to which the file will be copied + * @param pathKey the key (path) under which the file will be stored remotely + * @param localPath the file system path to the local file that needs to be copied + * @throws InvalidDataSourceException if the specified data source is invalid or cannot be retrieved + * @throws InvalidDataStoreException if the data store session cannot be established or is invalid + * @throws IOException if an I/O error occurs while reading the local file or writing to the remote store + */ + public void copyFileToRemote(String destinationName, String pathKey, String localPath) throws InvalidDataSourceException, InvalidDataStoreException, IOException{ + Optional dsOpt = this.getDataSource(new GetDataSourceInput(destinationName, DataSourceIOType.OUTPUT)); + if(dsOpt.isPresent()){ + var ds = dsOpt.get(); + Optional fdsOpt = getStoreSession(ds.getStoreName()); + if(fdsOpt.isPresent()){ + var fdstore = fdsOpt.get(); + File localFile = new File(localPath); + InputStream reader = new FileInputStream(localFile); + fdstore.Put(reader, pathKey); + } + } + } + + + /** + * Retrieves a session object of type {@code T} from a data store with the specified name. + *

+ * This method searches through the available {@code DataStore} instances to find one + * matching the given {@code name}. If found, it attempts to retrieve and cast the session + * object to the desired type {@code T}. + *

+ * + * @param The expected type of the session object. + * @param name The name of the data store to retrieve the session from. + * @return An {@code Optional} containing the session object if present and of the correct type, + * or {@code Optional.empty()} if no matching store is found or the session is {@code null}. + * @throws InvalidDataStoreException if the session cannot be cast to the specified type {@code T}. + */ + public Optional getStoreSession(String name) throws InvalidDataStoreException{ for(DataStore ds : stores){ if (ds.getName()==name){ - T session = (T)ds.getSession(); - if (session==null){ - throw new Exception("unable to cast to type t"); - }else{ - return session; + try{ + T session = (T)ds.getSession(); + if (session==null){ + return Optional.empty(); + } + return Optional.of(session); + } catch(ClassCastException ex){ + throw new InvalidDataStoreException(ex); } } } - throw new Exception("unable to find session by name of " + name); + return Optional.empty(); } } diff --git a/src/main/java/usace/cc/plugin/Message.java b/src/main/java/usace/cc/plugin/Message.java index 57f0837..3d5996d 100644 --- a/src/main/java/usace/cc/plugin/Message.java +++ b/src/main/java/usace/cc/plugin/Message.java @@ -1,12 +1,16 @@ package usace.cc.plugin; import com.fasterxml.jackson.annotation.JsonProperty; + public class Message { + @JsonProperty private String message; + public String getMessage(){ return message; } + public Message(String message){ this.message = message; } diff --git a/src/main/java/usace/cc/plugin/Payload.java b/src/main/java/usace/cc/plugin/Payload.java index b3e7c64..4ce7e11 100644 --- a/src/main/java/usace/cc/plugin/Payload.java +++ b/src/main/java/usace/cc/plugin/Payload.java @@ -2,18 +2,41 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class Payload { + @JsonProperty private IOManager ioManager; + @JsonProperty private Action[] actions; + public IOManager getIOManager(){ return ioManager; } + public Action[] getActions(){ return actions; } + public void setIOManager(IOManager inIOManager){ ioManager = inIOManager; } + + public DataStore[] getStores(){ + return this.ioManager.getStores(); + } + + public PayloadAttributes getAttributes(){ + return this.ioManager.getAttributes(); + } + + public DataSource[] getInputs(){ + return this.ioManager.getInputs(); + } + + public DataSource[] getOutputs(){ + return this.ioManager.getOutputs(); + } + + } diff --git a/src/main/java/usace/cc/plugin/PayloadAttributes.java b/src/main/java/usace/cc/plugin/PayloadAttributes.java index 8bdd074..8d91fa3 100644 --- a/src/main/java/usace/cc/plugin/PayloadAttributes.java +++ b/src/main/java/usace/cc/plugin/PayloadAttributes.java @@ -1,15 +1,48 @@ package usace.cc.plugin; import java.util.Map; +import java.util.Optional; + import com.fasterxml.jackson.annotation.JsonProperty; public class PayloadAttributes { + @JsonProperty private Map attributes; - public Map getParameters(){ + public Map getAttributes(){ return attributes; } + + public Optional get(String name) throws IllegalArgumentException{ + Object val = attributes.get(name); + if (val==null){ + return Optional.empty(); + }else{ + try { + @SuppressWarnings("unchecked") + T tval = (T) val; + return Optional.of(tval); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Invalid type cast.", e); + } + } + } + + //@TODO delete if uneccesary + public Optional getAlt1(String name, Class clazz) throws IllegalArgumentException{ + Object val = attributes.get(name); + if (val==null){ + return Optional.empty(); + }else{ + if (clazz.isInstance(val)){ + return Optional.of((T)val); + } + throw new IllegalArgumentException("Incorrect type"); + } + } + + /* public int getInt(String name) throws Exception{ Object val = attributes.get(name); if (val==null){ @@ -22,6 +55,7 @@ public int getInt(String name) throws Exception{ } } } + public int getIntOrDefault(String name, int defaultValue){ try{ int value = getInt(name); @@ -31,6 +65,7 @@ public int getIntOrDefault(String name, int defaultValue){ return defaultValue; } } + public long getInt64(String name) throws Exception{ Object val = attributes.get(name); if (val==null){ @@ -43,6 +78,7 @@ public long getInt64(String name) throws Exception{ } } } + public long getInt64OrDefault(String name, long defaultValue){ try{ long value = getInt64(name); @@ -52,6 +88,7 @@ public long getInt64OrDefault(String name, long defaultValue){ return defaultValue; } } + public double getDouble(String name) throws Exception{ Object val = attributes.get(name); if (val==null){ @@ -101,4 +138,5 @@ public void UpdatePayloadAttributes()throws Exception{ }//TODO: what if it is an array of string? } } + */ } \ No newline at end of file diff --git a/src/main/java/usace/cc/plugin/PluginManager.java b/src/main/java/usace/cc/plugin/PluginManager.java index 411f25f..8ec5c71 100644 --- a/src/main/java/usace/cc/plugin/PluginManager.java +++ b/src/main/java/usace/cc/plugin/PluginManager.java @@ -1,5 +1,8 @@ package usace.cc.plugin; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -32,51 +35,19 @@ private PluginManager(){ logger = new Logger(sender, ErrorLevel.WARN); cs = new CcStoreS3(); try { - payload = cs.GetPayload(); - payload.getIOManager().ConnectStores(); + this.payload = cs.GetPayload(); + this.connectStores(payload.getStores()); for (Action action : payload.getActions()) { - action.getIOManager().ConnectStores(); + this.connectStores(action.getStores()); } - //substitutePathVariables(); + substitutePathVariables(); } catch (Exception e) { e.printStackTrace(); } } - private void substitutePathVariables() throws Exception{ - payload.setIOManager(payload.getIOManager().UpdateIOManagerPaths()); - if(payload.getActions()!=null){ - for (int i= 0; i entry : attrs.entrySet()) { + var key = entry.getKey(); + var val = entry.getValue(); + if (val instanceof String){ + parameterSubstitute((String)val, pattrs); + } + } + } + + //a.k.a. pathssubstitute + private void substitutePaths(DataSource ds, PayloadAttributes attrs){ + var param = parameterSubstitute(ds.getName(),attrs); + ds.setName(param); + + var paths = ds.getPaths(); + for (int i=0;i valattr = attrs.get(subname); + //Optional valattr = (Optional)attrs.get(subname); + //Optional valattr = attrs.getAlt1(subname, String.class); + Optional optVal = attrs.get(subname); + if (optVal.isPresent()){ + param = param.replaceFirst("\\{"+result+"\\}", optVal.get());//? + m = p.matcher(param); + } else{ + //@TODO logging is kind of annoying + logger.LogMessage(new Message(String.format("Attribute %s not found", subname))); + } + break; + } + } + return param; + } + + + // private void substitutePathVariables() throws Exception{ + // payload.setIOManager(payload.getIOManager().UpdateIOManagerPaths()); + // if(payload.getActions()!=null){ + // for (int i= 0; i Date: Tue, 29 Apr 2025 15:47:44 -0400 Subject: [PATCH 4/6] get payloads working. finish up editing styles --- build.gradle | 2 +- src/main/java/usace/cc/plugin/AWSConfig.java | 3 - src/main/java/usace/cc/plugin/Action.java | 27 +---- src/main/java/usace/cc/plugin/CcStore.java | 12 +- src/main/java/usace/cc/plugin/CcStoreS3.java | 79 ++++++------ .../usace/cc/plugin/ConnectionDataStore.java | 4 +- src/main/java/usace/cc/plugin/Constants.java | 4 +- src/main/java/usace/cc/plugin/DataSource.java | 40 ++----- src/main/java/usace/cc/plugin/DataStore.java | 46 ++++--- .../cc/plugin/DataStoreTypeRegistry.java | 22 ++-- .../usace/cc/plugin/EnvironmentVariables.java | 1 + src/main/java/usace/cc/plugin/Error.java | 4 + .../java/usace/cc/plugin/FileDataStore.java | 7 +- .../java/usace/cc/plugin/FileDataStoreS3.java | 32 ++--- .../java/usace/cc/plugin/GetObjectInput.java | 21 ++-- src/main/java/usace/cc/plugin/IOManager.java | 16 ++- src/main/java/usace/cc/plugin/Logger.java | 16 +-- .../java/usace/cc/plugin/ObjectState.java | 4 +- src/main/java/usace/cc/plugin/Payload.java | 31 +---- .../usace/cc/plugin/PayloadAttributes.java | 102 +--------------- .../java/usace/cc/plugin/PluginManager.java | 113 ++++-------------- .../java/usace/cc/plugin/PullObjectInput.java | 24 ++-- .../java/usace/cc/plugin/PutObjectInput.java | 30 +++-- src/main/java/usace/cc/plugin/SeedSet.java | 5 + src/main/java/usace/cc/plugin/Status.java | 4 + src/main/java/usace/cc/plugin/StoreType.java | 3 +- 26 files changed, 243 insertions(+), 409 deletions(-) diff --git a/build.gradle b/build.gradle index b9c404d..4d8e03f 100644 --- a/build.gradle +++ b/build.gradle @@ -51,5 +51,5 @@ publishing { } group 'mil.army.usace.hec' - version '0.0.56' + version '0.0.50' } diff --git a/src/main/java/usace/cc/plugin/AWSConfig.java b/src/main/java/usace/cc/plugin/AWSConfig.java index df6726d..cde21de 100644 --- a/src/main/java/usace/cc/plugin/AWSConfig.java +++ b/src/main/java/usace/cc/plugin/AWSConfig.java @@ -18,9 +18,6 @@ public class AWSConfig { @JsonProperty public String aws_bucket; - @JsonProperty - public Boolean aws_mock; - @JsonProperty public String aws_endpoint; diff --git a/src/main/java/usace/cc/plugin/Action.java b/src/main/java/usace/cc/plugin/Action.java index 567fc5e..6d425be 100644 --- a/src/main/java/usace/cc/plugin/Action.java +++ b/src/main/java/usace/cc/plugin/Action.java @@ -2,41 +2,20 @@ import com.fasterxml.jackson.annotation.JsonProperty; -public class Action { +public class Action extends IOManager{ @JsonProperty private String type; - @JsonProperty + @JsonProperty("description") private String desc; - @JsonProperty - private IOManager ioManager; - public String getType(){ return type; } - public IOManager getIOManager(){ - return ioManager; - } + public String getDescription(){ return desc; } - public DataStore[] getStores(){ - return this.ioManager.getStores(); - } - - public DataSource[] getInputs() { - return this.ioManager.getInputs(); - } - - public DataSource[] getOutputs(){ - return this.ioManager.getOutputs(); - } - - public PayloadAttributes getAttributes(){ - return this.ioManager.getAttributes(); - } - } diff --git a/src/main/java/usace/cc/plugin/CcStore.java b/src/main/java/usace/cc/plugin/CcStore.java index 0d76bd7..9ef1b25 100644 --- a/src/main/java/usace/cc/plugin/CcStore.java +++ b/src/main/java/usace/cc/plugin/CcStore.java @@ -1,11 +1,11 @@ package usace.cc.plugin; public interface CcStore { - public boolean PutObject(PutObjectInput input); - public boolean PullObject(PullObjectInput input); - public byte[] GetObject(GetObjectInput input) throws Exception; - public Payload GetPayload() throws Exception; + public boolean putObject(PutObjectInput input); + public boolean pullObject(PullObjectInput input); + public byte[] getObject(GetObjectInput input) throws Exception; + public Payload getPayload() throws Exception; //public void SetPayload(Payload payload); only used in the go sdk to support cloudcompute which is written in go. - public String RootPath(); - public boolean HandlesDataStoreType(StoreType datastoretype); + public String rootPath(); + public boolean handlesDataStoreType(StoreType datastoretype); } diff --git a/src/main/java/usace/cc/plugin/CcStoreS3.java b/src/main/java/usace/cc/plugin/CcStoreS3.java index 4f124f6..9c42f56 100644 --- a/src/main/java/usace/cc/plugin/CcStoreS3.java +++ b/src/main/java/usace/cc/plugin/CcStoreS3.java @@ -34,37 +34,27 @@ public class CcStoreS3 implements CcStore { String bucket; String root; String manifestId; + String payloadId; StoreType storeType; AmazonS3 awsS3; AWSConfig config; - @Override - public String RootPath() { - return bucket; - } + public CcStoreS3(){ AWSConfig acfg = new AWSConfig(); acfg.aws_access_key_id = System.getenv(EnvironmentVariables.CC_PROFILE + "_" + EnvironmentVariables.AWS_ACCESS_KEY_ID); acfg.aws_secret_access_key_id = System.getenv(EnvironmentVariables.CC_PROFILE + "_" + EnvironmentVariables.AWS_SECRET_ACCESS_KEY); acfg.aws_region = System.getenv(EnvironmentVariables.CC_PROFILE + "_" + EnvironmentVariables.AWS_DEFAULT_REGION); acfg.aws_bucket = System.getenv(EnvironmentVariables.CC_PROFILE + "_" + EnvironmentVariables.AWS_S3_BUCKET); - acfg.aws_mock = Boolean.parseBoolean(System.getenv(EnvironmentVariables.CC_PROFILE + "_" +"S3_MOCK"));//convert to boolean;//stringformat - acfg.aws_endpoint = System.getenv(EnvironmentVariables.CC_PROFILE + "_" +"S3_ENDPOINT"); - acfg.aws_disable_ssl = Boolean.parseBoolean(System.getenv(EnvironmentVariables.CC_PROFILE + "_" +"S3_DISABLE_SSL"));//convert to bool? - acfg.aws_force_path_style = Boolean.parseBoolean(System.getenv(EnvironmentVariables.CC_PROFILE + "_" +"S3_FORCE_PATH_STYLE"));//convert to bool + acfg.aws_endpoint = System.getenv(EnvironmentVariables.CC_PROFILE + "_" +"AWS_ENDPOINT"); config = acfg; - //System.out.println(EnvironmentVariables.CC_PROFILE + "_" + EnvironmentVariables.AWS_DEFAULT_REGION+"::"+config.aws_region); - //System.out.println(EnvironmentVariables.CC_PROFILE + "_" + EnvironmentVariables.AWS_ACCESS_KEY_ID+"::"+config.aws_access_key_id); - //System.out.println(EnvironmentVariables.CC_PROFILE + "_" + EnvironmentVariables.AWS_SECRET_ACCESS_KEY+"::"+config.aws_secret_access_key_id); - //System.out.println(EnvironmentVariables.CC_PROFILE + "_" + EnvironmentVariables.AWS_S3_BUCKET+"::"+config.aws_bucket); + Region clientRegion = RegionUtils.getRegion(config.aws_region);//.toUpperCase().replace("-", "_"));//Regions.valueOf(config.aws_region.toUpperCase().replace("-", "_")); try { AmazonS3 s3Client = null; - if(config.aws_mock){ - System.out.println("mocking s3 with minio"); - //System.out.println(EnvironmentVariables.CC_PROFILE + "_S3_MOCK::"+config.aws_mock); - //System.out.println(EnvironmentVariables.CC_PROFILE + "_S3_ENDPOINT::"+config.aws_endpoint); - //System.out.println(EnvironmentVariables.CC_PROFILE + "_S3_DISABLE_SSL::"+config.aws_disable_ssl); - //System.out.println(EnvironmentVariables.CC_PROFILE + "_S3_FORCE_PATH_STYLE::"+config.aws_force_path_style); + if(!config.aws_endpoint.equals("")){ + System.out.println(String.format("Using alt endpoint: %s",config.aws_endpoint)); + config.aws_force_path_style=true; + config.aws_disable_ssl=true; AWSCredentials credentials = new BasicAWSCredentials(config.aws_access_key_id, config.aws_secret_access_key_id); ClientConfiguration clientConfiguration = new ClientConfiguration(); clientConfiguration.setSignerOverride("AWSS3V4SignerType"); @@ -97,20 +87,28 @@ public CcStoreS3(){ } storeType = StoreType.S3; manifestId = System.getenv(EnvironmentVariables.CC_MANIFEST_ID); - localRootPath = Constants.LocalRootPath; + payloadId = System.getenv(EnvironmentVariables.CC_PAYLOAD_ID); + localRootPath = Constants.LOCAL_ROOT_PATH; bucket = config.aws_bucket;// + Constants.RemoteRootPath; root = System.getenv(EnvironmentVariables.CC_ROOT); } + + @Override + public String rootPath() { + return bucket; + } + @Override - public boolean HandlesDataStoreType(StoreType storeType){ + public boolean handlesDataStoreType(StoreType storeType){ return this.storeType == storeType; } + @Override - public boolean PutObject(PutObjectInput input) { + public boolean putObject(PutObjectInput input) { String path = root + "/" + manifestId + "/" + input.getFileName() + "." + input.getFileExtension(); byte[] data; switch(input.getObjectState()){ - case LocalDisk: + case LOCAL_DISK: //read from local File file = new File(path); data = new byte[(int) file.length()]; @@ -120,11 +118,11 @@ public boolean PutObject(PutObjectInput input) { catch(Exception e){ //@TODOprint? } - UploadToS3(config.aws_bucket, path, data); + uploadToS3(config.aws_bucket, path, data); break; - case Memory: + case MEMORY: data = input.getData(); - UploadToS3(config.aws_bucket, path, data); + uploadToS3(config.aws_bucket, path, data); break; default: return false; @@ -132,14 +130,15 @@ public boolean PutObject(PutObjectInput input) { return true; } + @Override - public boolean PullObject(PullObjectInput input) { + public boolean pullObject(PullObjectInput input) { String path = root + "/" + manifestId + "/" + input.getFileName() + "." + input.getFileExtension(); byte[] data; String localPath = input.getDestRootPath() + "/" + input.getFileName() + "." + input.getFileExtension(); try { //get the object from s3 - data = DownloadBytesFromS3(path); + data = downloadBytesFromS3(path); //create localpath writer InputStream stream = new ByteArrayInputStream(data); //write it. @@ -149,6 +148,7 @@ public boolean PullObject(PullObjectInput input) { } return false; } + private void writeInputStreamToDisk(InputStream input, String outputDestination) throws IOException { String directory = new File(outputDestination).getParent(); File f = new File(directory); @@ -162,28 +162,31 @@ private void writeInputStreamToDisk(InputStream input, String outputDestination) e.printStackTrace(); }; } + @Override - public byte[] GetObject(GetObjectInput input) throws AmazonS3Exception { - String path = root + "/" + manifestId + "/" + input.getFileName() + "." + input.getFileExtension(); + public byte[] getObject(GetObjectInput input) throws AmazonS3Exception { + String path = root + "/" + payloadId + "/" + input.getFileName() + "." + input.getFileExtension(); byte[] data; try { - data = DownloadBytesFromS3(path); + data = downloadBytesFromS3(path); } catch (Exception e) { throw new AmazonS3Exception(e.toString()); } return data; } + @Override - public Payload GetPayload() throws AmazonS3Exception { - String filepath = root + "/" + manifestId + "/" + Constants.PayloadFileName; + public Payload getPayload() throws AmazonS3Exception { + String filepath = root + "/" + payloadId + "/" + Constants.PAYLOAD_FILE_NAME; try{ - byte[] body = DownloadBytesFromS3(filepath); - return ReadJsonModelPayloadFromBytes(body); + byte[] body = downloadBytesFromS3(filepath); + return readJsonModelPayloadFromBytes(body); } catch (Exception e){ throw new AmazonS3Exception(e.toString()); } } - private byte[] DownloadBytesFromS3(String key) throws Exception{ + + private byte[] downloadBytesFromS3(String key) throws Exception{ S3Object fullObject = null; boolean spaces = key.contains(" "); if(spaces){ @@ -208,7 +211,8 @@ private byte[] DownloadBytesFromS3(String key) throws Exception{ } } } - private Payload ReadJsonModelPayloadFromBytes(byte[] bytes) throws Exception { + + private Payload readJsonModelPayloadFromBytes(byte[] bytes) throws Exception { final ObjectMapper mapper = new ObjectMapper(); // jackson databind try { return mapper.readValue(bytes, Payload.class); @@ -216,7 +220,8 @@ private Payload ReadJsonModelPayloadFromBytes(byte[] bytes) throws Exception { throw e; } } - private void UploadToS3(String bucketName, String objectKey, byte[] fileBytes) { + + private void uploadToS3(String bucketName, String objectKey, byte[] fileBytes) { try { //File file = new File(objectPath); InputStream stream = new ByteArrayInputStream(fileBytes); diff --git a/src/main/java/usace/cc/plugin/ConnectionDataStore.java b/src/main/java/usace/cc/plugin/ConnectionDataStore.java index 6237d03..c6de7b2 100644 --- a/src/main/java/usace/cc/plugin/ConnectionDataStore.java +++ b/src/main/java/usace/cc/plugin/ConnectionDataStore.java @@ -1,6 +1,6 @@ package usace.cc.plugin; public interface ConnectionDataStore { - public ConnectionDataStore Connect(DataStore ds); - public Object RawSession(); + public ConnectionDataStore connect(DataStore ds); + public Object rawSession(); } diff --git a/src/main/java/usace/cc/plugin/Constants.java b/src/main/java/usace/cc/plugin/Constants.java index 825f3db..3c9610f 100644 --- a/src/main/java/usace/cc/plugin/Constants.java +++ b/src/main/java/usace/cc/plugin/Constants.java @@ -1,6 +1,6 @@ package usace.cc.plugin; public final class Constants { - public static String PayloadFileName = "payload"; - public static String LocalRootPath = "/data"; + public static String PAYLOAD_FILE_NAME = "payload"; + public static String LOCAL_ROOT_PATH = "/data"; } diff --git a/src/main/java/usace/cc/plugin/DataSource.java b/src/main/java/usace/cc/plugin/DataSource.java index 57b3fd1..c3afb5e 100644 --- a/src/main/java/usace/cc/plugin/DataSource.java +++ b/src/main/java/usace/cc/plugin/DataSource.java @@ -1,5 +1,8 @@ package usace.cc.plugin; + +import java.util.Map; import com.fasterxml.jackson.annotation.JsonProperty; + public class DataSource { @JsonProperty @@ -8,14 +11,14 @@ public class DataSource { @JsonProperty private String id; - @JsonProperty + @JsonProperty("store_name") private String storeName; @JsonProperty - private String[] paths; + private Map paths; - @JsonProperty - private String[] dataPaths; + @JsonProperty("data_paths") + private Map dataPaths; public String getId(){ return id; @@ -29,45 +32,24 @@ public void setName(String name){ this.name=name; } - public String[] getPaths(){ + public Map getPaths(){ return this.paths; } - public void setPaths(String[] paths){ + public void setPaths(Map paths){ this.paths=paths; } - public String[] getDataPaths(){ + public Map getDataPaths(){ return dataPaths; } - public void setDataPaths(String[] datapaths){ + public void setDataPaths(Map datapaths){ this.dataPaths=datapaths; } - public String getStoreName(){ return storeName; } - // public DataSource UpdatePaths()throws Exception{ - // DataSource dest = this; - // PluginManager pm = PluginManager.getInstance(); - // dest.Name = pm.substitutePath(this.getName()); - // if(this.getPaths()!=null){ - // for(int j=0; j parameters; + //private PayloadAttributes parameters; + + @JsonProperty("store_type") + private StoreType storeType; + + @JsonProperty("profile") + private String dsProfile; + + //@TODO can session be a narrower type or a generic? + private Object session; public String getName(){ - return Name; + return name; } public String getId(){ - return ID; + return id; } public StoreType getStoreType(){ - return StoreType; + return storeType; } public PayloadAttributes getParameters(){ - return Parameters; + return new PayloadAttributes(parameters); } public String getDsProfile(){ - return DsProfile; + return dsProfile; } public Object getSession(){ - return Session; + return session; } public void setSession(Object session){ - this.Session = session; + this.session = session; } } \ No newline at end of file diff --git a/src/main/java/usace/cc/plugin/DataStoreTypeRegistry.java b/src/main/java/usace/cc/plugin/DataStoreTypeRegistry.java index 9c91abb..19f975c 100644 --- a/src/main/java/usace/cc/plugin/DataStoreTypeRegistry.java +++ b/src/main/java/usace/cc/plugin/DataStoreTypeRegistry.java @@ -6,21 +6,25 @@ public final class DataStoreTypeRegistry { private Map registry; - private static DataStoreTypeRegistry _instance = null; - public static DataStoreTypeRegistry getInstance(){ - if (_instance==null){ - _instance = new DataStoreTypeRegistry(); - } - return _instance; - } + private static DataStoreTypeRegistry instance = null; + private DataStoreTypeRegistry(){ registry = new HashMap(); registry.put(StoreType.S3, FileDataStoreS3.class); } - public static void Register(StoreType storeType, Object storeInstance){ + + public static DataStoreTypeRegistry getInstance(){ + if (instance==null){ + instance = new DataStoreTypeRegistry(); + } + return instance; + } + + public static void register(StoreType storeType, Object storeInstance){ DataStoreTypeRegistry.getInstance().registry.put(storeType, storeInstance.getClass()); } - public Object New(StoreType s)throws Exception{ + + public Object newStore(StoreType s)throws Exception{ Type type = DataStoreTypeRegistry.getInstance().registry.get(s); return type.getClass().getDeclaredConstructor().newInstance(); } diff --git a/src/main/java/usace/cc/plugin/EnvironmentVariables.java b/src/main/java/usace/cc/plugin/EnvironmentVariables.java index 22e30fa..00d1822 100644 --- a/src/main/java/usace/cc/plugin/EnvironmentVariables.java +++ b/src/main/java/usace/cc/plugin/EnvironmentVariables.java @@ -2,6 +2,7 @@ public final class EnvironmentVariables { public static String CC_MANIFEST_ID = "CC_MANIFEST_ID"; + public static String CC_PAYLOAD_ID = "CC_PAYLOAD_ID"; //public static String CC_EVENT_NUMBER = "CC_EVENT_NUMBER"; public static String CC_EVENT_IDENTIFIER = "CC_EVENT_IDENTIFIER"; public static String CC_EVENT_ID = "CC_EVENT_ID"; diff --git a/src/main/java/usace/cc/plugin/Error.java b/src/main/java/usace/cc/plugin/Error.java index 20e0134..d0a2a6c 100644 --- a/src/main/java/usace/cc/plugin/Error.java +++ b/src/main/java/usace/cc/plugin/Error.java @@ -15,13 +15,17 @@ enum ErrorLevel { DISABLED; public static final EnumSet All_Opts = EnumSet.allOf(ErrorLevel.class); } + @JsonProperty private String error; + @JsonProperty private ErrorLevel errorlevel; + public String getError(){ return error; } + public ErrorLevel getErrorLevel(){ return errorlevel; } diff --git a/src/main/java/usace/cc/plugin/FileDataStore.java b/src/main/java/usace/cc/plugin/FileDataStore.java index 1c66a53..6dc6310 100644 --- a/src/main/java/usace/cc/plugin/FileDataStore.java +++ b/src/main/java/usace/cc/plugin/FileDataStore.java @@ -1,9 +1,10 @@ package usace.cc.plugin; + import java.io.InputStream; public interface FileDataStore { - public Boolean Copy(FileDataStore destStore, String srcPath, String destPath); + public Boolean copy(FileDataStore destStore, String srcPath, String destPath); public InputStream get(String path); - public Boolean Put(InputStream data, String path); - public Boolean Delete(String path); + public Boolean put(InputStream data, String path); + public Boolean delete(String path); } \ No newline at end of file diff --git a/src/main/java/usace/cc/plugin/FileDataStoreS3.java b/src/main/java/usace/cc/plugin/FileDataStoreS3.java index a8e5a20..a62ff5d 100644 --- a/src/main/java/usace/cc/plugin/FileDataStoreS3.java +++ b/src/main/java/usace/cc/plugin/FileDataStoreS3.java @@ -26,7 +26,7 @@ import com.amazonaws.services.s3.model.PutObjectResult; import com.amazonaws.services.s3.model.S3Object; - +//@TODO move all package private vars to class private vars public class FileDataStoreS3 implements FileDataStore, ConnectionDataStore { String bucket; String postFix; @@ -35,13 +35,15 @@ public class FileDataStoreS3 implements FileDataStore, ConnectionDataStore { AWSConfig config; private static String S3ROOT = "root"; + public FileDataStoreS3(){} + @Override - public Boolean Copy(FileDataStore destStore, String srcPath, String destPath) { + public Boolean copy(FileDataStore destStore, String srcPath, String destPath) { byte[] data; try { - data = GetObject(srcPath); + data = getObject(srcPath); ByteArrayInputStream bias = new ByteArrayInputStream(data); - return destStore.Put(bias, destPath); + return destStore.put(bias, destPath); } catch (RemoteException e) { e.printStackTrace(); return false; @@ -65,11 +67,11 @@ public InputStream get(String path) { } @Override - public Boolean Put(InputStream data, String path) { + public Boolean put(InputStream data, String path) { byte[] bytes; try { bytes = data.readAllBytes(); - return UploadToS3(config.aws_bucket, postFix + "/" + path, bytes); + return uploadToS3(config.aws_bucket, postFix + "/" + path, bytes); } catch (IOException e) { e.printStackTrace(); return false; @@ -78,7 +80,7 @@ public Boolean Put(InputStream data, String path) { } @Override - public Boolean Delete(String path) { + public Boolean delete(String path) { DeleteObjectRequest dor = new DeleteObjectRequest(config.aws_bucket,postFix + "/" + path); try{ awsS3.deleteObject(dor); @@ -93,15 +95,13 @@ public Boolean Delete(String path) { } } - public FileDataStoreS3(){} - @Override - public Object RawSession(){ + public Object rawSession(){ return awsS3; } @Override - public ConnectionDataStore Connect(DataStore ds) { + public ConnectionDataStore connect(DataStore ds) { config = new AWSConfig(); config.aws_access_key_id = System.getenv(ds.getDsProfile() + "_" + EnvironmentVariables.AWS_ACCESS_KEY_ID); config.aws_secret_access_key_id = System.getenv(ds.getDsProfile() + "_" + EnvironmentVariables.AWS_SECRET_ACCESS_KEY); @@ -139,9 +139,11 @@ public ConnectionDataStore Connect(DataStore ds) { e.printStackTrace(); throw new RuntimeException(e); } + storeType = StoreType.S3; String tmpRoot=""; + try { Optional optParam = ds.getParameters().get(FileDataStoreS3.S3ROOT); if (optParam.isPresent()){ @@ -161,17 +163,17 @@ public ConnectionDataStore Connect(DataStore ds) { return this; } - private byte[] GetObject(String path) throws RemoteException { + private byte[] getObject(String path) throws RemoteException { byte[] data; try { - data = DownloadBytesFromS3(path); + data = downloadBytesFromS3(path); } catch (Exception e) { throw new RemoteException(e.toString()); } return data; } - private byte[] DownloadBytesFromS3(String key) throws Exception{ + private byte[] downloadBytesFromS3(String key) throws Exception{ S3Object fullObject = null; key = postFix + "/" + key; System.out.println(key); @@ -194,7 +196,7 @@ private byte[] DownloadBytesFromS3(String key) throws Exception{ } } - private boolean UploadToS3(String bucketName, String objectKey, byte[] fileBytes) { + private boolean uploadToS3(String bucketName, String objectKey, byte[] fileBytes) { try { InputStream stream = new ByteArrayInputStream(fileBytes); ObjectMetadata meta = new ObjectMetadata(); diff --git a/src/main/java/usace/cc/plugin/GetObjectInput.java b/src/main/java/usace/cc/plugin/GetObjectInput.java index fb6c513..95ef467 100644 --- a/src/main/java/usace/cc/plugin/GetObjectInput.java +++ b/src/main/java/usace/cc/plugin/GetObjectInput.java @@ -5,25 +5,28 @@ public class GetObjectInput { private String fileExtension; private StoreType sourceStoreType; private String sourceRootPath; + + public GetObjectInput(String fileName, StoreType sourceStoreType, String sourceRootPath, String fileExtension){ + this.fileName = fileName; + this.sourceStoreType = sourceStoreType; + this.sourceRootPath = sourceRootPath; + this.fileExtension = fileExtension; + } + public String getFileName(){ return fileName; } + public String getFileExtension(){ return fileExtension; } + public StoreType getSourceStoreType(){ return sourceStoreType; } + public String getSourceRootPath(){ return sourceRootPath; } - /** - * - */ - public GetObjectInput(String fileName, StoreType sourceStoreType, String sourceRootPath, String fileExtension){ - this.fileName = fileName; - this.sourceStoreType = sourceStoreType; - this.sourceRootPath = sourceRootPath; - this.fileExtension = fileExtension; - } + } \ No newline at end of file diff --git a/src/main/java/usace/cc/plugin/IOManager.java b/src/main/java/usace/cc/plugin/IOManager.java index 9e18a62..258854c 100644 --- a/src/main/java/usace/cc/plugin/IOManager.java +++ b/src/main/java/usace/cc/plugin/IOManager.java @@ -1,6 +1,7 @@ package usace.cc.plugin; import java.util.Arrays; +import java.util.Map; import java.util.Optional; import java.io.ByteArrayInputStream; import java.io.File; @@ -10,6 +11,8 @@ import java.io.InputStream; import java.io.OutputStream; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; public class IOManager { @@ -36,21 +39,26 @@ public InvalidDataStoreException(Exception ex){ } @JsonProperty - private PayloadAttributes attributes; + @JsonIgnoreProperties(ignoreUnknown = true) + private Map attributes; + //private PayloadAttributes attributes; @JsonProperty + @JsonIgnoreProperties(ignoreUnknown = true) private DataStore[] stores; @JsonProperty + @JsonIgnoreProperties(ignoreUnknown = true) private DataSource[] inputs; @JsonProperty + @JsonIgnoreProperties(ignoreUnknown = true) private DataSource[] outputs; private IOManager parent; public PayloadAttributes getAttributes(){ - return attributes; + return new PayloadAttributes(attributes); } public DataStore[] getStores(){ @@ -264,7 +272,7 @@ public void put(byte[] data, String dataSourceName, String pathName, String data if (fdsOpt.isPresent()){ var fds = fdsOpt.get(); ByteArrayInputStream bais = new ByteArrayInputStream(data); - fds.Put(bais, pathName); + fds.put(bais, pathName); } else { throw new InvalidDataStoreException("Datastore not found"); } @@ -323,7 +331,7 @@ public void copyFileToRemote(String destinationName, String pathKey, String loca var fdstore = fdsOpt.get(); File localFile = new File(localPath); InputStream reader = new FileInputStream(localFile); - fdstore.Put(reader, pathKey); + fdstore.put(reader, pathKey); } } } diff --git a/src/main/java/usace/cc/plugin/Logger.java b/src/main/java/usace/cc/plugin/Logger.java index a8a13c6..6dc6e14 100644 --- a/src/main/java/usace/cc/plugin/Logger.java +++ b/src/main/java/usace/cc/plugin/Logger.java @@ -1,33 +1,35 @@ package usace.cc.plugin; import java.time.LocalDate; - import usace.cc.plugin.Error.ErrorLevel; public class Logger { //this is an aggregator, it is anticipated that this will get replaced but the api will remain. private ErrorLevel errorLevel; private String sender; + public Logger(String sender, ErrorLevel level){ this.sender = sender; this.errorLevel = level; } + public void setErrorLevel(ErrorLevel level){ this.errorLevel = level; } - public void LogMessage(Message message){ + + public void logMessage(Message message){ String line = this.sender + ":" + LocalDate.now() + "\n\t" + message.getMessage() + "\n"; System.out.println(line); } - public void LogError(Error error){ + + public void logError(Error error){ if (error.getErrorLevel().compareTo(this.errorLevel)>=0){ String line = sender + "issues a " + error.getErrorLevel().toString() + " error:" + LocalDate.now() + "\n\t" + error.getError() + "\n"; System.out.println(line); - } - - + } } - public void ReportStatus(Status report){ + + public void reportStatus(Status report){ String line = this.sender + ":" + report.getStatus().toString() + ":" + LocalDate.now() + "\n\t" + report.getProgress() + " percent complete." + "\n"; System.out.println(line); } diff --git a/src/main/java/usace/cc/plugin/ObjectState.java b/src/main/java/usace/cc/plugin/ObjectState.java index 89a170b..041ea69 100644 --- a/src/main/java/usace/cc/plugin/ObjectState.java +++ b/src/main/java/usace/cc/plugin/ObjectState.java @@ -1,7 +1,7 @@ package usace.cc.plugin; public enum ObjectState { - Memory, - LocalDisk, + MEMORY, + LOCAL_DISK, //RemoteDisk //ToDo } diff --git a/src/main/java/usace/cc/plugin/Payload.java b/src/main/java/usace/cc/plugin/Payload.java index 4ce7e11..83c5049 100644 --- a/src/main/java/usace/cc/plugin/Payload.java +++ b/src/main/java/usace/cc/plugin/Payload.java @@ -1,42 +1,15 @@ package usace.cc.plugin; import com.fasterxml.jackson.annotation.JsonProperty; -public class Payload { - - @JsonProperty - private IOManager ioManager; +public class Payload extends IOManager{ + @JsonProperty private Action[] actions; - public IOManager getIOManager(){ - return ioManager; - } - public Action[] getActions(){ return actions; } - public void setIOManager(IOManager inIOManager){ - ioManager = inIOManager; - } - - public DataStore[] getStores(){ - return this.ioManager.getStores(); - } - - public PayloadAttributes getAttributes(){ - return this.ioManager.getAttributes(); - } - - public DataSource[] getInputs(){ - return this.ioManager.getInputs(); - } - - public DataSource[] getOutputs(){ - return this.ioManager.getOutputs(); - } - - } diff --git a/src/main/java/usace/cc/plugin/PayloadAttributes.java b/src/main/java/usace/cc/plugin/PayloadAttributes.java index 8d91fa3..65c97ec 100644 --- a/src/main/java/usace/cc/plugin/PayloadAttributes.java +++ b/src/main/java/usace/cc/plugin/PayloadAttributes.java @@ -7,6 +7,10 @@ public class PayloadAttributes { + public PayloadAttributes(Map attrs){ + this.attributes=attrs; + } + @JsonProperty private Map attributes; @@ -41,102 +45,4 @@ public Optional getAlt1(String name, Class clazz) throws IllegalArgumentE throw new IllegalArgumentException("Incorrect type"); } } - - /* - public int getInt(String name) throws Exception{ - Object val = attributes.get(name); - if (val==null){ - throw new Exception(name + " not found"); - }else{ - if(val instanceof Number){ - return ((Number)val).intValue(); - }else{ - return Integer.parseInt(val.toString()); - } - } - } - - public int getIntOrDefault(String name, int defaultValue){ - try{ - int value = getInt(name); - return value; - }catch(Exception ex){ - System.out.println(ex.getMessage()); - return defaultValue; - } - } - - public long getInt64(String name) throws Exception{ - Object val = attributes.get(name); - if (val==null){ - throw new Exception(name + " not found"); - }else{ - if(val instanceof Number){ - return ((Number)val).longValue(); - }else{ - return Long.parseLong(val.toString()); - } - } - } - - public long getInt64OrDefault(String name, long defaultValue){ - try{ - long value = getInt64(name); - return value; - }catch(Exception ex){ - System.out.println(ex.getMessage()); - return defaultValue; - } - } - - public double getDouble(String name) throws Exception{ - Object val = attributes.get(name); - if (val==null){ - throw new Exception(name + " not found"); - }else{ - if(val instanceof Number){ - return ((Number)val).doubleValue(); - }else{ - return Double.parseDouble(val.toString()); - } - } - } - public double getDoubleOrDefault(String name, double defaultValue){ - try{ - double value = getDouble(name); - return value; - }catch(Exception ex){ - System.out.println(ex.getMessage()); - return defaultValue; - } - } - public String getString(String name) throws Exception{ - Object val = attributes.get(name); - if (val==null){ - throw new Exception(name + " not found"); - }else{ - return String.valueOf(val); - } - } - public String getStringOrDefault(String name, String defaultValue){ - try{ - String value = getString(name); - return value; - }catch(Exception ex){ - System.out.println(ex.getMessage()); - return defaultValue; - } - } - public void UpdatePayloadAttributes()throws Exception{ - PluginManager pm = PluginManager.getInstance(); - for(Map.Entry me : attributes.entrySet()){ - Object val = me.getValue(); - if(val instanceof String){ - String strval = String.valueOf(val); - strval = pm.substitutePath(strval); - attributes.replace(me.getKey(),strval); - }//TODO: what if it is an array of string? - } - } - */ } \ No newline at end of file diff --git a/src/main/java/usace/cc/plugin/PluginManager.java b/src/main/java/usace/cc/plugin/PluginManager.java index 8ec5c71..4f15db5 100644 --- a/src/main/java/usace/cc/plugin/PluginManager.java +++ b/src/main/java/usace/cc/plugin/PluginManager.java @@ -35,7 +35,7 @@ private PluginManager(){ logger = new Logger(sender, ErrorLevel.WARN); cs = new CcStoreS3(); try { - this.payload = cs.GetPayload(); + this.payload = cs.getPayload(); this.connectStores(payload.getStores()); for (Action action : payload.getActions()) { this.connectStores(action.getStores()); @@ -49,15 +49,16 @@ private PluginManager(){ public Payload getPayload(){ - if (!hasUpdatedPaths){ - try{ - substitutePathVariables(); - }catch(Exception e){ - e.printStackTrace(); - System.exit(1); - } - hasUpdatedPaths = true; - } + //@TODO why substitute here? + // if (!hasUpdatedPaths){ + // try{ + // substitutePathVariables(); + // }catch(Exception e){ + // e.printStackTrace(); + // System.exit(1); + // } + // hasUpdatedPaths = true; + // } return payload; } @@ -70,25 +71,25 @@ public void setLogLevel(ErrorLevel level){ } public void logMessage(Message message){ - logger.LogMessage(message); + logger.logMessage(message); } public void logError(Error error){ - logger.LogError(error); + logger.logError(error); } public void reportProgress(Status report){ - logger.ReportStatus(report); + logger.reportStatus(report); } private void connectStores(DataStore[] stores) throws Exception{ DataStoreTypeRegistry registry = DataStoreTypeRegistry.getInstance(); for (int i = 0; i entry : attrs.entrySet()) { - var key = entry.getKey(); + //var key = entry.getKey(); var val = entry.getValue(); if (val instanceof String){ parameterSubstitute((String)val, pattrs); @@ -136,14 +137,14 @@ private void substitutePaths(DataSource ds, PayloadAttributes attrs){ ds.setName(param); var paths = ds.getPaths(); - for (int i=0;i Date: Wed, 30 Apr 2025 09:28:35 -0400 Subject: [PATCH 5/6] switched to unchecked exceptions in DataSource. Add entryset to PayloadAttributes --- build.gradle | 2 +- src/main/java/usace/cc/plugin/IOManager.java | 6 +++--- src/main/java/usace/cc/plugin/PayloadAttributes.java | 10 ++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 4d8e03f..51a21e7 100644 --- a/build.gradle +++ b/build.gradle @@ -51,5 +51,5 @@ publishing { } group 'mil.army.usace.hec' - version '0.0.50' + version '0.0.59' } diff --git a/src/main/java/usace/cc/plugin/IOManager.java b/src/main/java/usace/cc/plugin/IOManager.java index 258854c..889ec74 100644 --- a/src/main/java/usace/cc/plugin/IOManager.java +++ b/src/main/java/usace/cc/plugin/IOManager.java @@ -18,7 +18,7 @@ public class IOManager { //IO Manager Error Types - static class InvalidDataSourceException extends Exception { + static class InvalidDataSourceException extends RuntimeException { public InvalidDataSourceException(String message) { super(message); } @@ -115,7 +115,7 @@ public Optional getStore(String name){ * @param gdsi an object encapsulating the data source name and the I/O type to search * @return an {@code Optional} containing the matching {@code DataSource}, or {@code Optional.empty()} * if no such data source exists in this instance or its parent chain - * @throws InvalidDataSourceException if the {@code DataSourceIOType} in the input is not recognized + * @throws InvalidDataSourceException runtime exception if the {@code DataSourceIOType} in the input is not recognized */ public Optional getDataSource(GetDataSourceInput gdsi) throws InvalidDataSourceException{ DataSource[] sources; @@ -131,7 +131,7 @@ public Optional getDataSource(GetDataSourceInput gdsi) throws Invali System.arraycopy(this.outputs, 0, sources, this.inputs.length, this.outputs.length); break; default: - throw new InvalidDataSourceException("datan source input type not recognized"); + throw new InvalidDataSourceException("data source input type not recognized"); } for(DataSource ds : sources){ diff --git a/src/main/java/usace/cc/plugin/PayloadAttributes.java b/src/main/java/usace/cc/plugin/PayloadAttributes.java index 65c97ec..d59df71 100644 --- a/src/main/java/usace/cc/plugin/PayloadAttributes.java +++ b/src/main/java/usace/cc/plugin/PayloadAttributes.java @@ -1,7 +1,9 @@ package usace.cc.plugin; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; +import java.util.Set; import com.fasterxml.jackson.annotation.JsonProperty; @@ -18,6 +20,14 @@ public Map getAttributes(){ return attributes; } + public Set> entrySet(){ + return attributes.entrySet(); + } + + public Set keySet(){ + return attributes.keySet(); + } + public Optional get(String name) throws IllegalArgumentException{ Object val = attributes.get(name); if (val==null){ From f1783d6a8adf61437df59aa95c56e33e6d987897 Mon Sep 17 00:00:00 2001 From: Will Lehman Date: Wed, 30 Apr 2025 09:13:50 -0500 Subject: [PATCH 6/6] Update CI.yml upgrade to java 17 --- .github/workflows/CI.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 40bd49b..ad0e2c8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -23,10 +23,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: Validate Gradle wrapper