/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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 Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.pms.ui.dialog; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.pentaho.metadata.util.Util; import org.pentaho.pms.core.exception.PentahoMetadataException; import org.pentaho.pms.messages.Messages; import org.pentaho.pms.mql.PMSFormula; import org.pentaho.pms.schema.BusinessColumn; import org.pentaho.pms.schema.BusinessModel; import org.pentaho.pms.schema.BusinessTable; import org.pentaho.pms.schema.PhysicalTable; import org.pentaho.pms.schema.RelationshipMeta; import org.pentaho.pms.schema.SchemaMeta; import org.pentaho.pms.schema.concept.ConceptUtilityBase; import org.pentaho.pms.schema.concept.ConceptUtilityInterface; import org.pentaho.pms.ui.concept.editor.BusinessTableModel; import org.pentaho.pms.ui.concept.editor.PropertyNavigationWidget; import org.pentaho.pms.ui.concept.editor.PropertyWidgetManager2; import org.pentaho.pms.util.ObjectAlreadyExistsException; import org.pentaho.pms.util.UniqueList; public class BusinessTableDialog extends AbstractTableDialog implements SelectionListener { private static final Log logger = LogFactory.getLog(BusinessTableDialog.class); private Combo physicalTableText; private Label physicalTableLabel; private BusinessTable businessTable; private List<BusinessColumn> otherBusinessColumns = new ArrayList<BusinessColumn>(); HashMap<Object, Object> modificationsMap = new HashMap<Object, Object>(); protected void configureShell(final Shell shell) { super.configureShell(shell); shell.setText(Messages.getString("BusinessTableDialog.USER_BUSINESS_TABLE_PROPERTIES")); } public BusinessTableDialog(Shell parent, BusinessColumn businessColumn, SchemaMeta schemaMeta) { super(parent); BusinessTable originalBusinessTable = businessColumn.getBusinessTable(); businessTable = (BusinessTable) originalBusinessTable.clone(); BusinessTableModel tableModel = new BusinessTableModel(businessTable, schemaMeta.getActiveModel()); initModificationsMap(originalBusinessTable, businessTable); init(tableModel, schemaMeta, businessColumn); otherBusinessColumns = getOtherBusinessColumns(originalBusinessTable); } public BusinessTableDialog(Shell parent, BusinessTable originalBusinessTable, SchemaMeta schemaMeta) { super(parent); businessTable = (BusinessTable) originalBusinessTable.clone(); BusinessTableModel tableModel = new BusinessTableModel(businessTable, schemaMeta.getActiveModel()); initModificationsMap(originalBusinessTable, businessTable); init(tableModel, schemaMeta, businessTable); otherBusinessColumns = getOtherBusinessColumns(originalBusinessTable); } private void initModificationsMap(BusinessTable origBusinessTable, BusinessTable workingBusinessTable) { modificationsMap.put(workingBusinessTable, origBusinessTable); List workingBusinessColumns = workingBusinessTable.getBusinessColumns().getList(); for (Iterator workingIter = workingBusinessColumns.iterator(); workingIter.hasNext();) { BusinessColumn workingBusinessColumn = (BusinessColumn) workingIter.next(); List origBusinessColumns = origBusinessTable.getBusinessColumns().getList(); for (Iterator origIter = origBusinessColumns.iterator(); origIter.hasNext();) { BusinessColumn origBusinessColumn = (BusinessColumn) origIter.next(); if (origBusinessColumn.equals(workingBusinessColumn)) { modificationsMap.put(workingBusinessColumn, origBusinessColumn); break; } } } } protected void addColumnPressed() { PhysicalTable physicalTable = schemaMeta.findPhysicalTable(physicalTableText.getText()); if (physicalTable != null) { AddBusinessColumnDialog dialog = new AddBusinessColumnDialog(getShell(), tableModel, schemaMeta.getActiveLocale()); dialog.open(); } else { showMissingPhysicalTableError(); } } private void showMissingPhysicalTableError() { MessageDialog.openError(getShell(), "Error", "You must select a physical table first."); } protected void okPressed() { boolean hasErrors = popupValidationErrorDialogIfNecessary(); if ( !hasErrors ) { try { if ( lastSelection != null ) { String id = conceptIdText.getText(); if ( id.trim().length() == 0 || !Util.validateId( id ) ) { MessageDialog.openError( getShell(), Messages.getString( "General.USER_TITLE_ERROR" ), Messages.getString( "BusinessTableDialog.USER_ERROR_INVALID_ID", conceptIdText.getText() ) ); tableColumnTree.setSelection( new StructuredSelection( lastSelection ) ); conceptIdText.forceFocus(); conceptIdText.selectAll(); } else { // if selection is business column, also verify current column ids aren't being used if ( lastSelection instanceof BusinessColumn ) { validateBusinessColumnUniqueness(); } lastSelection.setId( conceptIdText.getText() ); updateOriginalBusinessTable(); super.okPressed(); } } else { updateOriginalBusinessTable(); super.okPressed(); } } catch ( ObjectAlreadyExistsException e ) { if ( logger.isErrorEnabled() ) { logger.error( "an exception occurred", e ); } MessageDialog.openError( getShell(), Messages.getString( "General.USER_TITLE_ERROR" ), Messages.getString( "ConceptUtilityBase.ERROR_0001_OBJECT_ID_EXISTS", conceptIdText.getText() ) ); } } } public void selectionChanged(SelectionChangedEvent e) { if (lastSelection != null && lastSelection.equals(((StructuredSelection) e.getSelection()).getFirstElement())) { return; } boolean hasErrors = popupValidationErrorDialogIfNecessary(); if (!hasErrors) { if (lastSelection != null) { try { String id = conceptIdText.getText(); if (id.trim().length() == 0) { MessageDialog.openError(getShell(), Messages.getString("General.USER_TITLE_ERROR"), Messages.getString( "BusinessTableDialog.USER_ERROR_INVALID_ID", conceptIdText.getText())); tableColumnTree.setSelection(new StructuredSelection(lastSelection)); conceptIdText.forceFocus(); conceptIdText.selectAll(); } else { // if selection is business column, also verify current column ids aren't being used if (lastSelection instanceof BusinessColumn) { validateBusinessColumnUniqueness(); } lastSelection.setId(conceptIdText.getText()); super.selectionChanged(e); } } catch (ObjectAlreadyExistsException e1) { if (logger.isErrorEnabled()) { logger.error("an exception occurred", e1); } MessageDialog.openError(getShell(), Messages.getString("General.USER_TITLE_ERROR"), Messages.getString( "ConceptUtilityBase.ERROR_0001_OBJECT_ID_EXISTS", conceptIdText.getText())); } } else { super.selectionChanged(e); } } else { // set selection back where it was if (!lastSelection.equals(((StructuredSelection) e.getSelection()).getFirstElement())) { tableColumnTree.setSelection(new StructuredSelection(lastSelection)); } } } /** * Returns all business columns * * @return a UniqueList of all business columns in this model */ public List<BusinessColumn> getOtherBusinessColumns(BusinessTable table) { List<BusinessColumn> columns = new ArrayList<BusinessColumn>(); for (int i = 0; i < schemaMeta.getActiveModel().nrBusinessTables(); i++) { BusinessTable businessTable = schemaMeta.getActiveModel().getBusinessTable(i); if (!table.equals(businessTable)) { for (int j = 0; j < businessTable.nrBusinessColumns(); j++) { columns.add(businessTable.getBusinessColumn(j)); } } } return columns; } private void validateBusinessColumnUniqueness() throws ObjectAlreadyExistsException { for (int i =0; i < otherBusinessColumns.size(); i++) { ConceptUtilityBase base = (ConceptUtilityBase) otherBusinessColumns.get(i); if (base != lastSelection && base.getId().equals(conceptIdText.getText())) { // This is a problem... throw new ObjectAlreadyExistsException(Messages.getString( "ConceptUtilityBase.ERROR_0001_OBJECT_ID_EXISTS", conceptIdText.getText())); //$NON-NLS-1$ } } UniqueList bizCols = businessTable.getBusinessColumns(); for (int i =0; i < bizCols.size(); i++) { ConceptUtilityBase base = (ConceptUtilityBase) bizCols.get(i); if (base != lastSelection && base.getId().equals(conceptIdText.getText())) { // This is a problem... throw new ObjectAlreadyExistsException(Messages.getString( "ConceptUtilityBase.ERROR_0001_OBJECT_ID_EXISTS", conceptIdText.getText())); //$NON-NLS-1$ } } } private void updateOriginalBusinessTable() { // Find the original physical table. BusinessTable origTable = null; for (Iterator iterator = modificationsMap.values().iterator(); iterator.hasNext() && (origTable == null);) { Object target = iterator.next(); if (target instanceof BusinessTable) { origTable = (BusinessTable) target; } } // Remove any columns from the original physical table that were removed from the working copy. ArrayList<Map.Entry> entriesToRemove = new ArrayList<Map.Entry>(); Set<Map.Entry<Object, Object>> entrySet = modificationsMap.entrySet(); for (Iterator iterator = entrySet.iterator(); iterator.hasNext();) { boolean found = false; Map.Entry entry = (Map.Entry) iterator.next(); if (entry.getKey() instanceof BusinessColumn) { ConceptUtilityInterface[] workingColumns = tableModel.getColumns(); for (int i = 0; (i < workingColumns.length) && !found; i++) { found = (workingColumns[i] == entry.getKey()); } if (!found) { BusinessColumn column = origTable.findBusinessColumn(((BusinessColumn) entry.getValue()).getId()); // check if any complex joins reference it and warn about them warnCannotUpdateRelations(getAffectedRelationships(column)); int index = origTable.indexOfBusinessColumn(column); origTable.removeBusinessColumn(index); entriesToRemove.add(entry); } } } entrySet.removeAll(entriesToRemove); // Update the remaining columns in the physical table with the working info. for (Iterator iterator = modificationsMap.entrySet().iterator(); iterator.hasNext();) { Map.Entry entry = (Map.Entry) iterator.next(); ConceptUtilityInterface origConcept = (ConceptUtilityInterface) entry.getValue(); ConceptUtilityInterface workingConcept = (ConceptUtilityInterface) entry.getKey(); if (!StringUtils.equals(origConcept.getId(), workingConcept.getId())) { List<BusinessModel.RelationFormulaUpdate> updated = updateComplexRelationships(origConcept, workingConcept); try { origConcept.setId(workingConcept.getId()); } catch (ObjectAlreadyExistsException e) { // This should not happen as this exception would already have been caught earlier... } if (updated != null) { // test updated relation after change checkRelationshipUpdates(updated); } } origConcept.getConcept().clearChildProperties(); origConcept.getConcept().getChildPropertyInterfaces().putAll( workingConcept.getConcept().getChildPropertyInterfaces()); } // Add any columns from the working table that don't exist in the original table. ConceptUtilityInterface[] workingColumns = tableModel.getColumns(); for (int i = 0; i < workingColumns.length; i++) { boolean found = false; for (Iterator iterator = modificationsMap.keySet().iterator(); iterator.hasNext() && !found;) { found = (workingColumns[i] == iterator.next()); } if (!found) { try { origTable.addBusinessColumn((BusinessColumn) workingColumns[i]); } catch (ObjectAlreadyExistsException e) { // This should not happen as this exception would already have been caught earlier... } } } } /** * @param updated */ private void checkRelationshipUpdates(List<BusinessModel.RelationFormulaUpdate> updated) { Map<RelationshipMeta, String> relationsToTest = new HashMap<RelationshipMeta, String>(); Collection<RelationshipMeta> badRelations = new HashSet<RelationshipMeta>(); for (BusinessModel.RelationFormulaUpdate update : updated) { if (update.getErrors().isEmpty()) { // still need to test it relationsToTest.put(update.getRelationship(), update.getFormulaBefore()); } else { // not good badRelations.add(update.getRelationship()); } } for (RelationshipMeta relation : relationsToTest.keySet()) { try { // did the update go well? relation.getComplexJoinFormula(schemaMeta.getActiveModel()).parseAndValidate(); } catch (Exception e) { // nope, undo relation.setComplexJoin(relationsToTest.get(relation)); badRelations.add(relation); } } warnCannotUpdateRelations(badRelations); } private void warnCannotUpdateRelations(Collection<RelationshipMeta> badRelations) { if (!badRelations.isEmpty()) { String msg = Messages.getString("BusinessTableDialog.CANNOT_UPDATE_COMPLEX_JOIN_DESC"); for (RelationshipMeta relationship : badRelations) { //just give out a warning that manual intervention is required msg += System.getProperty("line.separator"); msg += relationship.toString(); } MessageDialog.openError(getShell() , Messages.getString("BusinessTableDialog.CANNOT_UPDATE_COMPLEX_JOIN_TITLE"), msg); } } private Collection<RelationshipMeta> getAffectedRelationships(BusinessColumn column) { return schemaMeta.getActiveModel().getAffectedComplexRelationships(column); } private List<BusinessModel.RelationFormulaUpdate> updateComplexRelationships(ConceptUtilityInterface origConcept, ConceptUtilityInterface workingConcept) { List<BusinessModel.RelationFormulaUpdate> updated = null; if (origConcept instanceof BusinessTable) { return schemaMeta.getActiveModel().updateComplexRelationships((BusinessTable) origConcept, (BusinessTable) workingConcept); } else if (origConcept instanceof BusinessColumn) { return schemaMeta.getActiveModel().updateComplexRelationships((BusinessColumn) origConcept, (BusinessColumn) workingConcept); } return null; } protected void editConcept(ConceptUtilityInterface cu) { if (cu instanceof BusinessTable) { physicalTableLabel.setVisible(true); physicalTableText.setVisible(true); } else { physicalTableLabel.setVisible(false); physicalTableText.setVisible(false); } super.editConcept(cu); } protected Composite createConceptEditor() { Composite conceptEditor = new Composite(cardComposite, SWT.NONE); conceptEditor.setLayout(new FillLayout()); Group group = new Group(conceptEditor, SWT.SHADOW_OUT); group.setText("Properties"); group.setLayout(new GridLayout()); SashForm s0 = new SashForm(group, SWT.HORIZONTAL); s0.SASH_WIDTH = 5; Composite leftComposite = new Composite(s0, SWT.NONE); leftComposite.setLayout(new GridLayout()); Label wlId = new Label(leftComposite, SWT.RIGHT); wlId.setText(Messages.getString("PhysicalTableDialog.USER_NAME_ID")); //$NON-NLS-1$ conceptIdText = new Text(leftComposite, SWT.SINGLE | SWT.LEFT | SWT.BORDER); conceptIdText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); propertyNavigationWidget = new PropertyNavigationWidget(leftComposite, SWT.NONE); propertyNavigationWidget.setLayoutData(new GridData(GridData.FILL_BOTH)); Composite rightComposite = new Composite(s0, SWT.NONE); rightComposite.setLayout(new GridLayout()); physicalTableLabel = new Label(rightComposite, SWT.RIGHT); physicalTableLabel.setText(Messages.getString("BusinessTableDialog.USER_PHYSICAL_TABLE")); //$NON-NLS-1$ physicalTableText = new Combo(rightComposite, SWT.BORDER | SWT.READ_ONLY | SWT.V_SCROLL | SWT.H_SCROLL); physicalTableText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); int selectedIndex = -1; for (int i = 0; i < schemaMeta.nrTables(); i++) { physicalTableText.add(schemaMeta.getTable(i).getId()); if (null != businessTable.getPhysicalTable() && businessTable.getPhysicalTable().getId().equals(schemaMeta.getTable(i).getId())) { selectedIndex = i; } } if (-1 != selectedIndex) { physicalTableText.select(selectedIndex); } physicalTableText.addSelectionListener(this); propertyWidgetManager = new PropertyWidgetManager2(rightComposite, SWT.NONE, propertyEditorContext, schemaMeta .getSecurityReference()); propertyWidgetManager.setLayoutData(new GridData(GridData.FILL_BOTH)); GridData gridData = new GridData(GridData.FILL_BOTH); gridData.heightHint = 20; s0.setLayoutData(gridData); s0.setWeights(new int[] { 1, 2 }); if (tableModel.getId() != null) { conceptIdText.setText(tableModel.getId()); if (initialTableOrColumnSelection == null) { conceptIdText.selectAll(); } } return conceptEditor; } public void widgetDefaultSelected(SelectionEvent arg0) { } public void widgetSelected(SelectionEvent arg0) { tableModel.setParent(schemaMeta.findPhysicalTable(physicalTableText.getText())); } }