Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
AttributeEasyRowAdderController.java 24.29 KiB
/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <hr>
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* This file has been modified by the OpenOLAT community. Changes are licensed
* under the Apache 2.0 license as the original file.
*/
package org.olat.course.condition;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.ArrayUtils;
import org.apache.log4j.Level;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.form.flexible.FormItem;
import org.olat.core.gui.components.form.flexible.FormItemContainer;
import org.olat.core.gui.components.form.flexible.elements.SingleSelection;
import org.olat.core.gui.components.form.flexible.elements.TextElement;
import org.olat.core.gui.components.form.flexible.impl.Form;
import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
import org.olat.core.gui.components.form.flexible.impl.elements.FormLinkImpl;
import org.olat.core.gui.components.link.Link;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.translator.Translator;
import org.olat.core.id.UserConstants;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.activity.LogModule;
import org.olat.core.util.ArrayHelper;
import org.olat.core.util.Util;
import org.olat.course.condition.operators.OperatorManager;
import org.olat.shibboleth.ShibbolethModule;
import org.olat.shibboleth.util.AttributeTranslator;
import org.olat.user.UserManager;

/**
 * Description:<br>
 * A subform that implement the shibboleth easy mode config rows.
 * <P>
 * Initial Date: 23.10.2006 <br>
 * 
 * @author Lars Eberle (<a href="http://www.bps-system.de/">BPS Bildungsportal Sachsen GmbH</a>)
 * @author Florian Gnägi (<a href="http://www.frentix.com/">frentix GmbH</a>)
 */
public class AttributeEasyRowAdderController extends FormBasicController {

	private static final String EASYROWS = "easyrows";
	// identifyer prefixes
	private final static String PRE_ATTRIBUTE = "attribute_";
	private final static String PRE_OPERATOR = "operator_";
	private final static String PRE_VALUE_TEXT = "valuetxt_";
	private final static String PRE_VALUE_SELECTION = "valuesel_";
	private final String[] attrKeys;
	private final String preselectedAttribute;
	private final String preselectedAttributeValue;
	private final AttributeTranslator attributeTranslator;
	private final String[] operatorKeys;
	// the attribute name form elements
	private List<String> columnAttribute;
	// the operator form elements
	private List<String> columnOperator;
	// the value text input field form elements
	private List<String> columnValueText;
	// the alternative value selection drop down form elements
	private List<String> columnValueSelection;
	// add and remove row elements
	private List<String> columnAddRow;
	private List<String> columnRemoveRow;
	// global row increment counter
	private int rowCreationCounter = 0;
	//
	private boolean isinit = false;

	/**
	 * Constructor for a shibboleth attribute rule creator form.
	 * 
	 * @param ureq
	 * @param wControl
	 * @param parentForm
	 */
	public AttributeEasyRowAdderController(final UserRequest ureq, final WindowControl wControl, final Form parentForm) {
		super(ureq, wControl, FormBasicController.LAYOUT_CUSTOM, EASYROWS, parentForm);
		// Set custom translator to use translations from shibb module as well
		setTranslator(Util.createPackageTranslator(ShibbolethModule.class, ureq.getLocale(), getTranslator()));
		attributeTranslator = ShibbolethModule.getAttributeTranslator();
		attrKeys = getShibAttributes();
		preselectedAttribute = ShibbolethModule.getPreselectedAttributeKey(ShibbolethModule.CONF_OLATUSERMAPPING_INSTITUTIONALNAME);
		preselectedAttributeValue = ureq.getIdentity().getUser().getProperty(UserConstants.INSTITUTIONALNAME, getLocale());
		operatorKeys = OperatorManager.getRegisteredOperatorKeys(ShibbolethModule.getOperatorKeys());
		this.init();
	}

	/**
	 * Constructor for a log attribute rule creator form.
	 * 
	 * @param ureq
	 * @param wControl
	 * @param parentForm
	 */
	public AttributeEasyRowAdderController(final UserRequest ureq, final WindowControl wControl, final Form parentForm, final String[] attributes) {
		super(ureq, wControl, FormBasicController.LAYOUT_CUSTOM, EASYROWS, parentForm);
		if (attributes == null) { throw new IllegalArgumentException("attributes must not be null"); }
		attrKeys = attributes.clone();
		preselectedAttribute = UserConstants.INSTITUTIONALNAME;
		preselectedAttributeValue = ureq.getIdentity().getUser().getProperty(UserConstants.INSTITUTIONALNAME, getLocale());
		attributeTranslator = null;
		operatorKeys = OperatorManager.getRegisteredOperatorKeys(LogModule.getOperatorKeys());
		this.init();
	}

	/**
	 * Call this to cleanup everything
	 */
	public void cleanUp() {
		if (columnAttribute != null) {
			while (columnAttribute.size() > 0) {
				removeRowAt(0);
			}
		}
		isinit = false;
	}

	/**
	 * Call this to initialize the form
	 */
	public void init() {
		if (!isinit) {
			initForm(flc, this, null);
			isinit = true;
		}
	}

	/**
	 * @see org.olat.core.gui.components.form.flexible.FormBasicController#doDispose()
	 */
	@Override
	protected void doDispose() {
		// help GC
		columnAttribute = null;
		columnOperator = null;
		columnValueText = null;
		columnValueSelection = null;
		columnAddRow = null;
		columnRemoveRow = null;
	}

	/**
	 * @see org.olat.core.gui.components.form.flexible.FormDefaultController#formInnerEvent(org.olat.core.gui.components.form.flexible.FormItem, org.olat.core.gui.components.form.flexible.FormEvent)
	 */
	@Override
	@SuppressWarnings("unused")
	protected void formInnerEvent(final UserRequest ureq, final FormItem source, final FormEvent event) {
		if (isinit) {
			final String compName = source.getName();
			if (columnAddRow.contains(compName)) {
				// add link clicked
				final int clickPos = ((Integer) source.getUserObject()).intValue();
				addRowAt(clickPos + 1);
			} else if (columnRemoveRow.contains(compName)) {
				// remove link clicked
				final int clickPos = ((Integer) source.getUserObject()).intValue();
				removeRowAt(clickPos);
			}
			if (compName.startsWith(PRE_ATTRIBUTE)) {
				// one of the attribute selection drop boxes has been clicked
				final SingleSelection s1 = (SingleSelection) source;
				String attr;
				if (s1.isOneSelected()) {
					attr = s1.getSelectedKey();
				} else {
					// Special case: two new rows, modify the attribute on the second one
					// without touching the first one -> nothing selected on the first row
					// In this case we use the first one which is the visible one.
					attr = s1.getKey(0);
				}
				// update the value form element depending on the selected attribute
				final int clickPos = ((Integer) s1.getUserObject()).intValue();
				updateValueElementForAttribute(attr, clickPos, null);
			}
		}
		// update whole container to reflect changes.
		this.flc.setDirty(true);
	}

	@Override
	protected void event(final UserRequest ureq, final Controller source, final Event event) {
		if (event instanceof FormEvent) {
			final FormEvent fe = (FormEvent) event;
			final FormItem sourceItem = fe.getFormItemSource();
			final String compName = sourceItem.getName();
			if (columnAddRow.contains(compName)) {
				// add link clicked
				final int clickPos = ((Integer) sourceItem.getUserObject()).intValue();
				addRowAt(clickPos + 1);
			} else if (columnRemoveRow.contains(compName)) {
				// remove link clicked
				final int clickPos = ((Integer) sourceItem.getUserObject()).intValue();
				removeRowAt(clickPos);
			}
		}
	}

	/**
	 * @see org.olat.core.gui.components.form.flexible.FormBasicController#formOK(org.olat.core.gui.UserRequest)
	 */
	@Override
	protected void formOK(final UserRequest ureq) {
		// nothing to do
	}

	/**
	 * @see org.olat.core.gui.components.form.flexible.FormBasicController#initFormElements(org.olat.core.gui.components.form.flexible.api.FormItemContainer, org.olat.core.gui.control.Controller,
	 *      org.olat.core.gui.UserRequest)
	 */
	@Override
	@SuppressWarnings({ "unused", "unchecked" })
	protected void initForm(final FormItemContainer formLayout, final Controller listener, final UserRequest ureq) {
		//
		columnAttribute = new ArrayList();
		columnOperator = new ArrayList();
		columnValueText = new ArrayList();
		columnValueSelection = new ArrayList();
		columnAddRow = new ArrayList();
		columnRemoveRow = new ArrayList();
		// add a 0 row by default
		addRowAt(0);
		//
		((FormLayoutContainer) formLayout).contextPut("columnAttribute", columnAttribute);
		((FormLayoutContainer) formLayout).contextPut("columnOperator", columnOperator);
		((FormLayoutContainer) formLayout).contextPut("columnValueText", columnValueText);
		((FormLayoutContainer) formLayout).contextPut("columnValueSelection", columnValueSelection);
		((FormLayoutContainer) formLayout).contextPut("columnAddRow", columnAddRow);
		((FormLayoutContainer) formLayout).contextPut("columnRemoveRow", columnRemoveRow);
		//
	}

	/**
	 * Internal helper to get the current row count
	 * 
	 * @return
	 */
	private int getRowCount() {
		if (columnAttribute != null) {
			return columnAttribute.size();
		} else {
			return 0;
		}
	}

	/**
	 * Method to get a list of extended conditions represented in this form
	 * 
	 * @return
	 */
	public List<ExtendedCondition> getAttributeConditions() {
		final List<ExtendedCondition> le = new ArrayList<ExtendedCondition>();
		for (final Iterator<String> iterator = columnAttribute.iterator(); iterator.hasNext();) {
			final String aname = iterator.next();
			final String row = aname.replace(PRE_ATTRIBUTE, "");
			final SingleSelection attribute = (SingleSelection) flc.getFormComponent(PRE_ATTRIBUTE + row);
			final String condName = attribute.getSelectedKey();
			final SingleSelection operator = (SingleSelection) flc.getFormComponent(PRE_OPERATOR + row);
			final String condOperator = operator.getSelectedKey();
			String condValue = "";
			final SingleSelection valuessi = (SingleSelection) flc.getFormComponent(PRE_VALUE_SELECTION + row);
			if (valuessi.isVisible()) {
				if (valuessi.isOneSelected()) {
					condValue = valuessi.getSelectedKey();
				} else {
					// user did not actively select one, maybe because the first one was already the one he wanted. Use this one
					condValue = valuessi.getKey(0);
				}
			} else {
				final TextElement valuetei = (TextElement) flc.getFormComponent(PRE_VALUE_TEXT + row);
				condValue = valuetei.getValue();
			}
			le.add(new ExtendedCondition(condName, condOperator, condValue));
		}
		return le;
	}

	/**
	 * Method to initialize this form with the given extended conditions
	 * 
	 * @param cond
	 */
	public void setAttributeConditions(final List<ExtendedCondition> cond) {
		if (getRowCount() > 1) { throw new AssertException("more than one row found, don't know what do do"); }
		if (!isinit) { throw new AssertException("must call init() before calling setAttributeConditions() !"); }
		// use default initialized rows when no conditions have to be set
		if (cond.size() == 0) { return; }
		// remove default row from init process to make process of generating the
		// existing configuration easier
		removeRowAt(0);
		for (final Iterator iterator = cond.iterator(); iterator.hasNext();) {
			final ExtendedCondition extendedCondition = (ExtendedCondition) iterator.next();
			final int row = getRowCount();
			// now count is always one more than the row position, thus the next position to add a row
			// is the same as the current row count
			addRowAt(row);
			// set value in attribute selection
			SingleSelection ssi = (SingleSelection) flc.getFormComponent(columnAttribute.get(row));
			ssi.select(extendedCondition.getAttribute(), true);
			// set value in operator selection
			ssi = (SingleSelection) flc.getFormComponent(columnOperator.get(row));
			ssi.select(extendedCondition.getOperator().getOperatorKey(), true);
			// set the selectable values for this attribute if available and set the
			// preselected / predefined value.
			final String attribute = extendedCondition.getAttribute();
			updateValueElementForAttribute(attribute, row, extendedCondition.getValue());
		}
	}

	/**
	 * Internal method to add a new row at the given position
	 * 
	 * @param i
	 */
	private void addRowAt(final int rowPos) {
		// 1) Make room for the new row if the row is inserted between existing
		// rows. Increment the row id in the user object of the form elements and
		// move them in the form element arrays
		final Map formComponents = flc.getFormComponents();
		for (int move = rowPos + 1; move <= columnAttribute.size(); move++) {
			FormItem oldPos = (FormItem) formComponents.get(columnAttribute.get(move - 1));
			oldPos.setUserObject(Integer.valueOf(move));
			oldPos = (FormItem) formComponents.get(columnOperator.get(move - 1));
			oldPos.setUserObject(Integer.valueOf(move));
			oldPos = (FormItem) formComponents.get(columnValueText.get(move - 1));
			oldPos.setUserObject(Integer.valueOf(move));
			oldPos = (FormItem) formComponents.get(columnValueSelection.get(move - 1));
			oldPos.setUserObject(Integer.valueOf(move));
			oldPos = (FormItem) formComponents.get(columnAddRow.get(move - 1));
			oldPos.setUserObject(Integer.valueOf(move));
			oldPos = (FormItem) formComponents.get(columnRemoveRow.get(move - 1));
			oldPos.setUserObject(Integer.valueOf(move));
		}
		// 2) create the new row

		// get gui translated shib attributes - fallback is to use the key also as value
		final String[] guiTranslatedAttKeys = new String[attrKeys.length];
		for (int j = 0; j < attrKeys.length; j++) {
			final String key = attrKeys[j];
			// OLAT-5089: use translate(String key, String[] args, boolean fallBackToDefaultLocale) version
			// of Translator because that's the only one not
			String translated = getTranslator().translate(key, null, Level.OFF);
			if (translated.indexOf(Translator.NO_TRANSLATION_ERROR_PREFIX) == 0) {
				final Translator translator = UserManager.getInstance().getPropertyHandlerTranslator(getTranslator());
				final String prefix = "form.name.";
				// OLAT-5089: use translate(String key, String[] args, boolean fallBackToDefaultLocale) version
				// of Translator because that's the only one not
				translated = translator.translate(prefix + key, null, Level.OFF);
				if (translated.indexOf(Translator.NO_TRANSLATION_ERROR_PREFIX) == 0) {
					// could not translate this key, use key for non-translated values
					guiTranslatedAttKeys[j] = key;
				} else {
					guiTranslatedAttKeys[j] = translated;
				}
			} else {
				guiTranslatedAttKeys[j] = translated;
			}
		}
		// sort after the values
		ArrayHelper.sort(attrKeys, guiTranslatedAttKeys, false, true, true);
		// use this sorted keys-values
		final SingleSelection attribute = uifactory.addDropdownSingleselect(PRE_ATTRIBUTE + rowCreationCounter, null, flc, attrKeys, guiTranslatedAttKeys, null);
		attribute.setUserObject(Integer.valueOf(rowPos));
		attribute.addActionListener(this, FormEvent.ONCHANGE);
		columnAttribute.add(rowPos, attribute.getName());

		// 2b) Operator selector
		final String[] values = OperatorManager.getRegisteredAndAlreadyTranslatedOperatorLabels(getLocale(), operatorKeys);
		final FormItem operator = uifactory.addDropdownSingleselect(PRE_OPERATOR + rowCreationCounter, null, flc, operatorKeys, values, null);
		operator.setUserObject(Integer.valueOf(rowPos));
		columnOperator.add(rowPos, operator.getName());

		// 2c) Attribute value - can be either a text input field or a selection
		// drop down box - create both and hide the selection box
		//
		final TextElement valuetxt = uifactory.addTextElement(PRE_VALUE_TEXT + rowCreationCounter, null, -1, null, flc);
		valuetxt.setDisplaySize(25);
		valuetxt.setNotEmptyCheck("form.easy.error.attribute");
		valuetxt.setUserObject(Integer.valueOf(rowPos));
		columnValueText.add(rowPos, valuetxt.getName());
		// now the selection box
		final FormItem iselect = uifactory.addDropdownSingleselect(PRE_VALUE_SELECTION + rowCreationCounter, null, flc, new String[0], new String[0], null);
		iselect.setUserObject(Integer.valueOf(rowPos));
		iselect.setVisible(false);
		columnValueSelection.add(rowPos, iselect.getName());
		// 3) Init values for this row, assume selection of attribute at position 0
		if (ArrayUtils.contains(attrKeys, preselectedAttribute)) {
			attribute.select(preselectedAttribute, true);
			updateValueElementForAttribute(attribute.getKey(ArrayUtils.indexOf(attrKeys, preselectedAttribute)), rowPos, preselectedAttributeValue);
		} else {
			updateValueElementForAttribute(attribute.getKey(0), rowPos, null);
		}
		// 4) Add the 'add' and 'remove' buttons
		final FormLinkImpl addL = new FormLinkImpl("add_" + rowCreationCounter, "add." + rowPos, "+", Link.BUTTON_SMALL + Link.NONTRANSLATED);
		addL.setUserObject(Integer.valueOf(rowPos));
		flc.add(addL);
		columnAddRow.add(rowPos, addL.getName());
		//
		final FormLinkImpl removeL = new FormLinkImpl("remove_" + rowCreationCounter, "remove." + rowPos, "-", Link.BUTTON_SMALL + Link.NONTRANSLATED);
		removeL.setUserObject(Integer.valueOf(rowPos));
		flc.add(removeL);
		columnRemoveRow.add(rowPos, removeL.getName());

		// new row created, increment counter for unique form element id's
		rowCreationCounter++;
	}

	/**
	 * Internal method to remove the row at the given position.
	 * 
	 * @param clickPos The row to be removed
	 */
	private void removeRowAt(final int clickPos) {
		// 1) remove the form elements from the form container
		flc.remove(flc.getFormComponent(columnAttribute.get(clickPos)));
		columnAttribute.remove(clickPos);
		flc.remove(flc.getFormComponent(columnOperator.get(clickPos)));
		columnOperator.remove(clickPos);
		flc.remove(flc.getFormComponent(columnValueText.get(clickPos)));
		columnValueText.remove(clickPos);
		flc.remove(flc.getFormComponent(columnValueSelection.get(clickPos)));
		columnValueSelection.remove(clickPos);
		flc.remove(flc.getFormComponent(columnAddRow.get(clickPos)));
		columnAddRow.remove(clickPos);
		flc.remove(flc.getFormComponent(columnRemoveRow.get(clickPos)));
		columnRemoveRow.remove(clickPos);
		// 2) adjust all rows below the removed row. set the new row id in the user
		// object of the form element and move the element in the element arrays
		for (int move = clickPos; move < columnAttribute.size(); move++) {
			FormItem oldPos = flc.getFormComponent(columnAttribute.get(move));
			oldPos.setUserObject(Integer.valueOf(move));
			oldPos = flc.getFormComponent(columnOperator.get(move));
			oldPos.setUserObject(Integer.valueOf(move));
			oldPos = flc.getFormComponent(columnValueText.get(move));
			oldPos.setUserObject(Integer.valueOf(move));
			oldPos = flc.getFormComponent(columnValueSelection.get(move));
			oldPos.setUserObject(Integer.valueOf(move));
			oldPos = flc.getFormComponent(columnAddRow.get(move));
			oldPos.setUserObject(Integer.valueOf(move));
			oldPos = flc.getFormComponent(columnRemoveRow.get(move));
			oldPos.setUserObject(Integer.valueOf(move));
		}
	}

	/**
	 * Internal method to update a row's value element. This can be a text input box or a selection drop down depending on the shibboleth module configuration and the selected attribute. The method
	 * will set the given value as the users selected / inputed value
	 * 
	 * @param attribute The attribute key. Must not be NULL
	 * @param row the row ID
	 * @param value The value that should be selected / used in the text input field. Can be NULL.
	 */
	private void updateValueElementForAttribute(final String attribute, final int row, final String value) {
		String[] selectableKeys = getSelectableKeys(attribute);

		// Get the value text input and selection drop down form elements for this
		// row. Don't use the element name since there we have the global row
		// counter in the name and _not_ the current row id!
		final SingleSelection iselect = (SingleSelection) flc.getFormComponent(columnValueSelection.get(row));
		final TextElement tei = (TextElement) flc.getFormComponent(columnValueText.get(row));
		if (selectableKeys.length > 0) {
			attributeAsSelectBox(attribute, value, selectableKeys, iselect, tei);
		} else {
			attributeAsTextField(value, iselect, tei);
		}
	}

	private String[] getSelectableKeys(final String attribute) {
		String[] selectableKeys;
		if (getAttributeTranslator() == null) {
			selectableKeys = new String[0];
		} else {
			selectableKeys = getAttributeTranslator().getSelectableValuesForAttribute(attribute);
		}
		if (selectableKeys == null) {
			selectableKeys = new String[0];
		}
		// POSTCONDITION:selectableKeys != null
		return selectableKeys;
	}

	private void attributeAsTextField(final String value, final SingleSelection iselect, final TextElement tei) {
		// update text element visibility and the value
		tei.setValue(value);
		tei.setVisible(true);
		// and hide selection box
		iselect.setVisible(false);
	}

	private void attributeAsSelectBox(final String attribute, final String value, final String[] selectableKeys, final SingleSelection iselect, final TextElement tei) {
		// to set value on selection drop down we first need to remove it and create it as new one with new keys
		// create an object array with key - value pairs in it
		final String[] guiTranslatedKeys = new String[selectableKeys.length];
		for (int i = 0; i < selectableKeys.length; i++) {
			final String key = selectableKeys[i];
			final String translated = getTranslator().translate(attribute + "." + key);
			if (translated.indexOf(Translator.NO_TRANSLATION_ERROR_PREFIX) == 0) {
				// could not translate this key, use key for non-translated values
				guiTranslatedKeys[i] = key;
			} else {
				guiTranslatedKeys[i] = translated;
			}
		}
		// sort the key-value-pairs by value
		ArrayHelper.sort(selectableKeys, guiTranslatedKeys, false, true, true);

		// update keys and values now
		iselect.setKeysAndValues(selectableKeys, guiTranslatedKeys, null);
		// set user value
		if (value != null) {
			// check if stored value exists, otherwise don't select anything
			if (Arrays.asList(selectableKeys).contains(value)) {
				iselect.select(value, true);
			} else {
				// ups, maybe this value has been removed from the list? maybe a programming error?
				logWarn("could not select value::" + value + " for shibboleth attribute::" + attribute + " in course easy mode, not found in selectable list", null);
			}
		}
		iselect.setVisible(true);
		// set alternative text input field as non visible
		tei.setVisible(false);
	}

	private AttributeTranslator getAttributeTranslator() {
		return attributeTranslator;
	}

	/**
	 * Internal helper to create a sting array that contains all shibboleth attributes that can be selected in the drop down
	 * 
	 * @return String[] - will never returh null
	 */
	private String[] getShibAttributes() {
		if (ShibbolethModule.isEnableShibbolethLogins()) {
			final AttributeTranslator attTrans = getAttributeTranslator();
			final Set<String> attributes = attTrans.getTranslateableAttributes();
			final String[] outNames = new String[attributes.size()];
			int i = 0;
			for (final String attribute : attributes) {
				outNames[i] = attTrans.translateAttribute(attribute);
				i++;
			}
			return outNames;
		}
		return new String[0];
	}

	/**
	 * Checks if this form produces an error
	 * 
	 * @return true: has an error; false: is valid
	 */
	public boolean hasError() {
		for (final Iterator<String> iterator = columnValueText.iterator(); iterator.hasNext();) {
			final String name = iterator.next();
			final TextElement tei = (TextElement) flc.getFormComponent(name);
			if (tei.isVisible() && tei.getValue().trim().length() == 0) { return true; }
		}
		return false;
	}

	/**
	 * Get the form item that forms this subform
	 * 
	 * @return
	 */
	public FormItem getFormItem() {
		return this.flc;
	}

}