/* 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.scripting.solutionmodel; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import org.mozilla.javascript.annotations.JSFunction; import org.mozilla.javascript.annotations.JSGetter; import org.mozilla.javascript.annotations.JSSetter; import com.servoy.j2db.IApplication; import com.servoy.j2db.dataprocessing.FoundSetManager; import com.servoy.j2db.documentation.ServoyDocumented; import com.servoy.j2db.persistence.Column; import com.servoy.j2db.persistence.IDataProvider; import com.servoy.j2db.persistence.IPersist; import com.servoy.j2db.persistence.LiteralDataprovider; import com.servoy.j2db.persistence.Relation; import com.servoy.j2db.persistence.RelationItem; import com.servoy.j2db.persistence.RepositoryException; import com.servoy.j2db.persistence.ScriptNameValidator; import com.servoy.j2db.persistence.Table; import com.servoy.j2db.scripting.IConstantsObject; import com.servoy.j2db.scripting.IReturnedTypesProvider; import com.servoy.j2db.scripting.ScriptObjectRegistry; import com.servoy.j2db.solutionmodel.ISMRelation; import com.servoy.j2db.util.ScopesUtils; import com.servoy.j2db.util.UUID; /** * @author jcompagner */ @ServoyDocumented(category = ServoyDocumented.RUNTIME) public class JSRelation implements IJSParent<Relation>, IConstantsObject, ISMRelation { static { ScriptObjectRegistry.registerReturnedTypesProviderForClass(JSRelation.class, new IReturnedTypesProvider() { public Class< ? >[] getAllReturnedTypes() { return null; } }); } private boolean isCopy = false; private Relation relation; private final IApplication application; public JSRelation(Relation relation, IApplication application, boolean isNew) { this.relation = relation; this.application = application; this.isCopy = isNew; } /** * @see com.servoy.j2db.scripting.solutionmodel.IJSParent#getSupportChild() */ public Relation getSupportChild() { return relation; } /** * @see com.servoy.j2db.scripting.solutionmodel.IJSParent#getJSParent() */ public IJSParent< ? > getJSParent() { return null; } /** * Returns an array of JSRelationItem objects representing the relation criteria defined for this relation. * * @sample * var criteria = relation.getRelationItems(); * for (var i=0; i<criteria.length; i++) * { * var item = criteria[i]; * application.output('relation item no. ' + i); * application.output('primary column: ' + item.primaryDataProviderID); * application.output('operator: ' + item.operator); * application.output('foreign column: ' + item.foreignColumnName); * } * * @return An array of JSRelationItem instances representing the relation criteria of this relation. */ @JSFunction public JSRelationItem[] getRelationItems() { ArrayList<JSRelationItem> al = new ArrayList<JSRelationItem>(); Iterator<IPersist> allObjects = relation.getAllObjects(); while (allObjects.hasNext()) { IPersist persist = allObjects.next(); if (persist instanceof RelationItem) { al.add(new JSRelationItem((RelationItem)persist, this, isCopy)); } } return al.toArray(new JSRelationItem[al.size()]); } /** * Creates a new relation item for this relation. The primary dataprovider, the foreign data provider * and one relation operators (like '=' '!=' '>' '<') must be provided. * * @sample * var relation = solutionModel.newRelation('parentToChild', 'db:/example_data/parent_table', 'db:/example_data/child_table', JSRelation.INNER_JOIN); * relation.newRelationItem('another_parent_table_id', '=', 'another_child_table_parent_id'); * // for literals use a prefix * relation.newRelationItem(JSRelationItem.LITERAL_PREFIX + "'hello'",'=', 'mytextfield'); * * @param dataprovider The name of the primary dataprovider. * * @param operator The operator used to relate the primary and the foreign dataproviders. * * @param foreinColumnName The name of the foreign dataprovider. * * @return A JSRelationItem instance representing the newly added relation item. */ @JSFunction public JSRelationItem newRelationItem(String dataprovider, String operator, String foreinColumnName) { if (dataprovider == null) { throw new IllegalArgumentException("dataprovider cannot be null"); //$NON-NLS-1$ } if (foreinColumnName == null) { throw new IllegalArgumentException("foreinColumnName cannot be null"); //$NON-NLS-1$ } int validOperator = RelationItem.getValidOperator(operator, RelationItem.RELATION_OPERATORS, null); if (validOperator == -1) { throw new IllegalArgumentException("operator " + operator + " is not a valid relation operator"); //$NON-NLS-1$//$NON-NLS-2$ } checkModification(); try { IDataProvider primaryDataProvider = null; if (ScopesUtils.isVariableScope(dataprovider)) { primaryDataProvider = application.getFlattenedSolution().getGlobalDataProvider(dataprovider); } else if (dataprovider.startsWith(LiteralDataprovider.LITERAL_PREFIX)) { primaryDataProvider = new LiteralDataprovider(dataprovider); if (((LiteralDataprovider)primaryDataProvider).getValue() == null) { throw new IllegalArgumentException("primary data provider " + dataprovider + " is not a valid relation primary data provider"); //$NON-NLS-1$ } } else { primaryDataProvider = application.getFlattenedSolution().getDataProviderForTable( (Table)application.getFoundSetManager().getTable(relation.getPrimaryDataSource()), dataprovider); } if (primaryDataProvider == null) { throw new IllegalArgumentException("cant create relation item primary dataprovider not found: " + dataprovider); //$NON-NLS-1$ } IDataProvider dp = application.getFlattenedSolution().getDataProviderForTable( (Table)application.getFoundSetManager().getTable(relation.getForeignDataSource()), foreinColumnName); if (!(dp instanceof Column)) { throw new IllegalArgumentException("Foreign columnname " + foreinColumnName + " is not a valid column"); //$NON-NLS-1$ //$NON-NLS-2$ } RelationItem result = relation.createNewRelationItem(application.getFoundSetManager(), primaryDataProvider, validOperator, (Column)dp); if (result != null) return new JSRelationItem(result, this, isCopy); return null; } catch (RepositoryException e) { throw new RuntimeException(e); } } /** * Removes the desired relation item from the specified relation. * * @sample * var relation = solutionModel.newRelation('myRelation', 'db:/myServer/parentTable', 'db:/myServer/childTable', JSRelation.INNER_JOIN); * relation.newRelationItem('someColumn1', '=', 'someColumn2'); * relation.newRelationItem('anotherColumn', '=', 'someOtherColumn'); * relation.removeRelationItem('someColumn1', '=', 'someColumn2'); * var criteria = relation.getRelationItems(); * for (var i = 0; i < criteria.length; i++) { * var item = criteria[i]; * application.output('primary column: ' + item.primaryDataProviderID); * application.output('operator: ' + item.operator); * application.output('foreign column: ' + item.foreignColumnName); * } * * @param primaryDataProviderID the primary data provider (column) name * @param operator the operator * @param foreignColumnName the foreign column name */ @JSFunction public void removeRelationItem(String primaryDataProviderID, String operator, String foreignColumnName) { checkModification(); Iterator<IPersist> allObjects = relation.getAllObjects(); while (allObjects.hasNext()) { IPersist persist = allObjects.next(); if (persist instanceof RelationItem) { RelationItem ri = (RelationItem)persist; int validOperator = RelationItem.getValidOperator(operator, RelationItem.RELATION_OPERATORS, null); if (ri.getPrimaryDataProviderID().equals(primaryDataProviderID) && ri.getOperator() == validOperator && ri.getForeignColumnName().equals(foreignColumnName)) { relation.removeChild(persist); relation.setValid(true); break; } } } } /** * @clonedesc com.servoy.j2db.persistence.Relation#getAllowCreationRelatedRecords() * * @sample * var relation = solutionModel.newRelation('parentToChild', 'db:/example_data/parent_table', 'db:/example_data/child_table', JSRelation.INNER_JOIN); * relation.allowCreationRelatedRecords = true; */ @JSGetter public boolean getAllowCreationRelatedRecords() { return relation.getAllowCreationRelatedRecords(); } @JSSetter public void setAllowCreationRelatedRecords(boolean arg) { checkModification(); relation.setAllowCreationRelatedRecords(arg); } /** * @clonedesc com.servoy.j2db.persistence.Relation#getAllowParentDeleteWhenHavingRelatedRecords() * * @sample * var relation = solutionModel.newRelation('parentToChild', 'db:/example_data/parent_table', 'db:/example_data/child_table', JSRelation.INNER_JOIN); * relation.allowParentDeleteWhenHavingRelatedRecords = false; */ @JSGetter public boolean getAllowParentDeleteWhenHavingRelatedRecords() { return relation.getAllowParentDeleteWhenHavingRelatedRecords(); } @JSSetter public void setAllowParentDeleteWhenHavingRelatedRecords(boolean arg) { checkModification(); relation.setAllowParentDeleteWhenHavingRelatedRecords(arg); } /** * @clonedesc com.servoy.j2db.persistence.Relation#getDeleteRelatedRecords() * * @sample * var relation = solutionModel.newRelation('parentToChild', 'db:/example_data/parent_table', 'db:/example_data/child_table', JSRelation.INNER_JOIN); * relation.deleteRelatedRecords = true; */ @JSGetter public boolean getDeleteRelatedRecords() { return relation.getDeleteRelatedRecords(); } @JSSetter public void setDeleteRelatedRecords(boolean arg) { checkModification(); relation.setDeleteRelatedRecords(arg); } /** * The name of the server where the foreign table is located. * * @deprecated As of release 5.1, replaced by foreignDataSource property. */ @Deprecated public String js_getForeignServerName() { return relation.getForeignServerName(); } /** * The name of the foreign table. * * @deprecated As of release 5.1, replaced by foreignDataSource property. */ @Deprecated public String js_getForeignTableName() { return relation.getForeignTableName(); } /** * @clonedesc com.servoy.j2db.persistence.Relation#getForeignDataSource() * * @sampleas com.servoy.j2db.scripting.solutionmodel.JSRelation#getPrimaryDataSource() */ @JSGetter public String getForeignDataSource() { return relation.getForeignDataSource(); } @JSSetter public void setForeignDataSource(String arg) { // check syntax, do not accept invalid URIs if (arg != null) { try { new URI(arg); } catch (URISyntaxException e) { throw new RuntimeException("Invalid dataSource URI: '" + arg + "' :" + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ } } checkModification(); relation.setForeignDataSource(arg); } /** * @clonedesc com.servoy.j2db.persistence.Relation#getInitialSort() * * @sample * var relation = solutionModel.newRelation('parentToChild', 'db:/example_data/parent_table', 'db:/example_data/child_table', JSRelation.INNER_JOIN); * relation.initialSort = 'another_child_table_text asc'; */ @JSGetter public String getInitialSort() { return relation.getInitialSort(); } @JSSetter public void setInitialSort(String sort) { checkModification(); relation.setInitialSort(sort); } /** * @clonedesc com.servoy.j2db.persistence.Relation#getJoinType() * * @sampleas com.servoy.j2db.solutionmodel.ISMRelation#INNER_JOIN */ @JSGetter public int getJoinType() { return relation.getJoinType(); } @JSSetter public void setJoinType(int joinType) { checkModification(); relation.setJoinType(joinType); } /** * @clonedesc com.servoy.j2db.persistence.Relation#getName() * * @sample * var relation = solutionModel.newRelation('parentToChild', 'db:/example_data/parent_table', 'db:/example_data/child_table', JSRelation.INNER_JOIN); * relation.name = 'anotherName'; * var firstTab = tabs.newTab('firstTab', 'Child Form', childForm, relation); * firstTab.relationName = relation.name; */ @JSGetter public String getName() { return relation.getName(); } @JSSetter public void setName(String name) { checkModification(); try { relation.updateName(new ScriptNameValidator(application.getFlattenedSolution()), name); } catch (RepositoryException e) { throw new RuntimeException("Error updating the name from " + relation.getName() + " to " + name, e); //$NON-NLS-1$ //$NON-NLS-2$ } } /** * The name of the server where the primary table is located. * * @deprecated As of release 5.1, replaced by primaryDataSource property. * * @sample * var relation = solutionModel.newRelation('parentToChild', 'db:/example_data/parent_table', 'db:/example_data/child_table', JSRelation.INNER_JOIN); * relation.primaryTableName = 'another_parent_table'; * relation.primaryServerName = 'user_data'; * relation.foreignTableName = 'another_child_table'; * relation.foreignServerName = 'user_data'; */ @Deprecated public String js_getPrimaryServerName() { return relation.getPrimaryServerName(); } /** * The name of the primary table. * * @deprecated As of release 5.1, replaced by primaryDataSource property. */ @Deprecated public String js_getPrimaryTableName() { return relation.getPrimaryTableName(); } /** * @clonedesc com.servoy.j2db.solutionmodel.ISMRelation#getPrimaryDataSource() * * @sampleas com.servoy.j2db.solutionmodel.ISMRelation#getForeignDataSource() */ @JSGetter public String getPrimaryDataSource() { return relation.getPrimaryDataSource(); } @JSSetter public void setPrimaryDataSource(String arg) { // check syntax, do not accept invalid URIs if (arg != null) { try { new URI(arg); } catch (URISyntaxException e) { throw new RuntimeException("Invalid dataSource URI: '" + arg + "' :" + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ } } checkModification(); relation.setPrimaryDataSource(arg); } @Deprecated public void js_setForeignServerName(String name) { checkModification(); relation.setForeignServerName(name); } @Deprecated public void js_setForeignTableName(String name) { checkModification(); relation.setForeignTableName(name); } @Deprecated public void js_setPrimaryServerName(String name) { checkModification(); relation.setPrimaryServerName(name); } @Deprecated public void js_setPrimaryTableName(String name) { checkModification(); relation.setPrimaryTableName(name); } /** * Returns the UUID of the relation object * * @sample * var relation = solutionModel.newRelation('parentToChild', 'db:/example_data/parent_table', 'db:/example_data/child_table', JSRelation.INNER_JOIN); * application.output(relation.getUUID().toString()) */ @JSFunction public UUID getUUID() { return relation.getUUID(); } /** * */ public final void checkModification() { try { if (((FoundSetManager)application.getFoundSetManager()).getSQLGenerator().getCachedTableSQLSheet(relation.getForeignDataSource()).getRelatedSQLDescription( relation.getName()) != null) { throw new RuntimeException("Cant modify a relation that is already used in the application: " + relation.getName()); //$NON-NLS-1$ } } catch (Exception e) { throw new RuntimeException(e); } if (!isCopy) { relation = application.getFlattenedSolution().createPersistCopy(relation); isCopy = true; } relation.flushCashedItems(); } /** * @see java.lang.Object#toString() */ @SuppressWarnings("nls") @Override public String toString() { return "JSRelation[name:" + relation.getName() + ",items:" + Arrays.toString(getRelationItems()) + ']'; } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((relation == null) ? 0 : relation.hashCode()); return result; } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; JSRelation other = (JSRelation)obj; if (relation == null) { if (other.relation != null) return false; } else if (!relation.getUUID().equals(other.relation.getUUID())) return false; return true; } }