Search in sources :

Example 1 with DbIdentValue

use of com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue in project servoy-client by Servoy.

the class Column method getAsRightType.

public static Object getAsRightType(BaseColumnType type, int flags, Object obj, String format, TimeZone timeZone, boolean throwOnFail, boolean truncate) {
    if (obj == null)
        return null;
    if (obj instanceof DbIdentValue || obj instanceof NullValue)
        return obj;
    // can't do anything else
    if (format == null)
        return getAsRightType(type, flags, obj, throwOnFail, truncate);
    try {
        if (obj instanceof String) {
            String str = ((String) obj).trim();
            ParsePosition pos = new ParsePosition(0);
            switch(mapToDefaultType(type)) {
                case DATETIME:
                    SimpleDateFormat dformatter = new SimpleDateFormat(format);
                    if (timeZone != null) {
                        dformatter.setTimeZone(timeZone);
                    }
                    Date date = dformatter.parse(str, pos);
                    return getAsRightType(type, flags, date, throwOnFail, false);
                case NUMBER:
                    DecimalFormat nformatter = new DecimalFormat(format);
                    {
                        String pos_prefix = nformatter.getPositivePrefix();
                        // $NON-NLS-1$
                        if (pos_prefix == null)
                            pos_prefix = "";
                        String neg_prefix = nformatter.getNegativePrefix();
                        // $NON-NLS-1$
                        if (neg_prefix == null)
                            neg_prefix = "-";
                        if (!str.startsWith(pos_prefix) && !str.startsWith(neg_prefix)) {
                            // $NON-NLS-1$
                            nformatter.setPositivePrefix("");
                            // $NON-NLS-1$
                            nformatter.setNegativePrefix("-");
                        }
                    }
                    {
                        String pos_suffix = nformatter.getPositiveSuffix();
                        // $NON-NLS-1$
                        if (pos_suffix == null)
                            pos_suffix = "";
                        String neg_suffix = nformatter.getNegativeSuffix();
                        // $NON-NLS-1$
                        if (neg_suffix == null)
                            neg_suffix = "";
                        if (!str.endsWith(pos_suffix) && !str.endsWith(neg_suffix)) {
                            // $NON-NLS-1$
                            nformatter.setPositiveSuffix("");
                            // $NON-NLS-1$
                            nformatter.setNegativeSuffix("");
                        }
                    }
                    return getAsRightType(type, flags, nformatter.parse(str, pos), throwOnFail, false);
                case INTEGER:
                    DecimalFormat iformatter = new DecimalFormat(format);
                    {
                        String pos_prefix = iformatter.getPositivePrefix();
                        // $NON-NLS-1$
                        if (pos_prefix == null)
                            pos_prefix = "";
                        String neg_prefix = iformatter.getNegativePrefix();
                        // $NON-NLS-1$
                        if (neg_prefix == null)
                            neg_prefix = "-";
                        if (!str.startsWith(pos_prefix) && !str.startsWith(neg_prefix)) {
                            // $NON-NLS-1$
                            iformatter.setPositivePrefix("");
                            // $NON-NLS-1$
                            iformatter.setNegativePrefix("-");
                        }
                    }
                    {
                        String pos_suffix = iformatter.getPositiveSuffix();
                        // $NON-NLS-1$
                        if (pos_suffix == null)
                            pos_suffix = "";
                        String neg_suffix = iformatter.getNegativeSuffix();
                        // $NON-NLS-1$
                        if (neg_suffix == null)
                            neg_suffix = "";
                        if (!str.endsWith(pos_suffix) && !str.endsWith(neg_suffix)) {
                            // $NON-NLS-1$
                            iformatter.setPositiveSuffix("");
                            // $NON-NLS-1$
                            iformatter.setNegativeSuffix("");
                        }
                    }
                    return getAsRightType(type, flags, iformatter.parse(str, pos), throwOnFail, false);
                case TEXT:
                    if (type.getLength() > 0 && str.length() >= type.getLength()) {
                        obj = str.substring(0, type.getLength());
                    }
                    return obj;
                case MEDIA:
                    if (obj instanceof byte[]) {
                        return obj;
                    }
                    if (throwOnFail) {
                        // $NON-NLS-1$
                        throw new RuntimeException(Messages.getString("servoy.conversion.error.media", new Object[] { obj }));
                    }
                    return null;
                default:
                    return obj.toString();
            }
        } else {
            switch(mapToDefaultType(type)) {
                case DATETIME:
                    if (obj instanceof Date) {
                        return getAsRightType(type, flags, obj, throwOnFail, false);
                    }
                    if (obj instanceof Number) {
                        return getAsRightType(type, flags, new Date(((Number) obj).longValue()), throwOnFail, false);
                    }
                    return getAsRightType(type, flags, obj.toString(), format, timeZone, throwOnFail, false);
                case NUMBER:
                    if (obj instanceof Number) {
                        return obj;
                    }
                    return getAsRightType(type, flags, obj.toString(), format, timeZone, throwOnFail, false);
                case INTEGER:
                    if (obj instanceof Number) {
                        if (obj instanceof Integer || obj instanceof Long) {
                            return obj;
                        }
                        return new Long(((Number) obj).longValue());
                    }
                    return getAsRightType(type, flags, obj.toString(), format, timeZone, throwOnFail, false);
                case TEXT:
                    String str = obj.toString();
                    if (truncate && type.getLength() > 0 && str.length() >= type.getLength()) {
                        str = str.substring(0, type.getLength());
                    }
                    return str;
                case MEDIA:
                    if (obj instanceof byte[]) {
                        return obj;
                    }
                    if (throwOnFail) {
                        // $NON-NLS-1$
                        throw new RuntimeException(Messages.getString("servoy.conversion.error.media", new Object[] { obj }));
                    }
                    return null;
                default:
                    return obj.toString();
            }
        }
    } catch (RuntimeException e) {
        if (throwOnFail)
            throw e;
        Debug.log(e);
    }
    return null;
}
Also used : NullValue(com.servoy.j2db.dataprocessing.ValueFactory.NullValue) DbIdentValue(com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue) DecimalFormat(java.text.DecimalFormat) SimpleDateFormat(java.text.SimpleDateFormat) Date(java.util.Date) ParsePosition(java.text.ParsePosition)

Example 2 with DbIdentValue

use of com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue in project servoy-client by Servoy.

the class RelatedFoundSet method checkForeignKeyValue.

private boolean checkForeignKeyValue(Object obj, Object whereArg, int operator) {
    if (obj instanceof DbIdentValue && ((DbIdentValue) obj).getPkValue() != null)
        obj = ((DbIdentValue) obj).getPkValue();
    if (whereArg instanceof DbIdentValue && ((DbIdentValue) whereArg).getPkValue() != null)
        whereArg = ((DbIdentValue) whereArg).getPkValue();
    int maskedOperator = operator & IBaseSQLCondition.OPERATOR_MASK;
    if (Utils.equalObjects(whereArg, obj, true)) {
        return (maskedOperator == IBaseSQLCondition.EQUALS_OPERATOR || maskedOperator == IBaseSQLCondition.GTE_OPERATOR || maskedOperator == IBaseSQLCondition.LTE_OPERATOR || maskedOperator == IBaseSQLCondition.LIKE_OPERATOR);
    }
    if ((operator & IBaseSQLCondition.ORNULL_MODIFIER) != 0 && Utils.equalObjects(obj, null)) {
        return true;
    }
    if (maskedOperator == IBaseSQLCondition.NOT_OPERATOR) {
        return true;
    }
    boolean equal = false;
    if (maskedOperator == IBaseSQLCondition.LIKE_OPERATOR) {
        if (obj == null)
            return false;
        // For LIKE we make case-insensitive comparison.
        String arg = whereArg.toString().toUpperCase();
        String objString = obj.toString().toUpperCase();
        // $NON-NLS-1$
        StringTokenizer st = new StringTokenizer(arg, "%", true);
        List<String> al = new ArrayList<String>();
        while (st.hasMoreTokens()) {
            al.add(st.nextToken());
        }
        boolean startsWidth = true;
        int prevIndex = 0;
        for (int i = 0; i < al.size(); i++) {
            String tokenOrString = al.get(i);
            if (// $NON-NLS-1$
            "%".equals(tokenOrString)) {
                startsWidth = false;
                // if only % (left) then everything is equal
                equal = true;
            } else {
                if (startsWidth) {
                    equal = objString.startsWith(tokenOrString);
                } else {
                    if (al.size() > i + 1) {
                        prevIndex = objString.indexOf(tokenOrString, prevIndex);
                        equal = prevIndex != -1;
                    } else {
                        equal = objString.endsWith(tokenOrString);
                    }
                }
                if (!equal) {
                    break;
                }
            }
        }
    } else if (maskedOperator != IBaseSQLCondition.EQUALS_OPERATOR) {
        // Now test the GT(E) and LT(E) if possible (Comparable)
        if (obj instanceof Comparable && whereArg instanceof Comparable) {
            int compare = 0;
            if (whereArg instanceof String) {
                compare = ((String) whereArg).compareToIgnoreCase((String) obj);
            } else if (obj instanceof Number && whereArg instanceof Number && obj.getClass() != whereArg.getClass()) {
                compare = (int) (((Number) whereArg).doubleValue() - ((Number) obj).doubleValue());
            } else {
                compare = ((Comparable) whereArg).compareTo(obj);
            }
            if (maskedOperator == IBaseSQLCondition.GT_OPERATOR || maskedOperator == IBaseSQLCondition.GTE_OPERATOR) {
                equal = compare >= 0;
            } else if (maskedOperator == IBaseSQLCondition.LT_OPERATOR || maskedOperator == IBaseSQLCondition.LTE_OPERATOR) {
                equal = compare <= 0;
            }
        }
    }
    return equal;
}
Also used : StringTokenizer(java.util.StringTokenizer) DbIdentValue(com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue) ArrayList(java.util.ArrayList) SafeArrayList(com.servoy.j2db.util.SafeArrayList)

Example 3 with DbIdentValue

use of com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue in project servoy-client by Servoy.

the class FoundSetManager method validateRecord.

@SuppressWarnings("nls")
@Override
public JSRecordMarkers validateRecord(IRecordInternal record, Object state) {
    if (record == null)
        return null;
    // always reset the validation object
    record.setRecordMarkers(null);
    // first check for a validation entity method
    ITable table = record.getParentFoundSet().getTable();
    JSRecordMarkers recordMarkers = new JSRecordMarkers(record, application, state);
    Object[] args = new Object[] { record, recordMarkers, state };
    Scriptable scope = record.getParentFoundSet() instanceof Scriptable ? (Scriptable) record.getParentFoundSet() : null;
    try {
        executeFoundsetTrigger(table, args, StaticContentSpecLoader.PROPERTY_ONVALIDATEMETHODID, true, scope);
    } catch (ServoyException e) {
        recordMarkers.addGenericException(e);
    }
    if (record.existInDataSource()) {
        try {
            // if the first returns false it will stop the rest (inline with what we had)
            if (!executeFoundsetTriggerBreakOnFalse(table, args, StaticContentSpecLoader.PROPERTY_ONUPDATEMETHODID, true, scope)) {
                recordMarkers.setOnBeforeUpdateFailed();
            }
        } catch (ServoyException e) {
            recordMarkers.addGenericException(e);
        }
    } else {
        try {
            // if the first returns false it will stop the rest (inline with what we had)
            if (!executeFoundsetTriggerBreakOnFalse(table, args, StaticContentSpecLoader.PROPERTY_ONINSERTMETHODID, true, scope)) {
                recordMarkers.setOnBeforeInsertFailed();
            }
        } catch (ServoyException e) {
            recordMarkers.addGenericException(e);
        }
    }
    // check for null and length and validators
    SQLSheet sqlSheet = record.getParentFoundSet().getSQLSheet();
    record.getParentFoundSet().getTable().getColumns().forEach(column -> {
        // null
        Object rawValue = record instanceof ViewRecord ? record.getValue(column.getDataProviderID()) : record.getRawData().getRawValue(column.getDataProviderID());
        if (isNullColumnValidatorEnabled() && !column.getAllowNull() && column.getDatabaseDefaultValue() == null && (rawValue == null || ("".equals(rawValue) && Column.mapToDefaultType(column.getType()) == IColumnTypes.TEXT))) {
            recordMarkers.report("i18n:servoy.record.error.null.not.allowed", column.getDataProviderID(), ILogLevel.ERROR, state, new Object[] { column.getDataProviderID() });
            // this would result normally in an Record.exception so for now also set that
            if (!(record instanceof ViewRecord)) {
                record.getRawData().setLastException(new DataException("Column " + column.getDataProviderID() + " can't be null", ServoyException.DATA_INTEGRITY_VIOLATION));
            }
        }
        // validators only for changed columns (based on the raw, "unconverted" value)
        Object oldRawValue = record instanceof ViewRecord ? ((ViewRecord) record).getOldVaue(column.getDataProviderID()) : record.existInDataSource() ? record.getRawData().getOldRawValue(column.getDataProviderID()) : null;
        if (!(rawValue instanceof DbIdentValue) && !Utils.equalObjects(rawValue, oldRawValue)) {
            // the length check
            int valueLen = Column.getObjectSize(rawValue, column.getType());
            if (// insufficient space to save value
            valueLen > 0 && column.getLength() > 0 && valueLen > column.getLength()) {
                recordMarkers.report("i18n:servoy.record.error.columnSizeTooSmall", column.getDataProviderID(), ILogLevel.ERROR, state, new Object[] { column.getDataProviderID(), Integer.valueOf(column.getLength()), rawValue });
            }
            if (// for ViewRecords this is null, we don't have the actual sheet here for the underlying column
            sqlSheet != null) {
                Pair<String, Map<String, String>> validatorInfo = sqlSheet.getColumnValidatorInfo(sqlSheet.getColumnIndex(column.getDataProviderID()));
                if (validatorInfo != null) {
                    IColumnValidator validator = columnValidatorManager.getValidator(validatorInfo.getLeft());
                    if (validator == null) {
                        Debug.error("Column '" + column.getDataProviderID() + "' does have column validator  information, but either the validator '" + validatorInfo.getLeft() + "'  is not available, is the validator installed? (default default_validators.jar in the plugins) or the validator information is incorrect.");
                        recordMarkers.report("i18n:servoy.error.validatorNotFound", column.getDataProviderID(), ILogLevel.ERROR, state, new Object[] { validatorInfo.getLeft() });
                    } else {
                        try {
                            if (validator instanceof IColumnValidator2) {
                                ((IColumnValidator2) validator).validate(validatorInfo.getRight(), rawValue, column.getDataProviderID(), recordMarkers, state);
                            } else {
                                validator.validate(validatorInfo.getRight(), rawValue);
                            }
                        } catch (IllegalArgumentException e) {
                            recordMarkers.report("i18n:servoy.record.error.validation", column.getDataProviderID(), ILogLevel.ERROR, state, new Object[] { column.getDataProviderID(), rawValue, e.getMessage() });
                        }
                    }
                }
            }
        }
    });
    if (recordMarkers.isInvalid()) {
        record.setRecordMarkers(recordMarkers);
        return recordMarkers;
    }
    return null;
}
Also used : Scriptable(org.mozilla.javascript.Scriptable) ServoyException(com.servoy.j2db.util.ServoyException) DbIdentValue(com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue) ITable(com.servoy.j2db.persistence.ITable) ServoyJSONObject(com.servoy.j2db.util.ServoyJSONObject) Map(java.util.Map) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) HashMap(java.util.HashMap) ConcurrentMap(java.util.concurrent.ConcurrentMap)

Example 4 with DbIdentValue

use of com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue in project servoy-client by Servoy.

the class SQLSheet method getDuplicateRecordData.

public Object[] getDuplicateRecordData(IServiceProvider app, Row toDuplicateRow) {
    Object[] toDuplicate = toDuplicateRow.getRawColumnData();
    SQLDescription desc = getSQLDescription(SELECT);
    List<?> list = desc.getDataProviderIDsDilivery();
    Object[] array = new Object[toDuplicate.length];
    // $NON-NLS-1$
    if (list.size() != array.length)
        throw new IllegalArgumentException("Data to duplicate MUST be created with (help) of this sheet");
    for (int i = 0; i < toDuplicate.length; i++) {
        Object obj = toDuplicate[i];
        if (obj instanceof ValueFactory.BlobMarkerValue) {
            obj = toDuplicateRow.getValue(i);
        } else if (obj instanceof ValueFactory.DbIdentValue && ((DbIdentValue) obj).getRow() == toDuplicateRow) {
            // only create a new db ident value if that db ident value belongs to the duplicated row (== pk db ident instead a a relation db ident that has to be kept!)
            obj = ValueFactory.createDbIdentValue();
        }
        array[i] = obj;
        try {
            Column c = table.getColumn((String) list.get(i));
            ColumnInfo ci = c.getColumnInfo();
            if (c.isDBIdentity()) {
                array[i] = ValueFactory.createDbIdentValue();
            } else if (ci != null && ci.hasSequence()) {
                array[i] = c.getNewRecordValue(app);
            }
            if (ci != null && ci.hasSystemValue()) {
                array[i] = c.getNewRecordValue(app);
            }
        } catch (Exception ex) {
            Debug.error(ex);
        }
    }
    return array;
}
Also used : IBaseColumn(com.servoy.base.persistence.IBaseColumn) Column(com.servoy.j2db.persistence.Column) DbIdentValue(com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue) ColumnInfo(com.servoy.j2db.persistence.ColumnInfo) ServoyException(com.servoy.j2db.util.ServoyException) IOException(java.io.IOException) RepositoryException(com.servoy.j2db.persistence.RepositoryException)

Example 5 with DbIdentValue

use of com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue in project servoy-client by Servoy.

the class EditRecordList method stopEditingImpl.

/**
 * This method should only be called through stopEditing(boolean,List<Record>) so that that can call onAutoSaveFailed.
 */
private int stopEditingImpl(final boolean javascriptStop, List<IRecord> recordsToSave, int recursionDepth) {
    if (recursionDepth > 50) {
        fsm.getApplication().reportJSError("stopEditing max recursion exceeded, look if on (or after) record update or inserts  are constantly changing records", new RuntimeException());
        return ISaveConstants.SAVE_FAILED;
    }
    if (ignoreSave) {
        return ISaveConstants.AUTO_SAVE_BLOCKED;
    }
    if (isSavingAll) {
        // we are saving all, no need to save anything more
        return ISaveConstants.STOPPED;
    }
    if (recordsToSave == null && savingRecords.size() > 0) {
        // we are saving some records, cannot call save all now, not supported
        return ISaveConstants.STOPPED;
    }
    if (recordsToSave != null && savingRecords.size() > 0) {
        // make a copy to be sure that removeAll is supported
        recordsToSave = new ArrayList<IRecord>(recordsToSave);
        recordsToSave.removeAll(savingRecords);
    }
    if (recordsToSave != null) {
        boolean hasEditedRecords = false;
        editRecordsLock.lock();
        try {
            for (IRecord record : recordsToSave) {
                if (editedRecords.contains(record)) {
                    hasEditedRecords = true;
                    break;
                }
            }
        } finally {
            editRecordsLock.unlock();
        }
        if (!hasEditedRecords)
            return ISaveConstants.STOPPED;
    }
    // here we can't have a test if editedRecords is empty (and return stop)
    // because for just globals or findstates (or deleted records)
    // we need to pass prepareForSave.
    final List<IRecord> recordsToSaveFinal = recordsToSave;
    if (!fsm.getApplication().isEventDispatchThread()) {
        // only the event dispatch thread can stop an current edit.
        // this is a fix for innerworkings because background aggregate queries seems to also trigger saves
        // $NON-NLS-1$
        Debug.trace("Stop edit postponend because it is not in the event dispatch thread: " + Thread.currentThread().getName());
        // calculations running from lazy table view loading threads may trigger stopEditing
        fsm.getApplication().invokeLater(new Runnable() {

            public void run() {
                // do not stop if the user is editing something else.
                boolean stop;
                editRecordsLock.lock();
                try {
                    stop = editedRecords.size() == 1 && recordsToSaveFinal != null && recordsToSaveFinal.size() == 1 && editedRecords.get(0) == recordsToSaveFinal.get(0);
                } finally {
                    editRecordsLock.unlock();
                }
                if (stop) {
                    stopEditing(javascriptStop, recordsToSaveFinal);
                } else {
                    // $NON-NLS-1$
                    Debug.trace("Stop edit skipped because other records are being edited");
                }
            }
        });
        return ISaveConstants.AUTO_SAVE_BLOCKED;
    }
    int editedRecordsSize;
    try {
        int p = prepareForSave(true);
        if (p != ISaveConstants.STOPPED) {
            return p;
        }
        if (recordsToSave == null) {
            isSavingAll = true;
        } else {
            savingRecords.addAll(recordsToSave);
        }
        // remove any non referenced failed records
        boolean fireChange = false;
        editRecordsLock.lock();
        try {
            if (failedRecords.size() != 0) {
                Iterator<IRecordInternal> it = failedRecords.iterator();
                while (it.hasNext()) {
                    IRecordInternal rec = it.next();
                    if (rec != null) {
                        if (rec.getParentFoundSet() == null) {
                            it.remove();
                        } else if (rec.getParentFoundSet().getRecordIndex(rec) == -1) {
                            it.remove();
                        }
                    }
                }
                if (failedRecords.size() == 0) {
                    fireChange = true;
                }
            }
        } finally {
            editRecordsLock.unlock();
        }
        if (fireChange)
            fireEditChange();
        // remove the unchanged, really calculate when it is a real stop (autosave = true or it is a javascript stop)
        removeUnChangedRecords(autoSave || javascriptStop, true);
        // check if anything left
        int editRecordListSize;
        editRecordsLock.lock();
        try {
            editRecordListSize = editedRecords.size();
            if (editRecordListSize == 0)
                return ISaveConstants.STOPPED;
        } finally {
            editRecordsLock.unlock();
        }
        // cannot stop, its blocked
        if (!autoSave && !javascriptStop) {
            return ISaveConstants.AUTO_SAVE_BLOCKED;
        }
        int failedCount = 0;
        boolean justValidationErrors = false;
        lastStopEditingException = null;
        List<RowUpdateInfo> rowUpdates = new ArrayList<RowUpdateInfo>(editRecordListSize);
        editRecordsLock.lock();
        try {
            if (recordsToSave == null) {
                // if it is a save all, then first filter out all the duplicate rows.
                for (int i = 0; i < editedRecords.size(); i++) {
                    Row toTest = editedRecords.get(i).getRawData();
                    for (int j = editedRecords.size(); --j > i; ) {
                        if (editedRecords.get(j).getRawData() == toTest) {
                            removeEditedRecord(editedRecords.get(j));
                        }
                    }
                }
            }
            Map<IRecordInternal, Integer> processed = new HashMap<IRecordInternal, Integer>();
            for (IRecordInternal tmp = getFirstElement(editedRecords, recordsToSave); tmp != null; tmp = getFirstElement(editedRecords, recordsToSave)) {
                // check if we do not have an infinite recursive loop
                Integer count = processed.get(tmp);
                if (count != null && count.intValue() > 50) {
                    fsm.getApplication().reportJSError("stopEditing max loop counter exceeded on " + tmp.getParentFoundSet().getDataSource() + "/" + tmp.getPKHashKey(), new RuntimeException());
                    return ISaveConstants.SAVE_FAILED;
                }
                processed.put(tmp, Integer.valueOf(count == null ? 1 : (count.intValue() + 1)));
                if (tmp instanceof Record) {
                    Record record = (Record) tmp;
                    // prevent multiple update for the same row (from multiple records)
                    for (int j = 0; j < rowUpdates.size(); j++) {
                        if (rowUpdates.get(j).getRow() == record.getRawData()) {
                            // create a new rowUpdate that contains both updates
                            RowUpdateInfo removed = rowUpdates.remove(j);
                            recordTested.remove(record);
                            // do use the first record, that one must always be leading. (for fire of events)
                            record = removed.getRecord();
                            break;
                        }
                    }
                    try {
                        // test for table events; this may execute table events if the user attached JS methods to them;
                        // the user might add/delete/edit records in the JS - thus invalidating a normal iterator (it)
                        // - edited record list changes; this is why an AllowListModificationIterator is used
                        // Note that the behaviour is different when trigger returns false or when it throws an exception.
                        // when the trigger returns false, record must stay in editedRecords.
                        // this is needed because the trigger may be used as validation to keep the user in the record when autosave=true.
                        // when the trigger throws an exception, the record must move from editedRecords to failedRecords so that in
                        // scripting the failed records can be examined (the thrown value is retrieved via record.exception.getValue())
                        editRecordsLock.unlock();
                        boolean validationErrors = false;
                        try {
                            JSRecordMarkers validateObject = fsm.validateRecord(record, null);
                            if (// throws ServoyException when trigger method throws exception
                            validateObject != null && validateObject.isHasErrors()) {
                                Object[] genericExceptions = validateObject.getGenericExceptions();
                                if (genericExceptions.length > 0) {
                                    // compartible with old code, then those exceptions are catched below.
                                    throw (Exception) genericExceptions[0];
                                }
                                // we always want to process all records, but mark this as a validation error so below the failed records are updated.
                                validationErrors = true;
                                // update the just failed boolean to true, if that is true and there is not really an exception then handleException of application is not called.
                                justValidationErrors = true;
                                failedCount++;
                                if (!failedRecords.contains(record)) {
                                    failedRecords.add(record);
                                }
                                recordTested.remove(record);
                            }
                        } finally {
                            editRecordsLock.lock();
                        }
                        if (!validationErrors) {
                            RowUpdateInfo rowUpdateInfo = getRecordUpdateInfo(record);
                            if (rowUpdateInfo != null) {
                                rowUpdateInfo.setRecord(record);
                                rowUpdates.add(rowUpdateInfo);
                            } else {
                                recordTested.remove(record);
                            }
                        }
                    } catch (ServoyException e) {
                        // $NON-NLS-1$//$NON-NLS-2$
                        log.debug(// $NON-NLS-1$//$NON-NLS-2$
                        "stopEditing(" + javascriptStop + ") encountered an exception - could be expected and treated by solution code or not", e);
                        // trigger method threw exception
                        lastStopEditingException = e;
                        failedCount++;
                        // set latest
                        record.getRawData().setLastException(e);
                        if (!failedRecords.contains(record)) {
                            failedRecords.add(record);
                        }
                        recordTested.remove(record);
                    } catch (Exception e) {
                        // $NON-NLS-1$ //$NON-NLS-2$
                        Debug.error("Not a normal Servoy/Db Exception generated in saving record: " + record + " removing the record", e);
                        recordTested.remove(record);
                    }
                } else {
                    // find state
                    recordTested.remove(tmp);
                }
                editedRecords.remove(tmp);
            }
        } finally {
            editRecordsLock.unlock();
        }
        if (failedCount > 0) {
            placeBackAlreadyProcessedRecords(rowUpdates);
            if (lastStopEditingException == null && justValidationErrors) {
                return ISaveConstants.VALIDATION_FAILED;
            } else {
                if (!(lastStopEditingException instanceof ServoyException)) {
                    lastStopEditingException = new ApplicationException(ServoyException.SAVE_FAILED, lastStopEditingException);
                }
                if (!javascriptStop)
                    // $NON-NLS-1$
                    fsm.getApplication().handleException(// $NON-NLS-1$
                    fsm.getApplication().getI18NMessage("servoy.formPanel.error.saveFormData"), lastStopEditingException);
                return ISaveConstants.SAVE_FAILED;
            }
        }
        if (rowUpdates.size() == 0) {
            fireEditChange();
            if (Debug.tracing()) {
                // $NON-NLS-1$
                Debug.trace("no records to update anymore, failed: " + failedRecords.size());
            }
            return ISaveConstants.STOPPED;
        }
        if (Debug.tracing()) {
            // $NON-NLS-1$ //$NON-NLS-2$
            Debug.trace("Updating/Inserting " + rowUpdates.size() + " records: " + rowUpdates.toString());
        }
        RowUpdateInfo[] infos = rowUpdates.toArray(new RowUpdateInfo[rowUpdates.size()]);
        if (infos.length > 1 && !fsm.disableInsertsReorder) {
            // search if there are new row pks used that are
            // used in records before this record and sort it based on that.
            boolean changed = false;
            List<RowUpdateInfo> al = new ArrayList<RowUpdateInfo>(Arrays.asList(infos));
            int prevI = -1;
            outer: for (int i = al.size(); --i > 0; ) {
                Row row = al.get(i).getRow();
                // only test for new rows and its pks.
                if (row.existInDB())
                    continue;
                String[] pkColumns = row.getRowManager().getSQLSheet().getPKColumnDataProvidersAsArray();
                Object[] pk = row.getPK();
                for (int j = 0; j < pk.length; j++) {
                    Object pkObject = pk[j];
                    // special case if pk was db ident and that value was copied from another row.
                    if (pkObject instanceof DbIdentValue && ((DbIdentValue) pkObject).getRow() != row)
                        continue;
                    for (int k = 0; k < i; k++) {
                        RowUpdateInfo updateInfo = al.get(k);
                        Object[] values = updateInfo.getRow().getRawColumnData();
                        int[] pkIndexes = updateInfo.getFoundSet().getSQLSheet().getPKIndexes();
                        IntHashMap<String> pks = new IntHashMap<String>(pkIndexes.length, 1);
                        for (int pkIndex : pkIndexes) {
                            // $NON-NLS-1$
                            pks.put(pkIndex, "");
                        }
                        for (int l = 0; l < values.length; l++) {
                            // skip all pk column indexes (except from dbidents from other rows, this may need resort). Those shouldn't be resorted
                            if (!(values[l] instanceof DbIdentValue && ((DbIdentValue) values[l]).getRow() != updateInfo.getRow()) && pks.containsKey(l))
                                continue;
                            boolean same = values[l] == pkObject;
                            if (!same && values[l] != null) {
                                Column pkColumn = row.getRowManager().getSQLSheet().getTable().getColumn(pkColumns[j]);
                                if (pkColumn.hasFlag(IBaseColumn.UUID_COLUMN)) {
                                    // same uuids are the same even if not the same object
                                    same = equalObjects(pkObject, values[l], 0, true);
                                }
                            }
                            if (same) {
                                al.add(k, al.remove(i));
                                // watch out for endless loops when 2 records both with pk's point to each other...
                                if (prevI != i) {
                                    prevI = i;
                                    i++;
                                }
                                changed = true;
                                continue outer;
                            }
                        }
                    }
                }
            }
            if (changed) {
                infos = al.toArray(infos);
            }
        }
        ISQLStatement[] statements;
        if (fsm.statementBatching && infos.length > 1) {
            // Merge insert statements insert statements from all info's: multiple info's can share the same statement of the records are batched together on the statement level
            List<ISQLStatement> mergedStatements = new ArrayList<ISQLStatement>(infos.length);
            ISQLStatement prevStatement = null;
            for (RowUpdateInfo rowUpdateInfo : infos) {
                ISQLStatement statement = rowUpdateInfo.getISQLStatement();
                if (statement.getAction() == ISQLActionTypes.INSERT_ACTION && prevStatement != null && prevStatement.getAction() == ISQLActionTypes.INSERT_ACTION && insertStatementsCanBeMerged(prevStatement, statement)) {
                    mergeInsertStatements(prevStatement, statement);
                } else {
                    prevStatement = statement;
                    mergedStatements.add(statement);
                }
            }
            statements = mergedStatements.toArray(new ISQLStatement[mergedStatements.size()]);
        } else {
            statements = stream(infos).map(RowUpdateInfo::getISQLStatement).toArray(ISQLStatement[]::new);
        }
        // TODO if one statement fails in a transaction how do we know which one? and should we rollback all rows in these statements?
        Object[] idents = null;
        try {
            idents = fsm.getDataServer().performUpdates(fsm.getApplication().getClientID(), statements);
        } catch (Exception e) {
            // $NON-NLS-1$//$NON-NLS-2$
            log.debug("stopEditing(" + javascriptStop + ") encountered an exception - could be expected and treated by solution code or not", e);
            lastStopEditingException = e;
            if (!javascriptStop)
                // $NON-NLS-1$
                fsm.getApplication().handleException(// $NON-NLS-1$
                fsm.getApplication().getI18NMessage("servoy.formPanel.error.saveFormData"), new ApplicationException(ServoyException.SAVE_FAILED, lastStopEditingException));
            return ISaveConstants.SAVE_FAILED;
        }
        if (idents.length != infos.length) {
            // $NON-NLS-1$
            Debug.error("Should be of same size!!");
        }
        List<RowUpdateInfo> infosToBePostProcessed = new ArrayList<RowUpdateInfo>();
        Map<FoundSet, List<Record>> foundsetToRecords = new HashMap<FoundSet, List<Record>>();
        Map<FoundSet, List<String>> foundsetToAggregateDeletes = new HashMap<FoundSet, List<String>>();
        List<Runnable> fires = new ArrayList<Runnable>(infos.length);
        // Walk in reverse over it, so that related rows are update in there row manger before they are required by there parents.
        for (int i = infos.length; --i >= 0; ) {
            RowUpdateInfo rowUpdateInfo = infos[i];
            FoundSet foundSet = rowUpdateInfo.getFoundSet();
            Row row = rowUpdateInfo.getRow();
            String oldKey = row.getPKHashKey();
            Record record = rowUpdateInfo.getRecord();
            if (idents != null && idents.length != 0 && idents[i] != null) {
                Object retValue = idents[i];
                if (retValue instanceof Exception) {
                    // $NON-NLS-1$//$NON-NLS-2$
                    log.debug(// $NON-NLS-1$//$NON-NLS-2$
                    "stopEditing(" + javascriptStop + ") encountered an exception - could be expected and treated by solution code or not", (Exception) retValue);
                    lastStopEditingException = (Exception) retValue;
                    failedCount++;
                    if (retValue instanceof ServoyException) {
                        ((ServoyException) retValue).fillScriptStack();
                    }
                    row.setLastException((Exception) retValue);
                    markRecordAsFailed(record);
                    JSRecordMarkers vo = record.getRecordMarkers() != null ? record.getRecordMarkers() : new JSRecordMarkers(record, fsm.getApplication());
                    vo.addGenericException((Exception) retValue);
                    record.setRecordMarkers(vo);
                    continue;
                } else if (retValue instanceof Object[]) {
                    Object[] rowData = (Object[]) retValue;
                    Object[] oldRowData = row.getRawColumnData();
                    if (oldRowData != null) {
                        if (oldRowData.length == rowData.length) {
                            for (int j = 0; j < rowData.length; j++) {
                                if (rowData[j] instanceof BlobMarkerValue) {
                                    rowData[j] = oldRowData[j];
                                }
                                if (oldRowData[j] instanceof DbIdentValue) {
                                    row.setDbIdentValue(rowData[j]);
                                }
                            }
                        } else {
                            Debug.error("Requery data has different length from row data.");
                        }
                    }
                    row.setRollbackData(rowData, Row.ROLLBACK_MODE.UPDATE_CHANGES);
                } else if (!Boolean.TRUE.equals(retValue)) {
                    // is db ident, can only be one column
                    row.setDbIdentValue(retValue);
                }
            }
            editRecordsLock.lock();
            try {
                recordTested.remove(record);
            } finally {
                editRecordsLock.unlock();
            }
            if (!row.existInDB()) {
                // when row was not saved yet row pkhash will be with new value, pksAndRecordsHolder will have initial value
                foundSet.updatePk(record);
            }
            try {
                ISQLStatement statement = rowUpdateInfo.getISQLStatement();
                row.getRowManager().rowUpdated(row, oldKey, foundSet, fires, statement instanceof ITrackingSQLStatement ? ((ITrackingSQLStatement) statement).getChangedColumns() : null);
            } catch (Exception e) {
                // $NON-NLS-1$//$NON-NLS-2$
                log.debug("stopEditing(" + javascriptStop + ") encountered an exception - could be expected and treated by solution code or not", e);
                lastStopEditingException = e;
                failedCount++;
                row.setLastException(e);
                JSRecordMarkers vo = record.getRecordMarkers() != null ? record.getRecordMarkers() : new JSRecordMarkers(record, fsm.getApplication());
                vo.addGenericException(e);
                record.setRecordMarkers(vo);
                editRecordsLock.lock();
                try {
                    if (!failedRecords.contains(record)) {
                        failedRecords.add(record);
                    }
                } finally {
                    editRecordsLock.unlock();
                }
            }
            infosToBePostProcessed.add(infos[i]);
            List<Record> lst = foundsetToRecords.get(foundSet);
            if (lst == null) {
                lst = new ArrayList<Record>(3);
                foundsetToRecords.put(foundSet, lst);
            }
            lst.add(record);
            List<String> aggregates = foundsetToAggregateDeletes.get(foundSet);
            if (aggregates == null) {
                foundsetToAggregateDeletes.put(foundSet, rowUpdateInfo.getAggregatesToRemove());
            } else {
                List<String> toMerge = rowUpdateInfo.getAggregatesToRemove();
                for (int j = 0; j < toMerge.size(); j++) {
                    String aggregate = toMerge.get(j);
                    if (!aggregates.contains(aggregate)) {
                        aggregates.add(aggregate);
                    }
                }
            }
        }
        // run rowmanager fires in reverse order (original order because info's were processed in reverse order) -> first inserted record is fired first
        for (int i = fires.size(); --i >= 0; ) {
            fires.get(i).run();
        }
        // get the size of the edited records before the table events, so that we can look if those events did change records again.
        editedRecordsSize = editedRecords.size();
        Record rowUpdateInfoRecord = null;
        for (RowUpdateInfo rowUpdateInfo : infosToBePostProcessed) {
            try {
                rowUpdateInfoRecord = rowUpdateInfo.getRecord();
                ((FoundSet) rowUpdateInfoRecord.getParentFoundSet()).executeFoundsetTrigger(new Object[] { rowUpdateInfoRecord }, rowUpdateInfo.getISQLStatement().getAction() == ISQLActionTypes.INSERT_ACTION ? StaticContentSpecLoader.PROPERTY_ONAFTERINSERTMETHODID : StaticContentSpecLoader.PROPERTY_ONAFTERUPDATEMETHODID, true);
            } catch (ServoyException e) {
                if (e instanceof DataException && e.getCause() instanceof JavaScriptException) {
                    // trigger method threw exception
                    // $NON-NLS-1$//$NON-NLS-2$
                    log.debug("stopEditing(" + javascriptStop + ") encountered an exception - could be expected and treated by solution code or not", e);
                    lastStopEditingException = e;
                    failedCount++;
                    rowUpdateInfoRecord.getRawData().setLastException(e);
                    JSRecordMarkers vo = rowUpdateInfoRecord.getRecordMarkers() != null ? rowUpdateInfoRecord.getRecordMarkers() : new JSRecordMarkers(rowUpdateInfoRecord, fsm.getApplication());
                    vo.addGenericException(e);
                    rowUpdateInfoRecord.setRecordMarkers(vo);
                    editRecordsLock.lock();
                    try {
                        if (!failedRecords.contains(rowUpdateInfoRecord)) {
                            failedRecords.add(rowUpdateInfoRecord);
                        }
                    } finally {
                        editRecordsLock.unlock();
                    }
                } else {
                    // $NON-NLS-1$
                    fsm.getApplication().handleException("Failed to execute after update/insert trigger.", e);
                }
            }
        }
        for (Map.Entry<FoundSet, List<Record>> entry : foundsetToRecords.entrySet()) {
            FoundSet fs = entry.getKey();
            fs.recordsUpdated(entry.getValue(), foundsetToAggregateDeletes.get(fs));
        }
        boolean shouldFireEditChange;
        editRecordsLock.lock();
        try {
            shouldFireEditChange = editedRecords.size() == 0;
        } finally {
            editRecordsLock.unlock();
        }
        if (shouldFireEditChange) {
            fireEditChange();
        }
        if (failedCount > 0) {
            if (!javascriptStop) {
                lastStopEditingException = new ApplicationException(ServoyException.SAVE_FAILED, lastStopEditingException);
                // $NON-NLS-1$
                fsm.getApplication().handleException(// $NON-NLS-1$
                fsm.getApplication().getI18NMessage("servoy.formPanel.error.saveFormData"), lastStopEditingException);
            }
            return ISaveConstants.SAVE_FAILED;
        }
    } catch (RuntimeException e) {
        if (e instanceof IllegalArgumentException) {
            fsm.getApplication().handleException(null, new ApplicationException(ServoyException.INVALID_INPUT, e));
            return ISaveConstants.SAVE_FAILED;
        } else if (e instanceof IllegalStateException) {
            // $NON-NLS-1$
            fsm.getApplication().handleException(fsm.getApplication().getI18NMessage("servoy.formPanel.error.saveFormData"), e);
            return ISaveConstants.SAVE_FAILED;
        } else {
            Debug.error(e);
            throw e;
        }
    } finally {
        if (recordsToSave == null) {
            isSavingAll = false;
        } else {
            savingRecords.removeAll(recordsToSave);
        }
        fireEvents();
    }
    if (editedRecords.size() != editedRecordsSize && recordsToSave == null) {
        // records where changed by the after insert/update table events, call stop edit again if this was not a specific record save.
        return stopEditingImpl(javascriptStop, null, recursionDepth + 1);
    }
    return ISaveConstants.STOPPED;
}
Also used : HashMap(java.util.HashMap) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) IntHashMap(com.servoy.j2db.util.IntHashMap) ArrayList(java.util.ArrayList) ServoyException(com.servoy.j2db.util.ServoyException) IBaseColumn(com.servoy.base.persistence.IBaseColumn) Column(com.servoy.j2db.persistence.Column) DbIdentValue(com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue) ArrayList(java.util.ArrayList) List(java.util.List) ServoyException(com.servoy.j2db.util.ServoyException) ApplicationException(com.servoy.j2db.ApplicationException) JavaScriptException(org.mozilla.javascript.JavaScriptException) RepositoryException(com.servoy.j2db.persistence.RepositoryException) JavaScriptException(org.mozilla.javascript.JavaScriptException) IntHashMap(com.servoy.j2db.util.IntHashMap) ApplicationException(com.servoy.j2db.ApplicationException) BlobMarkerValue(com.servoy.j2db.dataprocessing.ValueFactory.BlobMarkerValue) NativeObject(org.mozilla.javascript.NativeObject) HashMap(java.util.HashMap) ConcurrentMap(java.util.concurrent.ConcurrentMap) Map(java.util.Map) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) IntHashMap(com.servoy.j2db.util.IntHashMap)

Aggregations

DbIdentValue (com.servoy.j2db.dataprocessing.ValueFactory.DbIdentValue)13 ServoyException (com.servoy.j2db.util.ServoyException)4 ArrayList (java.util.ArrayList)4 IBaseColumn (com.servoy.base.persistence.IBaseColumn)3 Column (com.servoy.j2db.persistence.Column)3 RepositoryException (com.servoy.j2db.persistence.RepositoryException)3 ServoyJSONObject (com.servoy.j2db.util.ServoyJSONObject)3 Date (java.util.Date)3 Map (java.util.Map)3 ServoyBeanState (com.servoy.j2db.component.ServoyBeanState)2 IDisplayData (com.servoy.j2db.dataprocessing.IDisplayData)2 IDisplayRelatedData (com.servoy.j2db.dataprocessing.IDisplayRelatedData)2 IFoundSetInternal (com.servoy.j2db.dataprocessing.IFoundSetInternal)2 IRecordInternal (com.servoy.j2db.dataprocessing.IRecordInternal)2 IServoyAwareBean (com.servoy.j2db.dataui.IServoyAwareBean)2 QuerySelect (com.servoy.j2db.query.QuerySelect)2 IScriptable (com.servoy.j2db.scripting.IScriptable)2 IScriptableProvider (com.servoy.j2db.scripting.IScriptableProvider)2 ISupportOnRenderCallback (com.servoy.j2db.ui.ISupportOnRenderCallback)2 RenderEventExecutor (com.servoy.j2db.ui.RenderEventExecutor)2