/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.coregui.client.admin.storage;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_CQL_PORT;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_RHQ_REPLICATION_FACTOR;
import static org.rhq.coregui.client.admin.storage.StorageNodeDatasourceField.FIELD_SYSTEM_AUTH_REPLICATION_FACTOR;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.smartgwt.client.types.Alignment;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.form.fields.FormItem;
import com.smartgwt.client.widgets.form.fields.IntegerItem;
import com.smartgwt.client.widgets.form.fields.PasswordItem;
import com.smartgwt.client.widgets.form.fields.RadioGroupItem;
import com.smartgwt.client.widgets.form.fields.SelectItem;
import com.smartgwt.client.widgets.form.fields.StaticTextItem;
import com.smartgwt.client.widgets.form.fields.TextItem;
import com.smartgwt.client.widgets.form.fields.events.ChangedEvent;
import com.smartgwt.client.widgets.form.fields.events.ChangedHandler;
import com.smartgwt.client.widgets.form.validator.IntegerRangeValidator;
import com.smartgwt.client.widgets.form.validator.IsIntegerValidator;
import com.smartgwt.client.widgets.form.validator.MatchesFieldValidator;
import com.smartgwt.client.widgets.form.validator.Validator;
import com.smartgwt.client.widgets.layout.LayoutSpacer;
import com.smartgwt.client.widgets.toolbar.ToolStrip;
import org.rhq.core.domain.cloud.StorageClusterSettings;
import org.rhq.coregui.client.CoreGUI;
import org.rhq.coregui.client.RefreshableView;
import org.rhq.coregui.client.components.form.EnhancedDynamicForm;
import org.rhq.coregui.client.components.form.StringLengthValidator;
import org.rhq.coregui.client.gwt.GWTServiceLookup;
import org.rhq.coregui.client.util.enhanced.EnhancedIButton;
import org.rhq.coregui.client.util.enhanced.EnhancedIButton.ButtonColor;
import org.rhq.coregui.client.util.enhanced.EnhancedToolStrip;
import org.rhq.coregui.client.util.enhanced.EnhancedVLayout;
import org.rhq.coregui.client.util.message.Message;
/**
* The component for editing the cluster wide configuration
*
* @author Jirka Kremser
*/
public class ClusterConfigurationEditor extends EnhancedVLayout implements RefreshableView {
private EnhancedDynamicForm schemaForm;
private EnhancedDynamicForm clusterForm;
private EnhancedDynamicForm deploymentForm;
private EnhancedDynamicForm credentialsForm;
private EnhancedDynamicForm regularSnapshotsForm;
private EnhancedIButton saveButton;
private StorageClusterSettings settings;
private final boolean readOnly;
private static String FIELD_GOSSIP_PORT = "gossip_port";
private static String FIELD_AUTOMATIC_DEPLOYMENT = "automatic_deployment";
private static String FIELD_USERNAME = "username";
private static String FIELD_PASSWORD = "password";
private static String FIELD_PASSWORD_VERIFY = "verify_password";
private static String FIELD_SNAPSHOTS_ENABLED = "snapshots_enabled";
private static String FIELD_SNAPSHOTS_SCHEDULE = "snapshots_schedule";
private static String FIELD_SNAPSHOTS_RETENTION = "snapshots_retention";
private static String FIELD_SNAPSHOTS_COUNT = "snapshots_count";
private static String FIELD_SNAPSHOTS_DELETION = "snapshots_deletion";
private static String FIELD_SNAPSHOTS_LOCATION = "snapshots_location";
public ClusterConfigurationEditor(boolean readOnly) {
super();
this.readOnly = readOnly;
}
private void fetchClusterSettings() {
GWTServiceLookup.getStorageService().retrieveClusterSettings(new AsyncCallback<StorageClusterSettings>() {
@Override
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError(
MSG.view_adminTopology_storageNodes_clusterSettings_message_cantLoad() + caught.getMessage(),
caught);
}
@Override
public void onSuccess(StorageClusterSettings settings) {
ClusterConfigurationEditor.this.settings = settings;
prepareForms();
}
});
}
private void save() {
updateSettings();
GWTServiceLookup.getStorageService().updateClusterSettings(settings, new AsyncCallback<Void>() {
public void onSuccess(Void result) {
Message msg = new Message(MSG.view_adminTopology_storageNodes_clusterSettings_message_updateSuccess(),
Message.Severity.Info);
CoreGUI.getMessageCenter().notify(msg);
}
public void onFailure(Throwable caught) {
CoreGUI.getErrorHandler().handleError(
MSG.view_adminTopology_storageNodes_clusterSettings_message_updateFail(), caught);
}
});
}
private List<FormItem> buildHeaderItems() {
List<FormItem> fields = new ArrayList<FormItem>();
fields.add(createHeaderTextItem(MSG.view_configEdit_property()));
fields.add(createHeaderTextItem(MSG.common_title_value()));
fields.add(createHeaderTextItem(MSG.common_title_description()));
return fields;
}
private StaticTextItem createHeaderTextItem(String value) {
StaticTextItem unsetHeader = new StaticTextItem();
unsetHeader.setValue(value);
unsetHeader.setShowTitle(false);
unsetHeader.setCellStyle("configurationEditorHeaderCell");
return unsetHeader;
}
private EnhancedDynamicForm buildForm(String groupTitle) {
EnhancedDynamicForm form = new EnhancedDynamicForm();
form.setHiliteRequiredFields(true);
form.setNumCols(3);
form.setCellPadding(5);
form.setColWidths(190, 220, "*");
form.setIsGroup(true);
form.setGroupTitle(groupTitle);
form.setWidth100();
form.setOverflow(Overflow.VISIBLE);
form.setExtraSpace(15);
return form;
}
@Override
protected void onDraw() {
super.onDraw();
refresh();
}
private void prepareForms() {
setWidth100();
schemaForm = buildForm("<div align='left' class='storageConfig'><div>" +
MSG.view_adminTopology_storageNodes_clusterSettings_replication_settings() + "</div>" + "<div>" +
MSG.view_adminTopology_storageNodes_clusterSettings_replication_settings_desc() + "</div>");
FormItemBuilder builder = new FormItemBuilder();
List<FormItem> rhqSchemaItems = builder.withName(FIELD_RHQ_REPLICATION_FACTOR.propertyName())
.withTitle(FIELD_RHQ_REPLICATION_FACTOR.title())
.withValue(Integer.toString(settings.getRhqReplicationFactor()))
.withDescription(MSG.view_adminTopology_storageNodes_field_rhq_replication_factor_desc())
.withReadOnlySetTo(true)
.build();
List<FormItem> systemAuthSchemaItems = builder.withName(FIELD_SYSTEM_AUTH_REPLICATION_FACTOR.propertyName())
.withTitle(FIELD_SYSTEM_AUTH_REPLICATION_FACTOR.title())
.withValue(Integer.toString(settings.getSystemAuthReplicationFactor()))
.withDescription(MSG.view_adminTopology_storageNodes_field_system_auth_replication_factor_desc())
.withReadOnlySetTo(true)
.build();
List<FormItem> items = buildHeaderItems();
items.addAll(rhqSchemaItems);
items.addAll(systemAuthSchemaItems);
schemaForm.setFields(items.toArray(new FormItem[items.size()]));
clusterForm = buildForm("<div align='left' class='storageConfig'><div>"
+ MSG.view_adminTopology_storageNodes_clusterSettings_clusterSettings() + "</div><div>"
+ MSG.view_adminTopology_storageNodes_clusterSettings_clusterSettings_desc() + "</div>");
items = buildHeaderItems();
Validator validator = new IsIntegerValidator();
// cql port field
builder = new FormItemBuilder();
List<FormItem> cqlPortItems = builder.withName(FIELD_CQL_PORT.propertyName()).withTitle(FIELD_CQL_PORT.title())
.withValue(String.valueOf(settings.getCqlPort()))
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_clusterSettings_cqlPort())
.withValidators(validator).build();
items.addAll(cqlPortItems);
// gossip port field
builder = new FormItemBuilder();
List<FormItem> gossipPortItems = builder.withName(FIELD_GOSSIP_PORT)
.withTitle(MSG.view_adminTopology_storageNodes_field_gossipPort())
.withValue(String.valueOf(settings.getGossipPort()))
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_clusterSettings_gossipPort())
.withValidators(validator).build();
items.addAll(gossipPortItems);
clusterForm.setFields(items.toArray(new FormItem[items.size()]));
deploymentForm = buildForm("<div align='left' class='storageConfig'><div>"
+ MSG.view_adminTopology_storageNodes_clusterSettings_deployments() + "</div><div>"
+ MSG.view_adminTopology_storageNodes_clusterSettings_deployments_desc() + "</div>");
FormItemBuilder.resetOddRow();
items = buildHeaderItems();
// automatic deployment field
builder = new FormItemBuilder();
List<FormItem> automaticDeploymentItems = builder.withName(FIELD_AUTOMATIC_DEPLOYMENT)
.withTitle(MSG.view_adminTopology_storageNodes_clusterSettings_deployments_autoDeploy_title())
.withValue(Boolean.toString(settings.getAutomaticDeployment()))
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_deployments_autoDeploy())
.withReadOnlySetTo(readOnly).build((FormItem) GWT.create(RadioGroupItem.class));
RadioGroupItem autoDeployRadio = (RadioGroupItem) automaticDeploymentItems.get(1);
autoDeployRadio.setVertical(false);
LinkedHashMap<String, String> values = new LinkedHashMap<String, String>(2);
values.put("true", "On");
values.put("false", "Off");
autoDeployRadio.setValueMap(values);
autoDeployRadio.setValue(settings.getAutomaticDeployment());
items.addAll(automaticDeploymentItems);
deploymentForm.setFields(items.toArray(new FormItem[items.size()]));
credentialsForm = buildForm("<div align='left' class='storageConfig'><div>"
+ MSG.view_adminTopology_storageNodes_clusterSettings_credentials() + "</div><div>"
+ MSG.view_adminTopology_storageNodes_clusterSettings_credentials_desc() + "</div>");
FormItemBuilder.resetOddRow();
items = buildHeaderItems();
// username field
StringLengthValidator usernameValidator = new StringLengthValidator(4, 100, false);
builder = new FormItemBuilder();
List<FormItem> usernameItems = builder.withName(FIELD_USERNAME).withTitle(MSG.common_title_username())
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_credentials_username())
.withValue(settings.getUsername()).withReadOnlySetTo(true).withValidators(usernameValidator).build();
items.addAll(usernameItems);
// password field
StringLengthValidator passwordValidator1 = new StringLengthValidator(6, 100, false);
passwordValidator1.setErrorMessage(MSG.view_adminTopology_storageNodes_clusterSettings_credentials_err1());
// due to SmartGWT bug that changes focus after each input (https://code.google.com/p/smartgwt/issues/detail?id=309)
passwordValidator1.setValidateOnChange(false);
builder = new FormItemBuilder();
List<FormItem> passwordItems = builder.withName(FIELD_PASSWORD).withTitle(MSG.common_title_password())
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_credentials_password())
.withValue(settings.getPasswordHash()).withReadOnlySetTo(readOnly).withValidators(passwordValidator1)
.withAttribute("autocomplete", "off").build((FormItem) GWT.create(PasswordItem.class));
items.addAll(passwordItems);
// password_verify field
builder = new FormItemBuilder();
passwordValidator1 = new StringLengthValidator(6, 100, false);
passwordValidator1.setErrorMessage(MSG.view_adminTopology_storageNodes_clusterSettings_credentials_err1());
MatchesFieldValidator passwordValidator2 = new MatchesFieldValidator();
passwordValidator2.setOtherField(FIELD_PASSWORD);
passwordValidator2.setErrorMessage(MSG.view_adminTopology_storageNodes_clusterSettings_credentials_err2());
// due to same bug in SmartGWT as above
passwordValidator1.setValidateOnChange(false);
passwordValidator2.setValidateOnChange(false);
List<FormItem> passwordVerifyItems = builder.withName(FIELD_PASSWORD_VERIFY)
.withTitle(MSG.view_adminTopology_storageNodes_clusterSettings_credentials_verify_title())
.withValue(settings.getPasswordHash())
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_credentials_verify())
.withReadOnlySetTo(readOnly).withValidators(passwordValidator1, passwordValidator2)
.withAttribute("autocomplete", "off").build((FormItem) GWT.create(PasswordItem.class));
items.addAll(passwordVerifyItems);
credentialsForm.setFields(items.toArray(new FormItem[items.size()]));
validator = new IntegerRangeValidator();
((IntegerRangeValidator) validator).setMin(0);
regularSnapshotsForm = buildForm("<div align='left' class='storageConfig'><div>"
+ MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement() + "</div><div>"
+ MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_desc() + "</div>");
FormItemBuilder.resetOddRow();
items = buildHeaderItems();
builder = new FormItemBuilder();
List<FormItem> snapshotsEnabledFormItems = builder.withName(FIELD_SNAPSHOTS_ENABLED)
.withTitle(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_enabled_title())
.withValue(Boolean.toString(settings.getRegularSnapshots().getEnabled()))
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_enabled_desc())
.build((FormItem) GWT.create(RadioGroupItem.class));
RadioGroupItem snapshotsEnabledRadio = (RadioGroupItem) snapshotsEnabledFormItems.get(1);
snapshotsEnabledRadio.setVertical(false);
values = new LinkedHashMap<String, String>(2);
values.put("true", "On");
values.put("false", "Off");
snapshotsEnabledRadio.setValueMap(values);
snapshotsEnabledRadio.setValue(settings.getRegularSnapshots().getEnabled());
items.addAll(snapshotsEnabledFormItems);
List<FormItem> snapshotsScheduleFormItems = builder.withName(FIELD_SNAPSHOTS_SCHEDULE)
.withTitle(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_schedule_title())
.withValue(settings.getRegularSnapshots().getSchedule())
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_schedule_desc())
.build();
items.addAll(snapshotsScheduleFormItems);
List<FormItem> snapshotsRetentionFormItems = new FormItemBuilder().withName(FIELD_SNAPSHOTS_RETENTION)
.withTitle(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_retention_title())
.withValue(settings.getRegularSnapshots().getRetention())
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_retention_desc())
.build(new SelectItem());
SelectItem snapshotsRetentionSelect = (SelectItem) snapshotsRetentionFormItems.get(1);
snapshotsRetentionSelect.setValueMap("Keep All", "Keep Last N", "Delete Older Than N days");
items.addAll(snapshotsRetentionFormItems);
List<FormItem> snapshotsCountFormItems = new FormItemBuilder().withName(FIELD_SNAPSHOTS_COUNT)
.withTitle(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_count_title())
.withValue(String.valueOf(settings.getRegularSnapshots().getCount()))
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_count_desc())
.withReadOnlySetTo("Keep All".equals(settings.getRegularSnapshots().getRetention()))
.withValidators(validator).build(new IntegerItem());
items.addAll(snapshotsCountFormItems);
final FormItem snapshotsCountNumber = snapshotsCountFormItems.get(1);
snapshotsRetentionSelect.addChangedHandler(new ChangedHandler() {
@Override
public void onChanged(ChangedEvent event) {
snapshotsCountNumber.setDisabled("Keep All".equals(event.getValue()));
}
});
List<FormItem> snapshotsDeletionFormItems = new FormItemBuilder().withName(FIELD_SNAPSHOTS_DELETION)
.withTitle(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_deletion_title())
.withValue(settings.getRegularSnapshots().getDeletion())
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_deletion_desc())
.build(new SelectItem());
SelectItem snapshotsDeletionSelect = (SelectItem) snapshotsDeletionFormItems.get(1);
snapshotsDeletionSelect.setValueMap("Delete", "Move");
items.addAll(snapshotsDeletionFormItems);
List<FormItem> snapshotsLocationFormItems = new FormItemBuilder().withName(FIELD_SNAPSHOTS_LOCATION)
.withTitle(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_location_title())
.withValue(settings.getRegularSnapshots().getLocation())
.withDescription(MSG.view_adminTopology_storageNodes_clusterSettings_snapshotManagement_location_desc())
.withRequiredSetTo(false)
.withReadOnlySetTo("Delete".equals(settings.getRegularSnapshots().getDeletion()))
.build();
items.addAll(snapshotsLocationFormItems);
final FormItem snapshotsLocationText = snapshotsLocationFormItems.get(1);
snapshotsDeletionSelect.addChangedHandler(new ChangedHandler() {
@Override
public void onChanged(ChangedEvent event) {
snapshotsLocationText.setDisabled("Delete".equals(event.getValue()));
}
});
regularSnapshotsForm.setFields(items.toArray(new FormItem[items.size()]));
LayoutSpacer spacer = new LayoutSpacer();
spacer.setWidth100();
ToolStrip toolStrip = buildToolStrip();
setMembers(schemaForm, clusterForm, deploymentForm, credentialsForm, regularSnapshotsForm, spacer, toolStrip);
clusterForm.validate();
deploymentForm.validate();
credentialsForm.validate();
regularSnapshotsForm.validate();
markForRedraw();
}
@Override
public void refresh() {
fetchClusterSettings();
}
private EnhancedToolStrip buildToolStrip() {
saveButton = new EnhancedIButton(MSG.common_button_save(), ButtonColor.BLUE);
saveButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent clickEvent) {
if (clusterForm.validate() && deploymentForm.validate() && credentialsForm.validate()
&& regularSnapshotsForm.validate()) {
SC.ask(MSG.view_adminTopology_storageNodes_clusterSettings_message_confirmation(),
new BooleanCallback() {
@Override
public void execute(Boolean value) {
if (value) {
save();
}
}
});
}
}
});
saveButton.setDisabled(readOnly);
EnhancedToolStrip toolStrip = new EnhancedToolStrip();
toolStrip.setWidth100();
toolStrip.setMembersMargin(5);
toolStrip.setLayoutMargin(5);
toolStrip.addMember(saveButton);
return toolStrip;
}
private StorageClusterSettings updateSettings() {
settings.setCqlPort(Integer.parseInt(clusterForm.getValueAsString(FIELD_CQL_PORT.propertyName())));
settings.setGossipPort(Integer.parseInt(clusterForm.getValueAsString(FIELD_GOSSIP_PORT)));
settings.setAutomaticDeployment(Boolean.parseBoolean(deploymentForm
.getValueAsString(FIELD_AUTOMATIC_DEPLOYMENT)));
// set the credentials only if there was a change
String wannabeUsername = credentialsForm.getValueAsString(FIELD_USERNAME);
settings.setUsername(wannabeUsername.equals(settings.getUsername()) ? null : wannabeUsername);
String wannabePassword = credentialsForm.getValueAsString(FIELD_PASSWORD);
settings.setPasswordHash(wannabePassword.equals(settings.getPasswordHash()) ? null : wannabePassword);
settings.getRegularSnapshots().setEnabled(
Boolean.parseBoolean(regularSnapshotsForm.getValueAsString(FIELD_SNAPSHOTS_ENABLED)));
settings.getRegularSnapshots().setSchedule(regularSnapshotsForm.getValueAsString(FIELD_SNAPSHOTS_SCHEDULE));
settings.getRegularSnapshots().setRetention(regularSnapshotsForm.getValueAsString(FIELD_SNAPSHOTS_RETENTION));
settings.getRegularSnapshots().setCount(
Integer.parseInt(regularSnapshotsForm.getValueAsString(FIELD_SNAPSHOTS_COUNT)));
settings.getRegularSnapshots().setDeletion(regularSnapshotsForm.getValueAsString(FIELD_SNAPSHOTS_DELETION));
settings.getRegularSnapshots().setLocation(regularSnapshotsForm.getValueAsString(FIELD_SNAPSHOTS_LOCATION));
return settings;
}
private static class FormItemBuilder {
private String name;
private String title;
private String value;
private String description;
private Validator[] validators;
private boolean readOnly;
private boolean required = true;
private Map<String, Object> attributes = new HashMap<String, Object>();
private static boolean oddRow = true;
public static void resetOddRow() {
oddRow = true;
}
public FormItemBuilder withAttribute(String name, Object value) {
attributes.put(name, value);
return this;
}
public FormItemBuilder withRequiredSetTo(boolean required) {
this.required = required;
return this;
}
public FormItemBuilder withName(String name) {
this.name = name;
return this;
}
public FormItemBuilder withTitle(String title) {
this.title = title;
return this;
}
public FormItemBuilder withValue(String value) {
this.value = value;
return this;
}
public FormItemBuilder withDescription(String description) {
this.description = description;
return this;
}
public FormItemBuilder withValidators(Validator... validators) {
this.validators = validators;
return this;
}
public FormItemBuilder withReadOnlySetTo(boolean readOnly) {
this.readOnly = readOnly;
return this;
}
public List<FormItem> build() {
return build(new TextItem());
}
// GWT doesn't support reflection by default, therefore this "hack"
public List<FormItem> build(FormItem valueItem) {
List<FormItem> fields = new ArrayList<FormItem>();
StaticTextItem nameItem = new StaticTextItem();
nameItem.setStartRow(true);
nameItem.setValue("<b>" + title + "</b>");
nameItem.setShowTitle(false);
nameItem.setCellStyle(oddRow ? "OddRow" : "EvenRow");
fields.add(nameItem);
valueItem.setName(name);
valueItem.setValue(value);
valueItem.setWidth(220);
if (validators != null && validators.length > 0) {
valueItem.setValidators(validators);
}
valueItem.setValidateOnChange(true);
valueItem.setAlign(Alignment.CENTER);
valueItem.setShowTitle(false);
valueItem.setRequired(required);
valueItem.setCellStyle(oddRow ? "OddRow" : "EvenRow");
valueItem.setDisabled(readOnly);
fields.add(valueItem);
StaticTextItem descriptionItem = new StaticTextItem();
descriptionItem.setValue(description);
descriptionItem.setShowTitle(false);
descriptionItem.setEndRow(true);
descriptionItem.setCellStyle(oddRow ? "OddRow" : "EvenRow");
fields.add(descriptionItem);
oddRow = !oddRow;
for (Map.Entry<String, Object> e : attributes.entrySet()) {
valueItem.setAttribute(e.getKey(), e.getValue());
}
return fields;
}
}
}