IDEMPIERE-2981 - Implement JSON field type (#2255)

* IDEMPIERE-2981 - Implement JSON Field Type
* IDEMPIERE-2981 - Added field to the Test Window and Unit Tests
* IDEMPIERE-2981 - Fixed migration scripts
* IDEMPIERE-2981 - Changed oracle json syntax
* IDEMPIERE-2981 - Increased the number of lines of jsonData test field
* IDEMPIERE-2981 - Validate and Prettify JSON string on the field editor
* IDEMPIERE-2981 - Support for oracle
* IDEMPIERE-2981 - Applied patch from Carlos Ruiz
This commit is contained in:
Diego Ruiz 2024-03-07 16:10:43 +01:00 committed by Carlos Ruiz
parent a45283b855
commit 5f44c9c19d
25 changed files with 360 additions and 21 deletions

View File

@ -0,0 +1,10 @@
-- IDEMPIERE-2981 - Implement JSON Field type
SELECT register_migration_script('202402261300_IDEMPIERE-2981.sql') FROM dual;
SET SQLBLANKLINES ON
SET DEFINE OFF
-- Feb 26, 2024, 1:00:29 PM CET
INSERT INTO AD_Reference (AD_Reference_ID,Name,Description,ValidationType,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,EntityType,IsOrderByValue,AD_Reference_UU,ShowInactive) VALUES (200267,'JSON','JSON format values','D',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,'D','N','b6fcc751-edd8-4421-acd0-3cde02a9576d','N')
;

View File

@ -0,0 +1,30 @@
-- IDEMPIERE-2981 - Implement JSON Field type
SELECT register_migration_script('202402261354_IDEMPIERE-2981.sql') FROM dual;
SET SQLBLANKLINES ON
SET DEFINE OFF
-- Feb 26, 2024, 1:54:35 PM CET
INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203924,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,'JsonData','JSON Data','The json field stores json data.','JSON Data','D','c4ea7a81-96a9-4a5d-bb87-e913e1c8ed48')
;
-- Feb 26, 2024, 1:55:37 PM CET
INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216570,0,'JSON Data','The json field stores json data.',135,'JsonData',0,'N','N','N','N','N',0,'N',200267,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,203924,'Y','N','D','N','N','N','Y','927b83df-d161-4332-ad44-8ffed99e8cf4','Y',0,'N','N','N','N')
;
-- Feb 28, 2024, 5:41:55 PM CET
ALTER TABLE Test ADD JsonData CLOB DEFAULT NULL CONSTRAINT test_jsondata_ij CHECK (JsonData IS JSON)
;
-- Feb 26, 2024, 1:56:08 PM CET
INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,ColumnSpan) VALUES (208472,'JSON Data','The json field stores json data.',152,216570,'Y',100,310,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','e47ef529-71ba-4f9b-9014-b188e17e8ef4','Y',290,5)
;
-- Feb 29, 2024, 1:52:50 PM CET
UPDATE AD_Field SET NumLines=5,Updated=TO_TIMESTAMP('2024-02-29 13:52:50','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208472
;
-- Feb 29, 2024, 2:07:30 PM CET
INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Invalid JSON',0,0,'Y',TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,200876,'InvalidJSON','D','a263376f-a12e-4943-92f1-7d7ce8a67a2b')
;

View File

@ -0,0 +1,7 @@
-- IDEMPIERE-2981 - Implement JSON Field type
SELECT register_migration_script('202402261300_IDEMPIERE-2981.sql') FROM dual;
-- Feb 26, 2024, 1:00:29 PM CET
INSERT INTO AD_Reference (AD_Reference_ID,Name,Description,ValidationType,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,EntityType,IsOrderByValue,AD_Reference_UU,ShowInactive) VALUES (200267,'JSON','JSON format values','D',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:00:28','YYYY-MM-DD HH24:MI:SS'),100,'D','N','b6fcc751-edd8-4421-acd0-3cde02a9576d','N')
;

View File

@ -0,0 +1,27 @@
-- IDEMPIERE-2981 - Implement JSON Field type
SELECT register_migration_script('202402261354_IDEMPIERE-2981.sql') FROM dual;
-- Feb 26, 2024, 1:54:35 PM CET
INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203924,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:54:35','YYYY-MM-DD HH24:MI:SS'),100,'JsonData','JSON Data','The json field stores json data.','JSON Data','D','c4ea7a81-96a9-4a5d-bb87-e913e1c8ed48')
;
-- Feb 26, 2024, 1:55:37 PM CET
INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216570,0,'JSON Data','The json field stores json data.',135,'JsonData',0,'N','N','N','N','N',0,'N',200267,0,0,'Y',TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:55:36','YYYY-MM-DD HH24:MI:SS'),100,203924,'Y','N','D','N','N','N','Y','927b83df-d161-4332-ad44-8ffed99e8cf4','Y',0,'N','N','N','N')
;
-- Feb 26, 2024, 1:55:55 PM CET
ALTER TABLE Test ADD COLUMN JsonData JSON DEFAULT NULL
;
-- Feb 26, 2024, 1:56:08 PM CET
INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,ColumnSpan) VALUES (208472,'JSON Data','The json field stores json data.',152,216570,'Y',100,310,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-26 13:56:08','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','e47ef529-71ba-4f9b-9014-b188e17e8ef4','Y',290,5)
;
-- Feb 29, 2024, 1:52:50 PM CET
UPDATE AD_Field SET NumLines=5,Updated=TO_TIMESTAMP('2024-02-29 13:52:50','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208472
;
-- Feb 29, 2024, 2:07:30 PM CET
INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Invalid JSON',0,0,'Y',TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2024-02-29 14:07:30','YYYY-MM-DD HH24:MI:SS'),100,200876,'InvalidJSON','D','a263376f-a12e-4943-92f1-7d7ce8a67a2b')
;

View File

@ -167,7 +167,7 @@ public class TabCreateFields extends SvrProcess
}
if (column.getAD_Reference_ID() == DisplayType.Text) {
field.setNumLines(3);
} else if (column.getAD_Reference_ID() == DisplayType.TextLong) {
} else if (column.getAD_Reference_ID() == DisplayType.TextLong || column.getAD_Reference_ID() == DisplayType.JSON) {
field.setNumLines(5);
} else if (column.getAD_Reference_ID() == DisplayType.Memo) {
field.setNumLines(8);

View File

@ -1421,7 +1421,7 @@ public class GridTabCSVImporter implements IGridTabImporter
return (new Optional(new ParseBigDecimal(new DecimalFormatSymbols(Language.getLoginLanguage().getLocale()))));
} else if (DisplayType.YesNo == field.getDisplayType()) {
return (new Optional(new ParseBool("y", "n")));
} else if (DisplayType.TextLong == field.getDisplayType()) {
} else if (DisplayType.TextLong == field.getDisplayType() || DisplayType.JSON == field.getDisplayType()) {
return (new Optional(new StrMinMax(1, Long.MAX_VALUE)));
} else if (DisplayType.isText(field.getDisplayType())) {
return (new Optional(new StrMinMax(1, field.getFieldLength())));

View File

@ -202,7 +202,18 @@ public interface AdempiereDatabase
*/
public String TO_NUMBER (BigDecimal number, int displayType);
/**
* Return string as JSON object for INSERT statements
* @param value
* @return value as JSON
*/
public String TO_JSON (String value);
/**
* @return string with right casting for JSON inserts
*/
public String getJSONCast ();
/**
* Get next sequence number in this Sequence
* @param Name Sequence name
@ -424,6 +435,11 @@ public interface AdempiereDatabase
*/
public String getClobDataType();
/**
* @return json object data type name
*/
public String getJsonDataType();
/**
* @return time stamp data type name
*/

View File

@ -2106,6 +2106,7 @@ public class GridField
if (m_vo.displayType == DisplayType.Text
|| m_vo.displayType == DisplayType.Memo
|| m_vo.displayType == DisplayType.TextLong
|| m_vo.displayType == DisplayType.JSON
|| m_vo.displayType == DisplayType.Binary
|| m_vo.displayType == DisplayType.RowID
|| isEncrypted())

View File

@ -22,7 +22,7 @@ import org.compiere.util.KeyNamePair;
/** Generated Interface for Test
* @author iDempiere (generated)
* @version Release 11
* @version Release 12
*/
public interface I_Test
{
@ -55,8 +55,8 @@ public interface I_Test
/** Column name AD_Client_ID */
public static final String COLUMNNAME_AD_Client_ID = "AD_Client_ID";
/** Get Tenant.
* Tenant for this installation.
/** Get Client.
* Client/Tenant for this installation.
*/
public int getAD_Client_ID();
@ -64,12 +64,12 @@ public interface I_Test
public static final String COLUMNNAME_AD_Org_ID = "AD_Org_ID";
/** Set Organization.
* Organizational entity within tenant
* Organizational entity within client
*/
public void setAD_Org_ID (int AD_Org_ID);
/** Get Organization.
* Organizational entity within tenant
* Organizational entity within client
*/
public int getAD_Org_ID();
@ -253,6 +253,19 @@ public interface I_Test
*/
public boolean isActive();
/** Column name JsonData */
public static final String COLUMNNAME_JsonData = "JsonData";
/** Set JSON Data.
* The json field stores json data.
*/
public void setJsonData (Object JsonData);
/** Get JSON Data.
* The json field stores json data.
*/
public Object getJsonData();
/** Column name M_Locator_ID */
public static final String COLUMNNAME_M_Locator_ID = "M_Locator_ID";

View File

@ -380,7 +380,7 @@ public class MColumn extends X_AD_Column implements ImmutablePOSupport
}
int displayType = getAD_Reference_ID();
if (DisplayType.isLOB(displayType)) // LOBs are 0
if (DisplayType.isLOB(displayType) || displayType == DisplayType.JSON) // LOBs are 0
{
if (getFieldLength() != 0)
setFieldLength(0);

View File

@ -115,7 +115,7 @@ public abstract class PO
/**
*
*/
private static final long serialVersionUID = -7758079724744033518L;
private static final long serialVersionUID = 6591172659109078284L;
/* String key to create a new record based in UUID constructor */
public static final String UUID_NEW_RECORD = "";
@ -3046,6 +3046,8 @@ public abstract class PO
{
if (value instanceof Timestamp && dt == DisplayType.Date)
sql.append("trunc(cast(? as date))");
else if (dt == DisplayType.JSON)
sql.append(DB.getJSONCast());
else
sql.append("?");
@ -3071,7 +3073,7 @@ public abstract class PO
} else {
params.add(encrypt(i,value));
}
}
}
else
{
params.add(value);
@ -3660,6 +3662,8 @@ public abstract class PO
{
if (value instanceof Timestamp && dt == DisplayType.Date)
sqlValues.append("trunc(cast(? as date))");
else if (dt == DisplayType.JSON)
sqlValues.append(DB.getJSONCast());
else
sqlValues.append("?");

View File

@ -154,7 +154,7 @@ public class PO_LOB implements Serializable
try
{
pstmt = con.prepareStatement(sql.toString());
if (m_displayType == DisplayType.TextLong)
if (m_displayType == DisplayType.TextLong || m_displayType == DisplayType.JSON)
pstmt.setString(1, (String)m_value);
else
pstmt.setBytes(1, (byte[])m_value);

View File

@ -164,6 +164,7 @@ public class SystemIDs
public final static int REFERENCE_DATATYPE_TABLEDIR_UU = 200234;
public final static int REFERENCE_DATATYPE_TEXT = 14;
public final static int REFERENCE_DATATYPE_TEXTLONG = 36;
public final static int REFERENCE_DATATYPE_JSON = 200267;
public final static int REFERENCE_DATATYPE_TIME = 24;
public final static int REFERENCE_DATATYPE_TIMESTAMP_WITH_TIMEZONE = 200133;
public final static int REFERENCE_DATATYPE_TIMEZONE = 200135;

View File

@ -26,7 +26,7 @@ import org.compiere.util.KeyNamePair;
/** Generated Model for Test
* @author iDempiere (generated)
* @version Release 11 - $Id$ */
* @version Release 12 - $Id$ */
@org.adempiere.base.Model(table="Test")
public class X_Test extends PO implements I_Test, I_Persistent
{
@ -34,7 +34,7 @@ public class X_Test extends PO implements I_Test, I_Persistent
/**
*
*/
private static final long serialVersionUID = 20231222L;
private static final long serialVersionUID = 20240226L;
/** Standard Constructor */
public X_Test (Properties ctx, int Test_ID, String trxName)
@ -382,6 +382,22 @@ public class X_Test extends PO implements I_Test, I_Persistent
return (String)get_Value(COLUMNNAME_Help);
}
/** Set JSON Data.
@param JsonData The json field stores json data.
*/
public void setJsonData (Object JsonData)
{
set_Value (COLUMNNAME_JsonData, JsonData);
}
/** Get JSON Data.
@return The json field stores json data.
*/
public Object getJsonData()
{
return get_Value(COLUMNNAME_JsonData);
}
public I_M_Locator getM_Locator() throws RuntimeException
{
return (I_M_Locator)MTable.get(getCtx(), I_M_Locator.Table_ID)

View File

@ -1140,7 +1140,7 @@ public class DataEngine
pde = new PrintDataElement(pdc.getAD_PrintFormatItem_ID(), pdc.getColumnName(), Boolean.valueOf(b), pdc.getDisplayType(), pdc.getFormatPattern());
}
}
else if (pdc.getDisplayType() == DisplayType.TextLong)
else if (pdc.getDisplayType() == DisplayType.TextLong || (pdc.getDisplayType() == DisplayType.JSON && DB.isOracle()))
{
String value = "";
if ("java.lang.String".equals(rs.getMetaData().getColumnClassName(counter)))

View File

@ -2208,6 +2208,24 @@ public final class DB
//
return out.toString();
} // TO_STRING
/**
* Return string as JSON object for INSERT statements with correct precision
* @param value
* @return value as json
*/
public static String TO_JSON (String value)
{
return s_cc.getDatabase().TO_JSON(value);
}
/**
* @return string with right casting for JSON inserts
*/
public static String getJSONCast()
{
return s_cc.getDatabase().getJSONCast();
}
/**
* Convenient method to close result set

View File

@ -66,6 +66,7 @@ import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_TIMEZONE;
import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_URL;
import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_UUID;
import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_YES_NO;
import static org.compiere.model.SystemIDs.REFERENCE_DATATYPE_JSON;
import java.text.DateFormat;
import java.text.DecimalFormat;
@ -196,8 +197,9 @@ public final class DisplayType
public static final int RecordID = REFERENCE_DATATYPE_RECORD_ID;
public static final int RecordUU = REFERENCE_DATATYPE_RECORD_UU;
public static final int JSON = REFERENCE_DATATYPE_JSON;
public static final int TimestampWithTimeZone = REFERENCE_DATATYPE_TIMESTAMP_WITH_TIMEZONE;
public static final int TimeZoneId = REFERENCE_DATATYPE_TIMEZONE;
@ -409,7 +411,7 @@ public final class DisplayType
public static boolean isText(int displayType)
{
if (displayType == String || displayType == Text
|| displayType == TextLong || displayType == Memo
|| displayType == TextLong || displayType == JSON || displayType == Memo
|| displayType == FilePath || displayType == FileName
|| displayType == URL || displayType == PrinterName
|| displayType == SingleSelectionGrid || displayType == Color
@ -588,7 +590,8 @@ public final class DisplayType
public static boolean isLOB (int displayType)
{
if (displayType == Binary
|| displayType == TextLong)
|| displayType == TextLong
|| (displayType == JSON && DB.isOracle()))
return true;
//not custom type, don't have to check factory
@ -932,7 +935,7 @@ public final class DisplayType
*/
public static Class<?> getClass (int displayType, boolean yesNoAsBoolean)
{
if (isText(displayType) || displayType == List || displayType == Payment || displayType == RadiogroupList)
if (isText(displayType) || displayType == List || displayType == Payment || displayType == RadiogroupList || displayType == JSON)
return String.class;
else if (isID(displayType) || displayType == Integer) // note that Integer is stored as BD
return Integer.class;
@ -972,6 +975,7 @@ public final class DisplayType
s_customDisplayTypeNegativeCache.put(customTypeKey, Boolean.TRUE);
}
}
//
return Object.class;
} // getClass
@ -1044,7 +1048,9 @@ public final class DisplayType
return getDatabase().getNumericDataType()+"(10)";
else
return getDatabase().getCharacterDataType()+"(" + fieldLength + ")";
}
}
if (displayType == DisplayType.JSON)
return getDatabase().getJsonDataType();
IServiceReferenceHolder<IDisplayTypeFactory> cache = s_displayTypeFactoryCache.get(displayType);
if (cache != null) {
@ -1175,6 +1181,8 @@ public final class DisplayType
return "Text";
case TextLong:
return "TextLong";
case JSON:
return "JSON";
case Time:
return "Time";
case TimestampWithTimeZone:

View File

@ -44,8 +44,14 @@ import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import org.adempiere.exceptions.AdempiereException;
import org.compiere.Adempiere;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.PdfContentByte;
@ -783,5 +789,20 @@ public class Util
public static boolean isDeveloperMode() {
return Files.isDirectory(Paths.get(Adempiere.getAdempiereHome() + File.separator + "org.adempiere.base")) || "Y".equals(System.getProperty("org.idempiere.developermode"));
}
/**
* Returns a string with a formatted JSON object
* @return string with a pretty JSON format
*/
public static String prettifyJSONString(String value) {
Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
try {
JsonElement jsonElement = JsonParser.parseString(value);
return gson.toJson(jsonElement);
} catch (JsonSyntaxException e) {
throw new AdempiereException(Msg.getMsg(Env.getCtx(), "InvalidJSON"));
}
}
} // Util

View File

@ -397,7 +397,8 @@ public class PoFiller{
setInteger(qName);
} else if (info.getColumnClass(index) == Timestamp.class) {
setTimestamp(qName);
}else if(DisplayType.TextLong == info.getColumnDisplayType(index)) {// export column from system have type is normal string, but import to system have this column but type is textlong (mean blob)
} else if(DisplayType.TextLong == info.getColumnDisplayType(index) || DisplayType.JSON == info.getColumnDisplayType(index)) {
// export column from system have type is normal string, but import to system have this column but type is text long (mean blob)
if (getStringValue (qName) != null && !isBlobOnPackinFile(qName)) {
setString(qName);
}else {

View File

@ -1447,6 +1447,7 @@ public class ProcessParameterPanel extends Panel implements
if (displayType == DisplayType.Text
|| displayType == DisplayType.Memo
|| displayType == DisplayType.TextLong
|| displayType == DisplayType.JSON
|| displayType == DisplayType.Binary
|| displayType == DisplayType.RowID
|| editor.getGridField().isEncrypted())

View File

@ -0,0 +1,39 @@
package org.adempiere.webui.editor;
import org.compiere.model.GridField;
import org.compiere.util.Util;
public class WJsonEditor extends WStringEditor {
/**
*
* @param gridField
*/
public WJsonEditor(GridField gridField) {
super(gridField);
getComponent().setMultiline(true);
setChangeEventWhenEditing(false);
}
/**
*
* @param gridField
* @param tableEditor
* @param editorConfiguration
*/
public WJsonEditor(GridField gridField, boolean tableEditor, IEditorConfiguration editorConfiguration) {
super(gridField, tableEditor, editorConfiguration);
getComponent().setMultiline(true);
setChangeEventWhenEditing(false);
}
@Override
public void setValue(Object value) {
super.setValue(value);
if (value != null && !Util.isEmpty(value.toString()))
getComponent().setValue(Util.prettifyJSONString(value.toString()));
}
}

View File

@ -30,6 +30,7 @@ import org.adempiere.webui.editor.WFileDirectoryEditor;
import org.adempiere.webui.editor.WFilenameEditor;
import org.adempiere.webui.editor.WHtmlEditor;
import org.adempiere.webui.editor.WImageEditor;
import org.adempiere.webui.editor.WJsonEditor;
import org.adempiere.webui.editor.WLocationEditor;
import org.adempiere.webui.editor.WLocatorEditor;
import org.adempiere.webui.editor.WNumberEditor;
@ -240,6 +241,10 @@ public class DefaultEditorFactory implements IEditorFactory {
else if (displayType == DisplayType.RecordUU)
{
editor = new WRecordUUIDEditor(gridField, tableEditor, editorConfiguration);
}
else if (displayType == DisplayType.JSON)
{
editor = new WJsonEditor(gridField, tableEditor, editorConfiguration);
}
else
{

View File

@ -536,7 +536,23 @@ public class DB_Oracle implements AdempiereDatabase
}
return result.toString();
} // TO_NUMBER
/**
* @return string with right casting for JSON inserts
*/
public String getJSONCast () {
return "?";
}
/**
* Return string as JSON object for INSERT statements
* @param value
* @return value as json
*/
public String TO_JSON (String value)
{
return value;
}
/**
* Get SQL Commands.
@ -1026,6 +1042,11 @@ public class DB_Oracle implements AdempiereDatabase
public String getClobDataType() {
return "CLOB";
}
@Override
public String getJsonDataType() {
return getClobDataType();
}
@Override
public String getTimestampDataType() {
@ -1072,6 +1093,8 @@ public class DB_Oracle implements AdempiereDatabase
// Inline Constraint
if (column.getAD_Reference_ID() == DisplayType.YesNo)
sql.append(" CHECK (").append(column.getColumnName()).append(" IN ('Y','N'))");
else if (column.getAD_Reference_ID() == DisplayType.JSON)
sql.append("CONSTRAINT ").append(column.getAD_Table().getTableName()).append("_").append(column.getColumnName()).append("_isjson CHECK (").append(column.getColumnName()).append(" IS JSON)");
// Null
if (column.isMandatory())

View File

@ -539,8 +539,31 @@ public class DB_PostgreSQL implements AdempiereDatabase
}
return result.toString();
} // TO_NUMBER
/**
* @return string with right casting for JSON inserts
*/
public String getJSONCast () {
return "CAST (? AS jsonb)";
}
/**
* Return string as JSON object for INSERT statements
* @param value
* @return value as json
*/
public String TO_JSON (String value)
{
if (value == null)
return "NULL";
StringBuilder retValue = null;
retValue = new StringBuilder("CAST (");
retValue.append(value);
retValue.append(" AS jsonb)");
return retValue.toString();
}
/**
* Get SQL Commands
* @param cmdType CMD_*
@ -1211,6 +1234,11 @@ public class DB_PostgreSQL implements AdempiereDatabase
public String getClobDataType() {
return "TEXT";
}
@Override
public String getJsonDataType() {
return "JSONB";
}
@Override
public String getTimestampDataType() {

View File

@ -0,0 +1,70 @@
package org.idempiere.test.base;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File;
import org.compiere.dbPort.Convert;
import org.compiere.model.MTest;
import org.compiere.util.Env;
import org.compiere.util.Ini;
import org.idempiere.test.AbstractTestCase;
import org.junit.jupiter.api.Test;
public class JsonFieldTest extends AbstractTestCase {
/**
*
*/
public JsonFieldTest() {
}
@Test
public void testSavingJSONValue() {
MTest testPO = new MTest(Env.getCtx(), getClass().getName(), 1, getTrxName());
boolean updated;
testPO.setJsonData("Testing if JSON allows to save regular strings");
updated = testPO.save();
assertFalse(updated);
testPO = new MTest(Env.getCtx(), getClass().getName(), 1, getTrxName());
String validJsonString = "{ \"name\": \"iDempiere\", \"id\": 100 }";
testPO.setJsonData(validJsonString);
updated = testPO.save();
assertTrue(updated);
String validJsonArray= "[ {\"type\": \"mobile\", \"phone\": \"001001\"} , {\"type\": \"fix\", \"phone\": \"002002\"} ]";
testPO.setJsonData(validJsonArray);
updated = testPO.save();
assertTrue(updated);
testPO.setJsonData(null);
updated = testPO.save();
assertTrue(updated);
String fileName = Convert.getMigrationScriptFileName("testLogMigrationScript");
String folderPg = Convert.getMigrationScriptFolder("postgresql");
String folderOr = Convert.getMigrationScriptFolder("oracle");
//Test inserting/updating with Values
Env.getCtx().setProperty(Ini.P_LOGMIGRATIONSCRIPT, "Y");
testPO.setJsonData(validJsonString);
updated = testPO.save();
assertTrue(updated);
testPO.setJsonData(validJsonArray);
updated = testPO.save();
assertTrue(updated);
Env.getCtx().setProperty(Ini.P_LOGMIGRATIONSCRIPT, "");
rollback();
File file = new File(folderPg + fileName);
assertTrue(file.exists(), "Not found: " + folderPg + fileName);
file.delete();
file = new File(folderOr + fileName);
assertTrue(file.exists(), "Not found: " + folderOr + fileName);
file.delete();
}
}