/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.dataprocessing; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import com.servoy.base.persistence.constants.IValueListConstants; import com.servoy.base.query.BaseQueryTable; import com.servoy.base.query.IBaseSQLCondition; import com.servoy.j2db.IApplication; import com.servoy.j2db.IServiceProvider; import com.servoy.j2db.component.ComponentFactory; import com.servoy.j2db.dataprocessing.CustomValueList.DisplayString; import com.servoy.j2db.persistence.Column; import com.servoy.j2db.persistence.IDataProvider; import com.servoy.j2db.persistence.IRepository; import com.servoy.j2db.persistence.Relation; import com.servoy.j2db.persistence.RepositoryException; import com.servoy.j2db.persistence.Table; import com.servoy.j2db.persistence.ValueList; import com.servoy.j2db.query.CompareCondition; import com.servoy.j2db.query.OrCondition; import com.servoy.j2db.query.QuerySelect; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.Pair; import com.servoy.j2db.util.SafeArrayList; import com.servoy.j2db.util.ServoyException; import com.servoy.j2db.util.Utils; /** * Valuelist for lookup of values * * @author jcompagner, jblok */ public class LookupValueList implements IValueList { private TableChangeListener tableListener; private final Map<ListDataListener, Object> listeners = new WeakHashMap<ListDataListener, Object>(); private final List<Object> alReal = new SafeArrayList<Object>(); private final List<Object> alDisplay = new ArrayList<Object>(); private final ValueList valueList; private final IServiceProvider application; private boolean dontQuery; private final Table table; private final int showValues; private final int returnValues; private final boolean concatReturnValues; private final boolean concatShowValues; private IRecordInternal parentState; private IFoundSetInternal relatedFoundset; private final IValueList secondLookup; private final String displayFormat; protected final int maxValuelistRows; public LookupValueList(ValueList list, IServiceProvider application, IValueList fallback, String displayFormat) throws Exception { this.valueList = list; this.application = application; this.secondLookup = fallback; this.displayFormat = displayFormat; String dataSource = null; Relation[] relations = null; if (list.getDatabaseValuesType() == IValueListConstants.TABLE_VALUES) { dataSource = list.getDataSource(); } else { relations = application.getFlattenedSolution().getRelationSequence(list.getRelationName()); if (relations != null) { dataSource = relations[relations.length - 1].getForeignDataSource(); } } int maxRowsSetting = (application instanceof IApplication) ? Utils.getAsInteger(((IApplication)application).getClientProperty(IApplication.VALUELIST_MAX_ROWS)) : 0; maxValuelistRows = (maxRowsSetting > 0 && maxRowsSetting <= 1000) ? maxRowsSetting : ((FoundSetManager)application.getFoundSetManager()).pkChunkSize * 4; table = (Table)application.getFoundSetManager().getTable(dataSource); showValues = list.getShowDataProviders(); returnValues = list.getReturnDataProviders(); //more than one value -> concat concatShowValues = ((showValues != 1) && (showValues != 2) && (showValues != 4)); concatReturnValues = ((returnValues != 1) && (returnValues != 2) && (returnValues != 4)); if (table != null && showValues != returnValues && tableListener == null) { //register try { tableListener = new TableChangeListener(); if (table != null) { ((FoundSetManager)application.getFoundSetManager()).addTableListener(table, tableListener); for (int i = 0; relations != null && i < relations.length - 1; i++) { ((FoundSetManager)application.getFoundSetManager()).addTableListener(relations[i].getForeignTable(), tableListener); } } } catch (RepositoryException e) { Debug.error("Error registering table", e); //$NON-NLS-1$ } } } public void setFallbackValueList(IValueList list) { // ignore is being done in the constructor } /** * @see com.servoy.j2db.dataprocessing.IValueList#getFallbackValueList() */ public IValueList getFallbackValueList() { return secondLookup; } private final class TableChangeListener implements ITableChangeListener { public void tableChange(TableEvent e) { clear(); Iterator<ListDataListener> it = listeners.keySet().iterator(); ListDataEvent lde = new ListDataEvent(LookupValueList.this, ListDataEvent.CONTENTS_CHANGED, -1, -1); while (it.hasNext()) { it.next().contentsChanged(lde); } } } private Object getAsRightType(String dataprovider, Object val) { if (dataprovider != null && table != null) { return table.getColumn(dataprovider).getAsRightType(val); } return null; } public String getName() { return valueList.getName(); } public ValueList getValueList() { return valueList; } public Object getRealElementAt(int index) { if (index < -1 && secondLookup != null) { return secondLookup.getRealElementAt(index * -1 - 2); } return alReal.get(index); } public String getRelationName() { return valueList.getRelationName(); } public void fill(IRecordInternal ps) { if (valueList.getDatabaseValuesType() == IValueListConstants.RELATED_VALUES) { this.parentState = ps; if (parentState != null) { IFoundSetInternal relation = parentState.getRelatedFoundSet(getRelationName()); if (relatedFoundset != relation) { clear(); if (relation != null && relation.getSize() > 0) { // try to load what is already in memory int count = relation.getSize(); // don't trigger an extra load. if (relation.hadMoreRows()) count--; // max the number of rows. if (count > maxValuelistRows) { count = maxValuelistRows; } IRecordInternal[] records = relation.getRecords(0, count); Object[][] data = new Object[records.length][]; for (int i = 0; i < records.length; i++) { data[i] = new Object[3]; if (valueList.getDataProviderID1() != null) data[i][0] = records[i].getValue(valueList.getDataProviderID1()); if (valueList.getDataProviderID2() != null) data[i][1] = records[i].getValue(valueList.getDataProviderID2()); if (valueList.getDataProviderID3() != null) data[i][2] = records[i].getValue(valueList.getDataProviderID3()); } String[] displayFormats = getDisplayFormat(); for (Object[] element : data) { DisplayString obj = CustomValueList.handleDisplayData(valueList, displayFormats, concatShowValues, showValues, element, application); if (obj != null && !obj.equals("")) //$NON-NLS-1$ { alDisplay.add(obj); alReal.add(CustomValueList.handleRowData(valueList, concatReturnValues, returnValues, element, application)); } } } } relatedFoundset = relation; } else clear(); } if (secondLookup != null) secondLookup.fill(ps); } public void setDoNotQuery(boolean b) { dontQuery = b; } public void addRow(Object real, Object display) { int index = alReal.indexOf(real); if (index == -1) { alReal.add(real); alDisplay.add(display); } else { alDisplay.set(index, display); } } private void fill(Object display, Object real) throws ServoyException, RemoteException { if (dontQuery || table == null) return; Object value = null; int values = 0; if (display != null) { values = showValues; value = display; } else { values = returnValues; value = real; } QuerySelect select = null; BaseQueryTable qTable = null; if (valueList.getDatabaseValuesType() == IValueListConstants.TABLE_VALUES) { select = DBValueList.createValuelistQuery(application, valueList, table); if (select != null) { qTable = select.getTable(); } } else { Relation[] relations = application.getFlattenedSolution().getRelationSequence(valueList.getRelationName()); Pair<QuerySelect, BaseQueryTable> pair = RelatedValueList.createRelatedValuelistQuery(application, valueList, relations, parentState); if (pair != null) { select = pair.getLeft(); qTable = pair.getRight(); } } if (select == null) { return; } String[] displayValues = null; String separator = valueList.getSeparator(); if (values == showValues && value != null && separator != null && !separator.equals("")) //$NON-NLS-1$ { if (values != 1 && values != 2 && values != 4) { // its a combination displayValues = Utils.stringSplit(value.toString(), separator); } } OrCondition where = new OrCondition(); if ((values & 1) != 0) { String dp1 = valueList.getDataProviderID1(); if (displayValues != null) { for (String displayValue : displayValues) { where.addCondition(new CompareCondition(IBaseSQLCondition.EQUALS_OPERATOR, DBValueList.getQuerySelectValue(table, qTable, dp1), getAsRightType(dp1, displayValue))); } } // also just add the complete value, for the possibility that it was a value with a separator. value = getAsRightType(dp1, value); where.addCondition(new CompareCondition(IBaseSQLCondition.EQUALS_OPERATOR, DBValueList.getQuerySelectValue(table, qTable, dp1), value)); } if ((values & 2) != 0) { String dp2 = valueList.getDataProviderID2(); if (displayValues != null) { for (String displayValue : displayValues) { where.addCondition(new CompareCondition(IBaseSQLCondition.EQUALS_OPERATOR, DBValueList.getQuerySelectValue(table, qTable, dp2), getAsRightType(dp2, displayValue))); } } value = getAsRightType(dp2, value); where.addCondition(new CompareCondition(IBaseSQLCondition.EQUALS_OPERATOR, DBValueList.getQuerySelectValue(table, qTable, dp2), value)); } if ((values & 4) != 0) { String dp3 = valueList.getDataProviderID3(); if (displayValues != null) { for (String displayValue : displayValues) { where.addCondition(new CompareCondition(IBaseSQLCondition.EQUALS_OPERATOR, DBValueList.getQuerySelectValue(table, qTable, dp3), getAsRightType(dp3, displayValue))); } } value = getAsRightType(dp3, value); where.addCondition(new CompareCondition(IBaseSQLCondition.EQUALS_OPERATOR, DBValueList.getQuerySelectValue(table, qTable, dp3), value)); } select.setCondition(SQLGenerator.CONDITION_SEARCH, where); FoundSetManager foundSetManager = ((FoundSetManager)application.getFoundSetManager()); String transaction_id = foundSetManager.getTransactionID(table.getServerName()); ArrayList<TableFilter> tableFilterParams = foundSetManager.getTableFilterParams(table.getServerName(), select); if (valueList.getUseTableFilter()) //apply name as filter on column valuelist_name { if (tableFilterParams == null) { tableFilterParams = new ArrayList<TableFilter>(); } tableFilterParams.add(new TableFilter("lookupValueList.nameFilter", table.getServerName(), table.getName(), table.getSQLName(), //$NON-NLS-1$ DBValueList.NAME_COLUMN, IBaseSQLCondition.EQUALS_OPERATOR, valueList.getName())); } SQLStatement trackingInfo = null; if (foundSetManager.getEditRecordList().hasAccess(table, IRepository.TRACKING_VIEWS)) { trackingInfo = new SQLStatement(ISQLActionTypes.SELECT_ACTION, table.getServerName(), table.getName(), null, null); trackingInfo.setTrackingData(select.getColumnNames(), new Object[][] { }, new Object[][] { }, application.getUserUID(), foundSetManager.getTrackingInfo(), application.getClientID()); } IDataSet set = application.getDataServer().performQuery(application.getClientID(), table.getServerName(), transaction_id, select, tableFilterParams, !select.isUnique(), 0, maxValuelistRows, IDataServer.VALUELIST_QUERY, trackingInfo); String[] displayFormats = getDisplayFormat(); for (int i = 0; i < set.getRowCount(); i++) { Object[] row = CustomValueList.processRow(set.getRow(i), showValues, returnValues); DisplayString obj = CustomValueList.handleDisplayData(valueList, displayFormats, concatShowValues, showValues, row, application); if (obj != null && !obj.equals("")) //$NON-NLS-1$ { alDisplay.add(obj); alReal.add(CustomValueList.handleRowData(valueList, concatReturnValues, returnValues, row, application)); } } } public boolean hasRealValues() { return showValues != returnValues; } public int realValueIndexOf(Object obj) { return realValueIndexOf(obj, true); } public int realValueIndexOf(Object obj, boolean addIfNotPresent) { if (obj == null) return -1; int index = alReal.indexOf(obj); if (index == -1) { try { fill(null, obj); } catch (Exception e) { Debug.error(e); } index = alReal.indexOf(obj); // If a real object still isn't there after the fill // add it to the list so that it won't be queried at again and again if (index == -1) { if (secondLookup != null) { try { index = secondLookup.realValueIndexOf(obj); if (index != -1) { return (index + 2) * -1; } } catch (Exception e) { Debug.error(e); } } if (addIfNotPresent) { index = alReal.size(); alReal.add(obj); alDisplay.add(CustomValueList.convertToString(obj, application)); } } else if (showValues != returnValues) { // Check if display wasn't stored in real/display together. Object displayValue = alDisplay.get(index); if (!(displayValue instanceof DisplayString)) { int index2 = alReal.indexOf(displayValue); if (index2 > -1) { alDisplay.remove(index2); alReal.remove(index2); } } } } return index; } private int searchDisplayArray(Object value) { for (int i = 0; i < alDisplay.size(); i++) { Object element = alDisplay.get(i); if (element == null) { if (value == null) return i; } else { if (element.equals(value)) { return i; } } } return -1; } public int indexOf(Object elem) { int index = searchDisplayArray(elem); if (index == -1 && elem != null && !"".equals(elem)) //$NON-NLS-1$ { try { fill(elem, null); } catch (Exception e) { Debug.error(e); } index = searchDisplayArray(elem); if (index == -1 && secondLookup != null) { index = secondLookup.indexOf(elem); if (index != -1) { index = (index + 2) * -1; // all values range from -2 > N } } } return index; } public void deregister() { clear(); if (tableListener != null && table != null) { ((FoundSetManager)application.getFoundSetManager()).removeTableListener(table, tableListener); Relation[] relations = application.getFlattenedSolution().getRelationSequence(valueList.getRelationName()); for (int i = 0; relations != null && i < relations.length - 1; i++) { try { ((FoundSetManager)application.getFoundSetManager()).removeTableListener(relations[i].getForeignTable(), tableListener); } catch (RepositoryException e) { Debug.error("Error deregistering table", e); //$NON-NLS-1$ } } tableListener = null; } } public void clear() { alReal.clear(); alDisplay.clear(); relatedFoundset = null; } public boolean getAllowEmptySelection() { return valueList.getAddEmptyValue() == IValueListConstants.EMPTY_VALUE_ALWAYS; } public int getSize() { return alReal.size(); } public Object getElementAt(int index) { if (index < -1 && secondLookup != null) { return secondLookup.getElementAt(index * -1 - 2); } Object object = alDisplay.get(index); if (object instanceof DisplayString) { return object.toString(); } return object; } public void addListDataListener(ListDataListener l) { listeners.put(l, null); } public void removeListDataListener(ListDataListener l) { listeners.remove(l); } public String[] getDisplayFormat() { String[] displayFormats = new String[3]; if (displayFormat == null || hasRealValues()) // format is linked to dataproviderid, so returning it could lead to incorrect display { if (table != null) { Column col1 = table.getColumn(valueList.getDataProviderID1()); if (col1 != null && col1.getColumnInfo() != null) displayFormats[0] = col1.getColumnInfo().getDefaultFormat(); Column col2 = table.getColumn(valueList.getDataProviderID2()); if (col2 != null && col2.getColumnInfo() != null) displayFormats[1] = col2.getColumnInfo().getDefaultFormat(); Column col3 = table.getColumn(valueList.getDataProviderID3()); if (col3 != null && col3.getColumnInfo() != null) displayFormats[2] = col3.getColumnInfo().getDefaultFormat(); } } else { for (int i = 0; i < displayFormats.length; i++) { displayFormats[i] = displayFormat; } } return displayFormats; } /** * Get real value list, used when this LookupValueList was used as fallback value list. */ public IValueList getRealValueList() { return ComponentFactory.getRealValueList(application, valueList, true, 0, null, null); } @Override public IDataProvider[] getDependedDataProviders() { if (valueList.getDatabaseValuesType() == IValueListConstants.GLOBAL_METHOD_VALUES) { return new IDataProvider[0]; } else if (valueList.getDatabaseValuesType() == IValueListConstants.RELATED_VALUES) { Relation[] relations = application.getFlattenedSolution().getRelationSequence(valueList.getRelationName()); if (relations != null && relations.length > 0) { try { return relations[relations.length - 1].getPrimaryDataProviders(application.getFlattenedSolution()); } catch (RepositoryException e) { Debug.error(e); } } return new IDataProvider[0]; } return null; } }