diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000000..3c7e669b67
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.gitignore b/.gitignore
index edb2523fcf..52eb8ec1f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@
/.gradle/
build
!**/build
-/**/build
\ No newline at end of file
+/**/build
+/bin/
diff --git a/.project b/.project
new file mode 100644
index 0000000000..f673ce1ee0
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ adempiere-base
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/authentication/src/main/java/base/org/spin/authentication/services/addons/provider/GenericAuthentication.java b/authentication/src/main/java/base/org/spin/authentication/services/addons/provider/GenericAuthentication.java
new file mode 100644
index 0000000000..9d1c225cfb
--- /dev/null
+++ b/authentication/src/main/java/base/org/spin/authentication/services/addons/provider/GenericAuthentication.java
@@ -0,0 +1,66 @@
+/******************************************************************************
+ * Product: Adempiere ERP & CRM Smart Business Solution *
+ * This program is free software; you can redistribute it and/or modify it *
+ * under the terms version 2 of the GNU General Public License as published *
+ * by the Free Software Foundation. This program is distributed in the hope *
+ * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
+ * See the GNU General Public License for more details. *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
+ * For the text or an alternative of this public license, you may reach us *
+ * Copyright (C) 2003-2015 E.R.P. Consultores y Asociados, C.A. *
+ * All Rights Reserved. *
+ * Contributor(s): Carlos Parada www.erpya.com *
+ *****************************************************************************/
+package org.spin.authentication.services.addons.provider;
+
+import java.util.Optional;
+import org.spin.authentication.services.OpenIDConnect;
+import org.spin.model.MADAppRegistration;
+import com.nimbusds.oauth2.sdk.ResponseType;
+
+/**
+ * @author Carlos Parada, cparada@erpya.com, ERPCyA http://www.erpya.com
+ * Add support to login with Generic Open ID service
+ */
+public class GenericAuthentication extends OpenIDConnect{
+
+ /**Default Scope*/
+ private final static String defaultScope = "openid,email,profile";
+ /**Authorization End point Tag*/
+ private static final String AUTHORIZATION_ENDPOINT_TAG = "AUTHORIZATION_ENDPOINT";
+ /**Token End point Tag*/
+ private static final String TOKEN_ENDPOINT_TAG = "TOKEN_ENDPOINT";
+ /**User Info End point Tag*/
+ private static final String USERINFO_ENDPOINT_TAG = "USERINFO_ENDPOINT";
+ /**Scope Parameter*/
+ private static final String PARAMETER_SCOPE = "SCOPE";
+ /**
+ * Constructor
+ */
+ public GenericAuthentication() {
+ super();
+ setResponseType(new ResponseType(ResponseType.Value.CODE));
+ }
+
+ /***
+ * Set Default values from Application Registration
+ */
+ @Override
+ public void setAppRegistrationId(int registrationId) {
+ super.setAppRegistrationId(registrationId);
+ Optional maybeApRegistration = Optional.ofNullable(getApplicationRegistration());
+ maybeApRegistration.ifPresent(appRegistration -> {
+ String authorizationEndPoint = Optional.ofNullable(appRegistration.getParameterValue(AUTHORIZATION_ENDPOINT_TAG)).orElse("");
+ String tokenEndPoint = Optional.ofNullable(appRegistration.getParameterValue(TOKEN_ENDPOINT_TAG)).orElse("");
+ String userInfoEndPoint = Optional.ofNullable(appRegistration.getParameterValue(USERINFO_ENDPOINT_TAG)).orElse("");
+ String scope = Optional.ofNullable(appRegistration.getParameterValue(PARAMETER_SCOPE)).orElse(defaultScope);
+ setAuthorizationEndPoint(authorizationEndPoint);
+ setTokenEndpoint(tokenEndPoint);
+ setUserInfoEndpoint(userInfoEndPoint);
+ setScope(scope.split(","));
+ });
+ }
+}
diff --git a/authentication/xml/migration/10330_Add_App_Support_to_Keycloak_OpenID.xml b/authentication/xml/migration/10330_Add_App_Support_to_Keycloak_OpenID.xml
new file mode 100644
index 0000000000..d6e0771e0d
--- /dev/null
+++ b/authentication/xml/migration/10330_Add_App_Support_to_Keycloak_OpenID.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+ fff42604-9889-4493-a638-227399cfdba8
+ KeyCloak Authentication
+ KeyCloak Authentication
+
+ https://www.keycloak.org/documentation
+ org.spin.authentication.services.provider.GenericAuthentication
+ 2024-01-09 09:29:36.911
+ 2024-01-09 09:29:36.911
+ 100
+ true
+ 0
+ 0
+ true
+ 100
+ OIA
+ 50040
+
+
+
+
+ b640990d-4ebc-4a90-81df-9edabdda1a4e
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:30:11.336
+ 2024-01-09 09:30:11.336
+ 100
+ 100
+ 50083
+ http://localhost/webui
+ true
+
+ 50040
+ REDIRECT_URL
+ 10
+
+
+
+
+ e128578b-4847-4464-9971-e81268f4c363
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:30:23.516
+ 2024-01-09 09:30:23.516
+ 100
+ 100
+ 50084
+ <Client identifier>
+ true
+
+ 50040
+ CLIENT_ID
+ 10
+
+
+
+
+ 40a046c9-73da-4d6c-91de-00592ad1d640
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:30:42.13
+ 2024-01-09 09:30:42.13
+ 100
+ 100
+ 50085
+ <Client Secret>
+ true
+
+ 50040
+ CLIENT_SECRET
+ 10
+
+
+
+
+ 99720a45-addb-423a-87a8-f68bd40f85b7
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:33:05.241
+ 2024-01-09 09:33:05.241
+ 100
+ 100
+ 50086
+ https://localhost/realms/<Realm Name>/protocol/openid-connect/auth
+ true
+
+ 50040
+ AUTHORIZATION_ENDPOINT
+ 10
+
+
+
+
+ 220639ba-dd13-4be5-9020-f1b22c3506e3
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:33:37.259
+ 2024-01-09 09:33:37.259
+ 100
+ 100
+ 50087
+ https://localhost/realms/<Realm Name>/protocol/openid-connect/token
+ true
+
+ 50040
+ TOKEN_ENDPOINT
+ 10
+
+
+
+
+ 3477e6d4-7656-4964-ae06-da62882ecd69
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:33:48.495
+ 2024-01-09 09:33:48.495
+ 100
+ 100
+ 50088
+ https://localhost/realms/<Realm Name>/protocol/openid-connect/userinfo
+ true
+
+ 50040
+ USERINFO_ENDPOINT
+ 10
+
+
+
+
diff --git a/authentication/xml/migration/10340_Add_App_Support_to_Okta_OpenID.xml b/authentication/xml/migration/10340_Add_App_Support_to_Okta_OpenID.xml
new file mode 100644
index 0000000000..8ec577d2bf
--- /dev/null
+++ b/authentication/xml/migration/10340_Add_App_Support_to_Okta_OpenID.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+ bd02f9ca-1d29-499d-9ca4-4a953f356261
+ Okta Authentication
+ Okta Authentication
+ https://www.okta.com/
+ https://help.okta.com/oie/en-us/Content/Topics/Apps/Apps_App_Integration_Wizard_OIDC.htm
+ org.spin.authentication.services.provider.GenericAuthentication
+ 2024-01-09 09:22:44.517
+ 2024-01-09 09:22:44.517
+ 100
+ false
+ 0
+ 0
+ true
+ 100
+ OIA
+ 50039
+
+
+
+
+ 9999a1fd-d196-4982-9055-66b1002efed5
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:23:05.672
+ 2024-01-09 09:23:05.672
+ 100
+ 100
+ 50077
+ http://localhost/webui
+ true
+
+ 50039
+ REDIRECT_URL
+ 10
+
+
+
+
+ be6b6b6b-3e68-497f-a666-35c1dc4d0304
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:23:19.347
+ 2024-01-09 09:23:19.347
+ 100
+ 100
+ 50078
+ <Client identifier>
+ true
+
+ 50039
+ CLIENT_ID
+ 10
+
+
+
+
+ 24e2a4c0-8fe1-48d3-8b08-b51e17d11c34
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:23:36.523
+ 2024-01-09 09:23:36.523
+ 100
+ 100
+ 50079
+ <Client Secret>
+ true
+
+ 50039
+ CLIENT_SECRET
+ 10
+
+
+
+
+ 1140d439-36d0-4987-b311-e82d7f4ec31f
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:24:07.745
+ 2024-01-09 09:24:07.745
+ 100
+ 100
+ 50080
+ https://< Organization >.okta.com/oauth2/v1/authorize
+ true
+
+ 50039
+ AUTHORIZATION_ENDPOINT
+ 10
+
+
+
+
+ 3652e032-bc9e-4483-83dc-8336eb547b11
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:24:24.099
+ 2024-01-09 09:24:24.099
+ 100
+ 100
+ 50081
+ https://< Organization >.okta.com/oauth2/v1/token
+ true
+
+ 50039
+ TOKEN_ENDPOINT
+ 10
+
+
+
+
+ 4fafff03-ac98-4c2c-bb35-d11c183e6bee
+ C
+ 0
+ 0
+ true
+ 2024-01-09 09:24:46.273
+ 2024-01-09 09:24:46.273
+ 100
+ 100
+ 50082
+ https://< Organization >.okta.com/oauth2/v1/userinfo
+ true
+
+ 50039
+ USERINFO_ENDPOINT
+ 10
+
+
+
+
diff --git a/base/build.gradle b/base/build.gradle
index 19f68cc278..76f4e786a1 100644
--- a/base/build.gradle
+++ b/base/build.gradle
@@ -15,6 +15,7 @@ repositories {
}
dependencies {
+ api "com.amazonaws:aws-java-sdk-s3:1.12.777"
api 'net.sf.jasperreports:jasperreports-fonts:6.21.0'
// https://mvnrepository.com/artifact/io.konik/harness
api 'io.konik:harness:1.0.0'
diff --git a/base/src/org/spin/eca62/setup/Deploy.java b/base/src/org/spin/eca62/setup/Deploy.java
new file mode 100644
index 0000000000..d4f717ea15
--- /dev/null
+++ b/base/src/org/spin/eca62/setup/Deploy.java
@@ -0,0 +1,78 @@
+/******************************************************************************
+ * Product: Adempiere ERP & CRM Smart Business Solution *
+ * This program is free software; you can redistribute it and/or modify it *
+ * under the terms version 2 of the GNU General Public License as published *
+ * by the Free Software Foundation. This program is distributed in the hope *
+ * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
+ * See the GNU General Public License for more details. *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
+ * For the text or an alternative of this public license, you may reach us *
+ * Copyright (C) 2003-2015 E.R.P. Consultores y Asociados, C.A. *
+ * All Rights Reserved. *
+ * Contributor(s): Yamel Senih www.erpya.com *
+ *****************************************************************************/
+package org.spin.eca62.setup;
+
+import java.util.Properties;
+
+import org.adempiere.exceptions.AdempiereException;
+import org.compiere.model.MClientInfo;
+import org.compiere.util.Env;
+import org.spin.model.MADAppRegistration;
+import org.spin.model.MADAppSupport;
+import org.spin.util.ISetupDefinition;
+
+/**
+ * Create App Registration to Connect to local minio
+ * @author Yamel Senih, ysenih@erpya.com, ERPCyA http://www.erpya.com
+ */
+public class Deploy implements ISetupDefinition {
+
+ private static final String APP_TYPE = "AS3";
+
+ @Override
+ public String doIt(Properties context, String transactionName) {
+ // create App Registration
+ MADAppRegistration appRegistration = createConnection(context, transactionName);
+
+ // Set App Registration to Client Info
+ configClient(context, appRegistration, transactionName);
+
+ return "@AD_SetupDefinition_ID@ @Ok@";
+ }
+
+ private MADAppRegistration createConnection(Properties context, String transactionName) {
+ MADAppRegistration aws3Connection = MADAppRegistration.getByApplicationType(context, APP_TYPE, transactionName);
+ if(aws3Connection == null
+ || aws3Connection.getAD_AppRegistration_ID() <= 0) {
+ MADAppSupport aws3Support = MADAppSupport.getByApplicationType(context, APP_TYPE, transactionName);
+ if(aws3Support == null
+ || aws3Support.getAD_AppSupport_ID() <= 0) {
+ throw new AdempiereException("@AD_AppSupport_ID@ @EMail@ @NotFound@");
+ }
+ aws3Connection = new MADAppRegistration(context, 0, transactionName);
+ aws3Connection.setValue("AWS3");
+ aws3Connection.setApplicationType(APP_TYPE);
+ aws3Connection.setAD_AppSupport_ID(aws3Support.getAD_AppSupport_ID());
+ aws3Connection.setName("Default AWS3 Minio File Storage");
+ aws3Connection.setVersionNo("1.0");
+ aws3Connection.setHost("http://0.0.0.0");
+ aws3Connection.setPort(9000);
+ aws3Connection.saveEx();
+ }
+ return aws3Connection;
+ }
+
+ private void configClient(Properties context, MADAppRegistration aws3Connection, String transactionName) {
+ int clientId = Env.getAD_Client_ID(context);
+ MClientInfo clientInfo = MClientInfo.get(context, clientId, transactionName);
+ clientInfo.setFileHandler_ID(
+ aws3Connection.getAD_AppRegistration_ID()
+ );
+ clientInfo.saveEx();
+ }
+
+}
diff --git a/base/src/org/spin/eca62/support/IS3.java b/base/src/org/spin/eca62/support/IS3.java
new file mode 100644
index 0000000000..210571e216
--- /dev/null
+++ b/base/src/org/spin/eca62/support/IS3.java
@@ -0,0 +1,52 @@
+/******************************************************************************
+ * Product: Adempiere ERP & CRM Smart Business Solution *
+ * This program is free software; you can redistribute it and/or modify it *
+ * under the terms version 2 of the GNU General Public License as published *
+ * by the Free Software Foundation. This program is distributed in the hope *
+ * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
+ * See the GNU General Public License for more details. *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
+ * For the text or an alternative of this public license, you may reach us *
+ * Copyright (C) 2003-2015 E.R.P. Consultores y Asociados, C.A. *
+ * All Rights Reserved. *
+ * Contributor(s): Yamel Senih www.erpya.com *
+ *****************************************************************************/
+package org.spin.eca62.support;
+
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * S3 interface, this can help to implement methods like shared URL
+ * @author Yamel Senih, ySenih@erpya.com, ERPCyA http://www.erpya.com
+ * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectOperations.html
+ */
+public interface IS3 {
+
+ /**
+ * Put resource based on metadata (Client, User, container Type, Table, Record ID)
+ * @param resourceMetadata
+ * @param resource
+ * @throws Exception
+ */
+ public String putResource(ResourceMetadata resourceMetadata, InputStream resource) throws Exception;
+
+ /**
+ * Put resource based on metadata (Client, User, container Type, Table, Record ID)
+ * @param resourceMetadata
+ * @return
+ * @throws Exception
+ */
+ public InputStream getResource(ResourceMetadata resourceMetadata) throws Exception;
+
+ /**
+ * Put a Temporary Resource
+ * @param file
+ * @return
+ * @throws Exception
+ */
+ public String putTemporaryFile(File file) throws Exception;
+}
diff --git a/base/src/org/spin/eca62/support/ResourceMetadata.java b/base/src/org/spin/eca62/support/ResourceMetadata.java
new file mode 100644
index 0000000000..17ecbbbeca
--- /dev/null
+++ b/base/src/org/spin/eca62/support/ResourceMetadata.java
@@ -0,0 +1,238 @@
+/******************************************************************************
+ * Product: Adempiere ERP & CRM Smart Business Solution *
+ * This program is free software; you can redistribute it and/or modify it *
+ * under the terms version 2 of the GNU General Public License as published *
+ * by the Free Software Foundation. This program is distributed in the hope *
+ * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
+ * See the GNU General Public License for more details. *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
+ * For the text or an alternative of this public license, you may reach us *
+ * Copyright (C) 2003-2015 E.R.P. Consultores y Asociados, C.A. *
+ * All Rights Reserved. *
+ * Contributor(s): Yamel Senih www.erpya.com *
+ *****************************************************************************/
+package org.spin.eca62.support;
+
+import org.adempiere.exceptions.AdempiereException;
+import org.compiere.model.MClient;
+import org.compiere.model.MRole;
+import org.compiere.model.MUser;
+import org.compiere.util.Env;
+import org.compiere.util.Util;
+
+/**
+ * S3 interface, this can help to implement methods like shared URL
+ * @author Yamel Senih, ySenih@erpya.com, ERPCyA http://www.erpya.com
+ * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectOperations.html
+ */
+public class ResourceMetadata {
+
+ private int clientId;
+ private int userId;
+ private int roleId;
+ private String containerId;
+ private String tableName;
+ private String columnName;
+ private int recordId;
+ private String name;
+ private String resourceName;
+ private ContainerType containerType;
+
+ public enum ContainerType {
+ WINDOW,
+ FORM,
+ PROCESS,
+ REPORT,
+ BROWSER,
+ ATTACHMENT,
+ RESOURCE,
+ APPLICATION,
+ };
+
+ private ResourceMetadata() {
+
+ }
+
+ public static ResourceMetadata newInstance() {
+ return new ResourceMetadata();
+ }
+
+ public int getClientId() {
+ return clientId;
+ }
+
+ public ResourceMetadata withClientId(int clientId) {
+ this.clientId = clientId;
+ return this;
+ }
+
+ public int getUserId() {
+ return userId;
+ }
+
+ public ResourceMetadata withUserId(int userId) {
+ this.userId = userId;
+ return this;
+ }
+
+ public String getContainerId() {
+ return containerId;
+ }
+
+ public ResourceMetadata withContainerId(String containerId) {
+ this.containerId = containerId;
+ return this;
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public ResourceMetadata withTableName(String tableName) {
+ this.tableName = tableName;
+ return this;
+ }
+
+ public int getRecordId() {
+ return recordId;
+ }
+
+ public ResourceMetadata withRecordId(int recordId) {
+ this.recordId = recordId;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public ResourceMetadata withName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public ResourceMetadata withResourceName(String resourceName) {
+ this.resourceName = resourceName;
+ return this;
+ }
+
+ public ContainerType getContainerType() {
+ return containerType;
+ }
+
+ public ResourceMetadata withContainerType(ContainerType containerType) {
+ this.containerType = containerType;
+ return this;
+ }
+
+ public String getColumnName() {
+ return columnName;
+ }
+
+ public ResourceMetadata withColumnName(String columnName) {
+ this.columnName = columnName;
+ return this;
+ }
+
+ public int getRoleId() {
+ return roleId;
+ }
+
+ public ResourceMetadata withRoleId(int roleId) {
+ this.roleId = roleId;
+ return this;
+ }
+
+ public String getResourcePath() {
+ if(clientId <= 0) {
+ throw new AdempiereException("Client ID is Mandatory");
+ }
+ if(containerType == null) {
+ throw new AdempiereException("Container Type is Mandatory");
+ }
+ if(recordId > 0 && Util.isEmpty(tableName)) {
+ throw new AdempiereException("Table Name is Mandatory");
+ }
+ if(recordId <= 0 && !Util.isEmpty(tableName)) {
+ throw new AdempiereException("Record ID is Mandatory");
+ }
+ if(!Util.isEmpty(columnName) && Util.isEmpty(tableName)) {
+ throw new AdempiereException("Table Name is Mandatory");
+ }
+ if(containerType != ContainerType.ATTACHMENT && Util.isEmpty(containerId)) {
+ throw new AdempiereException("Container ID is Mandatory");
+ }
+ if(containerType == ContainerType.ATTACHMENT && recordId <= 0 && Util.isEmpty(tableName)) {
+ throw new AdempiereException("Invalid Container Type (Mandatory Record ID and Table Name)");
+ }
+ String clientUuid = MClient.get(Env.getCtx(), clientId).getUUID();
+ if(Util.isEmpty(clientUuid)) {
+ throw new AdempiereException("Client UUID is Mandatory");
+ }
+ // Create Path
+ StringBuffer path = new StringBuffer(clientUuid).append("/");
+ if(userId > 0) {
+ String userUuid = MUser.get(Env.getCtx(), userId).getUUID();
+ if(Util.isEmpty(userUuid)) {
+ throw new AdempiereException("User UUID is Mandatory");
+ }
+ path.append("user").append("/").append(userUuid).append("/");
+ } else if(roleId > 0) {
+ String roleUuid = MRole.get(Env.getCtx(), roleId).getUUID();
+ if(Util.isEmpty(roleUuid)) {
+ throw new AdempiereException("Role UUID is Mandatory");
+ }
+ path.append("role").append("/").append(roleUuid).append("/");
+ } else {
+ path.append("client").append("/");
+ }
+ // Container Type
+ path.append(containerType.toString());
+ if(!Util.isEmpty(containerId)) {
+ path.append("/").append(containerId);
+ }
+ // Table Name
+ if(!Util.isEmpty(tableName)) {
+ path.append("/").append(getValidPathName(tableName)).append("/").append(recordId);
+ }
+ // Column
+ if(!Util.isEmpty(columnName)) {
+ path.append("/").append(getValidPathName(columnName));
+ }
+ return getValidPathName(path.toString().toLowerCase());
+ }
+
+ public String getResourceFileName() {
+ if(!Util.isEmpty(resourceName)) {
+ return resourceName;
+ }
+ if(Util.isEmpty(name)) {
+ throw new AdempiereException("Resource Name is Mandatory");
+ }
+ return (getResourcePath() + "/" + getValidFileName(name)).toLowerCase();
+ }
+
+ public static String getValidPathName(String path) {
+ if(path == null) {
+ return "";
+ }
+ return path.replaceAll("[^A-Za-z0-9/_-]", "_");
+ }
+
+ public static String getValidFileName(String path) {
+ if(path == null) {
+ return "";
+ }
+ return path.replaceAll("[^A-Za-z0-9._-]", "_");
+ }
+
+ public static String getValidCompleteName(String path) {
+ if(path == null) {
+ return "";
+ }
+ return path.replaceAll("[^A-Za-z0-9/._-]", "_");
+ }
+}
diff --git a/base/src/org/spin/eca62/support/S3.java b/base/src/org/spin/eca62/support/S3.java
new file mode 100644
index 0000000000..470568615f
--- /dev/null
+++ b/base/src/org/spin/eca62/support/S3.java
@@ -0,0 +1,343 @@
+/******************************************************************************
+ * Product: Adempiere ERP & CRM Smart Business Solution *
+ * This program is free software; you can redistribute it and/or modify it *
+ * under the terms version 2 or later of the GNU General Public License *
+ * as published *
+ * by the Free Software Foundation. This program is distributed in the hope *
+ * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
+ * See the GNU General Public License for more details. *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
+ * For the text or an alternative of this public license, you may reach us *
+ * Copyright (C) 2003-2020 E.R.P. Consultores y Asociados, C.A. *
+ * All Rights Reserved. *
+ * Contributor(s): Yamel Senih www.erpya.com *
+ *****************************************************************************/
+package org.spin.eca62.support;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.adempiere.exceptions.AdempiereException;
+import org.compiere.util.Env;
+import org.compiere.util.MimeType;
+import org.compiere.util.Util;
+import org.spin.model.MADAppRegistration;
+import org.spin.util.support.webdav.IWebDav;
+import org.spin.util.support.webdav.IWebDavResource;
+
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.client.builder.AwsClientBuilder;
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.AmazonS3ClientBuilder;
+import com.amazonaws.services.s3.model.CopyObjectRequest;
+import com.amazonaws.services.s3.model.DeleteObjectRequest;
+import com.amazonaws.services.s3.model.GetObjectRequest;
+import com.amazonaws.services.s3.model.ObjectMetadata;
+import com.amazonaws.services.s3.model.PutObjectRequest;
+import com.amazonaws.services.s3.model.S3Object;
+
+/**
+ * This is a implementation of Amazon S3 API for ADempiere
+ * @author Yamel Senih, ySenih@erpya.com, ERPCyA http://www.erpya.com
+ * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectOperations.html
+ */
+public class S3 implements IWebDav, IS3 {
+
+ public S3() {
+ super();
+ }
+
+ /** Registration Id */
+ private int registrationId = 0;
+ /** Bucket Name */
+ private String bucketName = null;
+ public static final String ACCESS_KEY = "ACCESS_KEY";
+ public static final String SECRET_KEY = "SECRET_KEY";
+ public static final String BUCKET_REGION = "BUCKET_REGION";
+ public static final String BUCKET_NAME = "BUCKET_NAME";
+ private String host;
+
+ /**
+ * Get Path from relative path and base path
+ * @param relativePath
+ * @return
+ */
+ private String getPath(String relativePath) {
+ return relativePath;
+ }
+
+ private String getHost() {
+ return host;
+ }
+
+ /**
+ * Load Connection from values
+ */
+ private MADAppRegistration getRegistrationInstance() {
+ if(getAppRegistrationId() <= 0) {
+ throw new AdempiereException("@AD_AppRegistration_ID@ @NotFound@");
+ }
+ MADAppRegistration registration = MADAppRegistration.getById(Env.getCtx(), getAppRegistrationId(), null);
+ // Access Key
+ if(Util.isEmpty(registration.getParameterValue(ACCESS_KEY))) {
+ throw new AdempiereException(ACCESS_KEY + " @NotFound@");
+ }
+ // Secret Key
+ if(Util.isEmpty(registration.getParameterValue(SECRET_KEY))) {
+ throw new AdempiereException(SECRET_KEY + " @NotFound@");
+ }
+ // Bucket End Point
+ if(Util.isEmpty(registration.getHost())) {
+ throw new AdempiereException("@Host@ @NotFound@");
+ }
+ host = registration.getHost();
+ int port = registration.getPort();
+ if(host.endsWith("/")) {
+ host = host.substring(0, host.lastIndexOf("/"));
+ }
+ if(port > 0) {
+ host = host + ":" + port;
+ }
+ // Region Name
+ if(Util.isEmpty(registration.getParameterValue(BUCKET_REGION))) {
+ throw new AdempiereException(BUCKET_REGION + " @NotFound@");
+ }
+ // Bucket Name
+ if(Util.isEmpty(registration.getParameterValue(BUCKET_NAME))) {
+ throw new AdempiereException(BUCKET_NAME + " @NotFound@");
+ }
+ // Set bucket name
+ bucketName = registration.getParameterValue(BUCKET_NAME);
+ // Return registration
+ return registration;
+ }
+
+ /**
+ * Get Bucket Name from configuration
+ * @return
+ */
+ private String getBucketName() {
+ if(Util.isEmpty(bucketName)) {
+ getRegistrationInstance();
+ }
+ return bucketName;
+ }
+
+ /**
+ * Get S3 Instance
+ * @return
+ */
+ private AmazonS3 getS3Instance() {
+ MADAppRegistration registration = getRegistrationInstance();
+ return AmazonS3ClientBuilder
+ .standard()
+ .withCredentials(new AWSStaticCredentialsProvider(
+ new BasicAWSCredentials(
+ registration.getParameterValue(ACCESS_KEY),
+ registration.getParameterValue(SECRET_KEY)
+ )
+ ))
+ .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(
+ getHost(),
+ registration.getParameterValue(BUCKET_REGION)))
+ .build();
+ }
+
+ @Override
+ public void setAppRegistrationId(int registrationId) {
+ this.registrationId = registrationId;
+ }
+
+ @Override
+ public int getAppRegistrationId() {
+ return registrationId;
+ }
+
+ @Override
+ public InputStream getResource(String relativePath) throws Exception {
+ if(Util.isEmpty(relativePath)) {
+ return null;
+ }
+ AmazonS3 s3Client = getS3Instance();
+ // Retrieve object
+ S3Object resource = null;
+ resource = s3Client.getObject(new GetObjectRequest(getBucketName(), getPath(relativePath)));
+ // Prevent NPE
+ if(resource == null) {
+ return null;
+ }
+ return resource.getObjectContent();
+ }
+
+ @Override
+ public void putResource(String relativePath, InputStream resource) throws Exception {
+ if(Util.isEmpty(relativePath)
+ || resource == null) {
+ return;
+ }
+ if(Util.isEmpty(relativePath)
+ || resource == null) {
+ return;
+ }
+ AmazonS3 s3Client = getS3Instance();
+ // Set metadata
+ ObjectMetadata metadata = new ObjectMetadata();
+ metadata.setContentLength(resource.available());
+ String fileName = ResourceMetadata.getValidCompleteName(relativePath);
+ metadata.setContentType(MimeType.getMimeType(fileName));
+ // Put object for bucket
+ s3Client.putObject(getBucketName(), fileName, resource, metadata);
+ }
+
+ @Override
+ public void deleteResource(String relativePath) throws Exception {
+ if(Util.isEmpty(relativePath)) {
+ return;
+ }
+ AmazonS3 s3Client = getS3Instance();
+ // Delete object
+ s3Client.deleteObject(new DeleteObjectRequest(getBucketName(), getPath(relativePath)));
+ }
+
+ @Override
+ public void createDirectory(String relativePathName) throws Exception {
+ if(Util.isEmpty(relativePathName)) {
+ return;
+ }
+ AmazonS3 s3Client = getS3Instance();
+ ObjectMetadata metadata = new ObjectMetadata();
+ metadata.setContentLength(0);
+ //
+ InputStream emptyContent = new ByteArrayInputStream(new byte[0]);
+ PutObjectRequest putObjectRequest = new PutObjectRequest(getBucketName(), getPath(relativePathName), emptyContent, metadata);
+ // Create
+ s3Client.putObject(putObjectRequest);
+ }
+
+ @Override
+ public void moveResource(String relativeSourcePath, String relativeTargetPath) throws Exception {
+ if(Util.isEmpty(relativeSourcePath)
+ || Util.isEmpty(relativeTargetPath)) {
+ return;
+ }
+ copyResource(relativeSourcePath, relativeTargetPath);
+ deleteResource(relativeSourcePath);
+ }
+
+ @Override
+ public void copyResource(String relativeSourcePath, String relativeTargetPath) throws Exception {
+ if(Util.isEmpty(relativeSourcePath)
+ || Util.isEmpty(relativeTargetPath)) {
+ return;
+ }
+ AmazonS3 s3Client = getS3Instance();
+ CopyObjectRequest copyObjRequest = new CopyObjectRequest(getBucketName(), getPath(relativeSourcePath), getBucketName(), getPath(relativeTargetPath));
+ s3Client.copyObject(copyObjRequest);
+ }
+
+ @Override
+ public boolean exists(String relativePath) throws Exception {
+ if(Util.isEmpty(relativePath)) {
+ return false;
+ }
+ // Verify if exist a object
+ return getS3Instance().doesObjectExist(getBucketName(), getPath(relativePath));
+ }
+
+ @Override
+ public List getResourceList(String relativePath) throws Exception {
+ AmazonS3 s3Client = getS3Instance();
+ List resources = new ArrayList<>();
+ s3Client
+ .listObjects(getBucketName(), getPath(relativePath))
+ .getObjectSummaries()
+ .forEach(resource -> resources.add(new S3Resource(resource)));
+ return resources;
+ }
+
+ @Override
+ public void putResource(String relativePath, byte[] resource) throws Exception {
+ if(Util.isEmpty(relativePath)
+ || resource == null) {
+ return;
+ }
+ AmazonS3 s3Client = getS3Instance();
+ InputStream inputStream = new ByteArrayInputStream(resource);
+ // Set metadata
+ ObjectMetadata metadata = new ObjectMetadata();
+ metadata.setContentLength(resource.length);
+ String fileName = getPath(relativePath);
+ metadata.setContentType(MimeType.getMimeType(fileName));
+ // Put object for bucket
+ s3Client.putObject(getBucketName(), fileName, inputStream, metadata);
+ }
+
+ @Override
+ public String testConnection() {
+ // Save file
+ StringBuffer message = new StringBuffer();
+ try {
+ List resources = getResourceList(null);
+ for(IWebDavResource resource : resources) {
+ if(message.length() > 0) {
+ message.append(Env.NL);
+ }
+ message.append(resource);
+ }
+ } catch (Exception e) {
+ message.append(e.getLocalizedMessage());
+ }
+ return message.toString();
+ }
+
+ /**
+ * Put a Temporary resource to S3
+ * @param file
+ * @return
+ */
+ @Override
+ public String putTemporaryFile(File file) throws Exception {
+ ResourceMetadata resourceMetadata = ResourceMetadata.newInstance()
+ .withClientId(Env.getAD_Client_ID(Env.getCtx()))
+ .withUserId(Env.getAD_User_ID(Env.getCtx()))
+ .withContainerType(ResourceMetadata.ContainerType.RESOURCE)
+ .withContainerId("tmp")
+ .withName(file.getName())
+ ;
+ return putResource(resourceMetadata, new FileInputStream(file));
+ }
+
+ @Override
+ public String putResource(ResourceMetadata resourceMetadata, InputStream resource) throws Exception {
+ AmazonS3 s3Client = getS3Instance();
+ // Set metadata
+ ObjectMetadata metadata = new ObjectMetadata();
+ metadata.setContentLength(resource.available());
+ String fileName = resourceMetadata.getResourceFileName();
+ metadata.setContentType(MimeType.getMimeType(fileName));
+ // Put object for bucket
+ s3Client.putObject(getBucketName(), resourceMetadata.getResourceFileName(), resource, metadata);
+ return fileName;
+ }
+
+ @Override
+ public InputStream getResource(ResourceMetadata resourceMetadata) throws Exception {
+ AmazonS3 s3Client = getS3Instance();
+ // Retrieve object
+ S3Object resource = null;
+ resource = s3Client.getObject(new GetObjectRequest(getBucketName(), resourceMetadata.getResourceFileName()));
+ // Prevent NPE
+ if(resource == null) {
+ return null;
+ }
+ return resource.getObjectContent();
+ }
+}
diff --git a/base/src/org/spin/eca62/support/S3Resource.java b/base/src/org/spin/eca62/support/S3Resource.java
new file mode 100644
index 0000000000..3dec1140f0
--- /dev/null
+++ b/base/src/org/spin/eca62/support/S3Resource.java
@@ -0,0 +1,81 @@
+/******************************************************************************
+ * Product: Adempiere ERP & CRM Smart Business Solution *
+ * This program is free software; you can redistribute it and/or modify it *
+ * under the terms version 2 of the GNU General Public License as published *
+ * by the Free Software Foundation. This program is distributed in the hope *
+ * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
+ * See the GNU General Public License for more details. *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
+ * For the text or an alternative of this public license, you may reach us *
+ * Copyright (C) 2003-2015 E.R.P. Consultores y Asociados, C.A. *
+ * All Rights Reserved. *
+ * Contributor(s): Yamel Senih www.erpya.com *
+ *****************************************************************************/
+package org.spin.eca62.support;
+
+import java.sql.Timestamp;
+import java.util.Date;
+
+import org.adempiere.exceptions.AdempiereException;
+import org.spin.util.support.webdav.IWebDavResource;
+
+import com.amazonaws.services.s3.model.S3ObjectSummary;
+
+/**
+ * Resource wrapper for Amazon S3 API
+ * @author Yamel Senih, ySenih@erpya.com, ERPCyA http://www.erpya.com
+ * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/ObjectOperations.html
+ */
+public class S3Resource implements IWebDavResource {
+
+ public S3Resource(S3ObjectSummary resource) {
+ if(resource == null) {
+ throw new AdempiereException("@Resource@ @NotFound@");
+ }
+ this.resource = resource;
+ }
+
+ /** Resource */
+ private S3ObjectSummary resource;
+
+ @Override
+ public String getDisplayName() {
+ return resource.getKey();
+ }
+
+ @Override
+ public String getName() {
+ return resource.getKey();
+ }
+
+ @Override
+ public String getPath() {
+ return resource.getKey();
+ }
+
+ @Override
+ public Timestamp getCreated() {
+ Date created = resource.getLastModified();
+ if(created == null) {
+ return null;
+ }
+ return new Timestamp(created.getTime());
+ }
+
+ @Override
+ public Timestamp getUpdated() {
+ Date created = resource.getLastModified();
+ if(created == null) {
+ return null;
+ }
+ return new Timestamp(created.getTime());
+ }
+
+ @Override
+ public String toString() {
+ return resource.toString();
+ }
+}
diff --git a/base/src/org/spin/eca62/util/S3Manager.java b/base/src/org/spin/eca62/util/S3Manager.java
new file mode 100644
index 0000000000..cb40b8ee33
--- /dev/null
+++ b/base/src/org/spin/eca62/util/S3Manager.java
@@ -0,0 +1,76 @@
+/******************************************************************************
+ * Product: Adempiere ERP & CRM Smart Business Solution *
+ * This program is free software; you can redistribute it and/or modify it *
+ * under the terms version 2 or later of the *
+ * GNU General Public License as published *
+ * by the Free Software Foundation. This program is distributed in the hope *
+ * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
+ * See the GNU General Public License for more details. *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
+ * For the text or an alternative of this public license, you may reach us *
+ * Copyright (C) 2003-2019 E.R.P. Consultores y Asociados, C.A. *
+ * All Rights Reserved. *
+ * Contributor(s): Yamel Senih www.erpya.com *
+ *****************************************************************************/
+package org.spin.eca62.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+
+import org.adempiere.exceptions.AdempiereException;
+import org.compiere.model.MClientInfo;
+import org.compiere.util.Env;
+import org.spin.eca62.support.IS3;
+import org.spin.eca62.support.ResourceMetadata;
+import org.spin.model.MADAppRegistration;
+import org.spin.util.support.AppSupportHandler;
+import org.spin.util.support.IAppSupport;
+
+/**
+ * Handle S3 Util Manager
+ * @author Yamel Senih, ysenih@erpya.com, ERPCyA http://www.erpya.com
+ */
+public class S3Manager {
+ /**
+ * Put a Temporary resource to S3
+ * @param file
+ * @return
+ */
+ public static String putTemporaryFile(File file) throws Exception {
+ try {
+ // Push to S3
+ MClientInfo clientInfo = MClientInfo.get(Env.getCtx());
+ if(clientInfo.getFileHandler_ID() <= 0) {
+ throw new AdempiereException("@FileHandler_ID@ @NotFound@");
+ }
+ MADAppRegistration genericConnector = MADAppRegistration.getById(Env.getCtx(), clientInfo.getFileHandler_ID(), null);
+ if(genericConnector == null) {
+ throw new AdempiereException("@AD_AppRegistration_ID@ @NotFound@");
+ }
+ // Load
+ IAppSupport supportedApi = AppSupportHandler.getInstance().getAppSupport(genericConnector);
+ if(supportedApi == null) {
+ throw new AdempiereException("@AD_AppSupport_ID@ @NotFound@");
+ }
+ if(!IS3.class.isAssignableFrom(supportedApi.getClass())) {
+ throw new AdempiereException("@AD_AppSupport_ID@ @Unsupported@");
+ }
+ // Push it
+ IS3 fileHandler = (IS3) supportedApi;
+ ResourceMetadata resourceMetadata = ResourceMetadata.newInstance()
+ .withClientId(Env.getAD_Client_ID(Env.getCtx()))
+ .withUserId(Env.getAD_User_ID(Env.getCtx()))
+ .withContainerType(ResourceMetadata.ContainerType.RESOURCE)
+ .withContainerId("tmp")
+ .withName(file.getName())
+ ;
+ String fileName = fileHandler.putResource(resourceMetadata, new FileInputStream(file));
+ return fileName;
+ } catch (Exception e) {
+ throw new AdempiereException(e);
+ }
+ }
+}
diff --git a/base/src/org/spin/util/AttachmentUtil.java b/base/src/org/spin/util/AttachmentUtil.java
index 9071276453..cee9f1cd00 100644
--- a/base/src/org/spin/util/AttachmentUtil.java
+++ b/base/src/org/spin/util/AttachmentUtil.java
@@ -22,10 +22,16 @@
import java.util.List;
import java.util.Properties;
+import org.adempiere.core.domains.models.I_AD_Image;
import org.adempiere.exceptions.AdempiereException;
+import org.compiere.model.MArchive;
+import org.compiere.model.MAttachment;
import org.compiere.model.MClientInfo;
+import org.compiere.model.MTable;
import org.compiere.util.Env;
import org.compiere.util.Util;
+import org.spin.eca62.support.IS3;
+import org.spin.eca62.support.ResourceMetadata;
import org.spin.model.MADAppRegistration;
import org.spin.model.MADAttachmentReference;
import org.spin.util.support.AppSupportHandler;
@@ -338,7 +344,7 @@ public byte[] getAttachment() throws Exception {
description = attachmentReference.getDescription();
note = attachmentReference.getTextMsg();
// Get data
- InputStream inputStream = handler.getResource(getCompleteFileName(attachmentReference));
+ InputStream inputStream = handler.getResource(getCompleteFileName(attachmentReference, handler));
if(inputStream == null) {
throw new AdempiereException("@FileName@ @NotFound@");
}
@@ -440,20 +446,7 @@ public void saveAttachment() throws Exception {
// Save
attachmentReference.saveEx();
// Save
- String validForlder = getValidFolder(attachmentReference);
- if(!Util.isEmpty(baseFolder)) {
- // Validate if exist
- if(!handler.exists(baseFolder)) {
- handler.createDirectory(baseFolder);
- }
- }
- if(!Util.isEmpty(validForlder)) {
- // Validate if exist
- if(!handler.exists(validForlder)) {
- handler.createDirectory(validForlder);
- }
- }
- handler.putResource(getCompleteFileName(attachmentReference), data);
+ handler.putResource(getCompleteFileName(attachmentReference, handler), data);
} catch (Exception e) {
if(attachmentReference.getAD_AttachmentReference_ID() > 0) {
attachmentReference.deleteEx(true);
@@ -475,7 +468,7 @@ public void deleteAttachment() throws Exception {
}
try {
// Save
- handler.deleteResource(getCompleteFileName(attachmentReference));
+ handler.deleteResource(getCompleteFileName(attachmentReference, handler));
// Remove from cache
MADAttachmentReference.resetAttachmentReferenceCache(fileHandlerId, attachmentReference);
// Delete reference
@@ -490,7 +483,54 @@ public void deleteAttachment() throws Exception {
* @param attachmentReference
* @return
*/
- private String getCompleteFileName(MADAttachmentReference attachmentReference) {
+ private String getCompleteFileName(MADAttachmentReference attachmentReference, IWebDav handler) {
+ if(IS3.class.isAssignableFrom(handler.getClass())) {
+ if(attachmentReference.getAD_Attachment_ID() > 0) {
+ MAttachment attachment = new MAttachment(context, attachmentReference.getAD_Attachment_ID(), attachmentReference.get_TrxName());
+ String tableName = MTable.getTableName(context, attachment.getAD_Table_ID());
+ return ResourceMetadata.newInstance()
+ .withClientId(attachmentReference.getAD_Client_ID())
+ .withContainerType(ResourceMetadata.ContainerType.ATTACHMENT)
+ .withTableName(tableName)
+ .withRecordId(attachment.getRecord_ID())
+ .withName(attachmentReference.getFileName())
+ .getResourceFileName()
+ ;
+ } else if(attachmentReference.getAD_Image_ID() > 0) {
+ return ResourceMetadata.newInstance()
+ .withClientId(attachmentReference.getAD_Client_ID())
+ .withContainerType(ResourceMetadata.ContainerType.RESOURCE)
+ .withContainerId("image")
+ .withTableName(I_AD_Image.Table_Name)
+ .withRecordId(attachmentReference.getAD_Image_ID())
+ .withName(attachmentReference.getFileName())
+ .getResourceFileName()
+ ;
+ } else if(attachmentReference.getAD_Archive_ID() > 0) {
+ MArchive archive = new MArchive(context, attachmentReference.getAD_Archive_ID(), attachmentReference.get_TrxName());
+ String tableName = MTable.getTableName(context, archive.getAD_Table_ID());
+ return ResourceMetadata.newInstance()
+ .withClientId(attachmentReference.getAD_Client_ID())
+ .withContainerType(ResourceMetadata.ContainerType.RESOURCE)
+ .withContainerId("archive")
+ .withTableName(tableName)
+ .withRecordId(archive.getRecord_ID())
+ .withName(attachmentReference.getFileName())
+ .getResourceFileName()
+ ;
+ }
+ } else {
+ return getCompleteFileNameOld(attachmentReference);
+ }
+ return null;
+ }
+
+ /**
+ * Get complete path from attachment reference
+ * @param attachmentReference
+ * @return
+ */
+ private String getCompleteFileNameOld(MADAttachmentReference attachmentReference) {
String fileName = attachmentReference.getValidFileName();
String validForlder = getValidFolder(attachmentReference);
String completePath = fileName;
diff --git a/build.gradle b/build.gradle
index 71b5b06c11..78d154917b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -43,6 +43,12 @@ dependencies {
api project(':telegram')
api project(':jasper_reports')
api project(':store')
+ api project(':processors')
+ api project(':dashboard')
+ api project(':jwt')
+ api project(':kafka')
+ api project(':pos')
+ api project(':withholding_engine')
}
def entityType = 'D'
diff --git a/dashboard/build.gradle b/dashboard/build.gradle
new file mode 100644
index 0000000000..757b120d73
--- /dev/null
+++ b/dashboard/build.gradle
@@ -0,0 +1,79 @@
+plugins {
+ id 'java-library'
+ id 'maven-publish'
+ id 'signing'
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+dependencies {
+ // Local
+ api project(path: ':base')
+}
+
+sourceSets {
+ main {
+ java {
+ srcDirs = ['src/main/java/base']
+ }
+ }
+}
+
+def entityType = 'ECA50'
+
+jar {
+ manifest {
+ attributes(
+ "Implementation-Title": "Dashboard",
+ "Implementation-Version": publishVersion,
+ "EntityType": entityType
+ )
+ }
+}
+
+publishing {
+ repositories {
+ mavenLocal()
+ maven {
+ url = publishLibraryRepo
+ credentials {
+ username = publishUsername
+ password = publishPassword
+ }
+ }
+ }
+ publications {
+ mavenJava(MavenPublication) {
+ groupId publishGroupId
+ artifactId 'dashboard'
+ version publishVersion
+ from components.java
+ pom {
+ name = 'Dashboard'
+ description = 'This project allows improve the dashboard use for ADempiere for new UI'
+ url = 'http://adempiere.io/'
+ licenses {
+ license {
+ name = 'GNU General Public License, version 2'
+ url = 'https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt'
+ }
+ }
+ developers {
+ developer {
+ id = 'yamelsenih'
+ name = 'Yamel Senih'
+ email = 'ysenih@erpya.com'
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/dashboard/src/main/java/base/org/spin/eca50/controller/ChartBuilder.java b/dashboard/src/main/java/base/org/spin/eca50/controller/ChartBuilder.java
new file mode 100644
index 0000000000..8835c4db98
--- /dev/null
+++ b/dashboard/src/main/java/base/org/spin/eca50/controller/ChartBuilder.java
@@ -0,0 +1,289 @@
+/*************************************************************************************
+ * Product: Adempiere ERP & CRM Smart Business Solution *
+ * This program is free software; you can redistribute it and/or modify it *
+ * under the terms version 2 or later of the GNU General Public License as published *
+ * by the Free Software Foundation. This program is distributed in the hope *
+ * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
+ * See the GNU General Public License for more details. *
+ * You should have received a copy of the GNU General Public License along *
+ * with this program; if not, write to the Free Software Foundation, Inc., *
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
+ * For the text or an alternative of this public license, you may reach us *
+ * Copyright (C) 2012-2023 E.R.P. Consultores y Asociados, S.A. All Rights Reserved. *
+ * Contributor(s): Yamel Senih www.erpya.com *
+ *************************************************************************************/
+package org.spin.eca50.controller;
+
+import java.math.BigDecimal;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.adempiere.exceptions.AdempiereException;
+import org.compiere.model.MChart;
+import org.compiere.model.MChartDatasource;
+import org.compiere.model.MRole;
+import org.compiere.model.Query;
+import org.compiere.util.DB;
+import org.compiere.util.Env;
+import org.compiere.util.TimeUtil;
+import org.compiere.util.Util;
+import org.spin.eca50.data.ChartDataValue;
+import org.spin.eca50.data.ChartSeriesValue;
+import org.spin.eca50.data.ChartValue;
+import org.spin.eca50.util.ChartQueryDefinition;
+
+/**
+ * A class as controller for Chart Service
+ * @author Yamel Senih at www.erpya.com
+ *
+ */
+public class ChartBuilder {
+
+ /**
+ * Get Data Source Query
+ * @param chartDatasourceId
+ * @param isTimeSeries
+ * @param timeUnit
+ * @param timeScope
+ * @param customParameters
+ * @return
+ */
+ private static ChartQueryDefinition getDataSourceQuery(int chartDatasourceId, boolean isTimeSeries, String timeUnit, int timeScope, Map customParameters) {
+ if(chartDatasourceId <= 0) {
+ throw new AdempiereException("@FillMandatory@ @AD_ChartDatasource_ID@");
+ }
+ MChartDatasource datasource = new MChartDatasource(Env.getCtx(), chartDatasourceId, null);
+ if (datasource == null || datasource.getAD_ChartDatasource_ID() <= 0) {
+ throw new AdempiereException("@AD_ChartDatasource_ID@ @NotFound@");
+ }
+
+ String value = datasource.getValueColumn();
+ String dateColumn = datasource.getDateColumn();
+ String seriesColumn = datasource.getSeriesColumn();
+ String name = datasource.getName();
+ String where = datasource.getWhereClause();
+ String fromClause = datasource.getFromClause();
+ String category;
+ String unit = "D";
+ if (!isTimeSeries) {
+ category = datasource.getCategoryColumn();
+ } else {
+ if (timeUnit.equals(MChart.TIMEUNIT_Week)) {
+ unit = "W";
+ } else if (timeUnit.equals(MChart.TIMEUNIT_Month)) {
+ unit = "MM";
+ } else if (timeUnit.equals(MChart.TIMEUNIT_Quarter)) {
+ unit = "Q";
+ } else if (timeUnit.equals(MChart.TIMEUNIT_Year)) {
+ unit = "Y";
+ }
+ category = " TRUNC(" + dateColumn + ", '" + unit + "') ";
+ }
+
+ String series = DB.TO_STRING(name);
+ boolean hasSeries = false;
+ if (seriesColumn != null) {
+ series = seriesColumn;
+ hasSeries = true;
+ }
+
+ List