/*
* Lokomo OneCMDB - An Open Source Software for Configuration
* Management of Datacenter Resources
*
* Copyright (C) 2006 Lokomo Systems AB
*
* 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; either version 2 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
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* Lokomo Systems AB can be contacted via e-mail: info@lokomo.com or via
* paper mail: Lokomo Systems AB, Sv�rdv�gen 27, SE-182 33
* Danderyd, Sweden.
*
*/
package org.onecmdb.web;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import org.onecmdb.core.ErrorObject;
import org.onecmdb.core.IAttribute;
import org.onecmdb.core.IAttributeModifiable;
import org.onecmdb.core.ICcb;
import org.onecmdb.core.ICi;
import org.onecmdb.core.ICiModifiable;
import org.onecmdb.core.ICmdbTransaction;
import org.onecmdb.core.IModelService;
import org.onecmdb.core.IPath;
import org.onecmdb.core.IReferenceService;
import org.onecmdb.core.IRfcResult;
import org.onecmdb.core.ISession;
import org.onecmdb.core.ITicket;
import org.onecmdb.core.IType;
import org.onecmdb.core.IValue;
import org.onecmdb.core.Multiplicity;
import org.onecmdb.core.internal.OneCmdb;
import org.onecmdb.core.internal.model.ItemId;
import org.onecmdb.web.SiteCommand.MemoryObject;
import org.springframework.validation.BindException;
public class EditCiAction extends CiSiteAction implements FormAction {
public EditCiAction() {
this("editci");
setDisplayName("Edit");
}
protected EditCiAction(String name) {
super(name);
}
@Override
protected void handleNavigationalChange(BindException errors) {
ICi ci = getCi();
if (ci == null) {
errors.reject("REJECT", new String[] {"No CI yet bound!"}, "No CI yet bound!" );
return;
}
IModelService modelsvc = (IModelService) getCommand().getSession()
.getService(IModelService.class);
IType xsstring = modelsvc.getType("xs:string");
IType xsbool = modelsvc.getType("xs:boolean");
for (IAttribute attr : ci.getAttributes()) {
String key = "ATTR"+attr.getId();
if ( !getFormParams().containsKey(key) ) {
IValue v = attr.getValue();
setFormParam(attr, v);
}
}
// there are some special treatments
{
String expr = ci.getAlias();
IValue v = xsstring.parseString(expr);
String key = "ALIAS";
if (!getFormParams().containsKey(key)) {
getFormParams().put(key, v != null ? v : xsstring.getNullValue());
}
}
{
String expr = ci.getDisplayNameExpression();
IValue v = xsstring.parseString(expr);
String key = "DISPEXPR";
if (!getFormParams().containsKey(key)) {
getFormParams().put(key, v != null ? v : xsstring.getNullValue());
}
}
{
String expr = ci.getDescription();
IValue v = xsstring.parseString(expr);
String key = "DESCR";
if (!getFormParams().containsKey(key)) {
getFormParams().put(key, v != null ? v : xsstring.getNullValue());
}
}
{
boolean expr = ci.isBlueprint();
IValue v = xsbool.parseString(""+expr);
String key = "BLUEPRINT";
if (!getFormParams().containsKey(key)) {
getFormParams().put(key, v != null ? v : xsbool.getNullValue());
}
}
}
// M+ (can we use the stored object to fill in values
public boolean isRecallable() {
MemoryObject mem = (MemoryObject) getCommand().getGlobals().get("mem");
if (mem != null) {
ICi ci = getCi();
ICi rclci = mem.getCi();
if (ci != null && rclci != null) {
IPath<IType> ciPath = ci.getOffspringPath();
IPath<IType> rclciPath = rclci.getOffspringPath();
return (rclciPath.isParent(ciPath) || rclciPath.isSibling(ciPath));
}
}
return false;
}
public void validate(BindException errors) {
for (IAttribute attr : getCi().getAttributes()) {
String key = "ATTR"+attr.getId();
if ( getFormParams().containsKey(key) ) {
IValue v = (IValue) getFormParams().get(key);
IValue current = attr.getValue();
if ( (v != null && !v.equals(current))
|| (current != null && !current.equals(v)) )
{
IType type = attr.getValueType();
if (type == null) {
OneCmdb.getLogger(EditCiAction.class).warn("Cannot validate. No type attached on attribute '" + attr.getAlias() +"'");
} else {
ErrorObject error = type.validate(v);
}
}
}
}
if (getFormParams().containsKey("ALIAS")) {
IValue alias = (IValue) getFormParams().get("ALIAS");
String s = alias.getAsString();
if ( s == null || "".equals(s) ) {
errors.rejectValue("action.formParams[ALIAS]",
"ERROR_CANNOTBEEMPTY", new String[] { "Cannot be empty" }, "{0}");
} else if (s.indexOf(' ') != -1) {
errors.rejectValue("action.formParams[ALIAS]",
"ERROR_CANNOTCONTAINSPACES", new String[] {"Cannot contain spaces" }, "{0}");
}
}
}
public void apply(BindException errors) {
// clean up...
ISession session = getCommand().getSession();
ICcb ccb = (ICcb) session.getService(ICcb.class);
ICmdbTransaction tx = ccb.getTx(session);
ICi ci = getCi();
for (IAttribute attr : ci.getAttributes()) {
String attrKey = "ATTR"+attr.getId();
if (getFormParams().containsKey(attrKey)) {
IValue newValue = (IValue) getFormParams().get(attrKey);
IAttributeModifiable tpl = tx.getAttributeTemplate(attr);
if (attr.getMaxOccurs() != 1 && newValue == null) {
// TODO: think of
//tpl.delete();
} else {
IValue currentValue = attr.getValue();
if ( (newValue != null && !newValue.equals(currentValue))
|| (currentValue != null && !currentValue.equals(newValue)) ) {
tpl.setValue(newValue);
}
}
}
}
final ICiModifiable tpl = tx.getTemplate(ci);
{
String attrKey = "ALIAS";
if (getFormParams().containsKey(attrKey)) {
IValue newValue = (IValue) getFormParams().get(attrKey);
tpl.setAlias(newValue.getAsString());
}
}
{
String attrKey = "DISPEXPR";
if (getFormParams().containsKey(attrKey)) {
IValue newValue = (IValue) getFormParams().get(attrKey);
tpl.setDisplayNameExpression(newValue.getAsString());
}
}
{
String attrKey = "DESCR";
if (getFormParams().containsKey(attrKey)) {
IValue newValue = (IValue) getFormParams().get(attrKey);
tpl.setDescription(newValue.getAsString());
}
}
{ // toggle the blueprint status?
String attrKey = "BLUEPRINT";
if (getFormParams().containsKey(attrKey)) {
IModelService modelsvc = (IModelService) getCommand().getSession()
.getService(IModelService.class);
IType xsbool = modelsvc.getType("xs:boolean");
IValue newValue = (IValue) getFormParams().get(attrKey);
if (newValue == null) {
newValue = xsbool.parseString("false");
}
if (!newValue.getValueType().equals(xsbool)) {
newValue = xsbool.parseString(newValue.getAsString());
}
IValue oldValue; {
boolean b = ci.isBlueprint();
oldValue = xsbool.parseString(""+b);
}
if (!oldValue.equals(newValue)) {
// toggle the value
tpl.setIsBlueprint((Boolean) newValue.getAsJavaObject());
}
}
}
ITicket ticket = ccb.submitTx(tx);
IRfcResult result = ccb.waitForTx(ticket);
if(result.isRejected()) {
errors.reject("REJECTED", new String[] { result.getRejectCause() }, "{0}" );
}
else {
forward();
clearCache();
}
}
protected void forward() {
SiteAction current = getCommand().getAction();
CiSiteAction action = (CiSiteAction) getCommand().getActionMap().get("viewci");
action.clearCache();
action.setParams(current.getParams());
for (SiteAction h : getCommand().getHistory()) {
if (h.equals(action)) {
((CiSiteAction) h).clearCache();
}
}
getCommand().setAction("viewci");
}
public final void setReturnTo(String navigate) {
if (navigate == null) {
getParams().remove("RETURNTO");
} else
getParams().put("RETURNTO", navigate);
}
protected final String getReturnTo() {
return (String) getParams().get("RETURNTO");
}
public final void setReturnParam(String paramKey) {
if (paramKey == null) {
getParams().remove("RETURNPARAM");
} else
getParams().put("RETURNPARAM", paramKey);
}
protected final String getReturnParam() {
return (String) getParams().get("RETURNPARAM");
}
public final void setReturnHash(String returnHash) {
if (returnHash == null) {
getParams().remove("RETURNHASH");
} else
getParams().put("RETURNHASH", returnHash);
}
/**
* Empty implementation. Nothing is really changed.
*/
public void cancel(BindException errors) {
// can we undo changes, then we sould implement such a feature
ISession session = getCommand().getSession();
ICcb ccb = (ICcb) session.getService(ICcb.class);
ICmdbTransaction tx = ccb.getTx(session);
ICi ci = getCi();
boolean submit = false;
for (IAttribute attr : ci.getAttributes()) {
String attrKey = "ATTR"+attr.getId();
if (getFormParams().containsKey(attrKey)) {
IValue newValue = (IValue) getFormParams().get(attrKey);
IAttributeModifiable tpl = tx.getAttributeTemplate(attr);
if (attr.getMaxOccurs() != 1 && newValue == null) {
tpl.delete();
submit = true;
}
}
}
if (submit) {
ITicket ticket = ccb.submitTx(tx);
IRfcResult result = ccb.waitForTx(ticket);
if(result.isRejected()) {
String applyExpr = "action.formParams["+ getName() + "]";
errors.reject("REJECTED", new String[] { result.getRejectCause() }, "{0}" );
}
}
forward();
}
/**
* TODO: Break up into classes (command pattern), instead of the mega switch
*/
@Override
protected void handleFormChange(FormChange change, BindException errors) {
if ("addValue".equals(change.getOperation())) {
addValue(change, errors);
}
else if ("removeValue".equals(change.getOperation())) {
removeValue(change, errors);
}
else if ("addAttr".equals(change.getOperation())) {
addAttr(change, errors);
}
else if ("deleteAttr".equals(change.getOperation())) {
deleteAttr(change, errors);
}
else if ("rclCi".equals(change.getOperation())) {
recallMem(change, errors);
}
else {
super.handleFormChange(change, errors);
}
}
private void addAttr(FormChange change, BindException errors) {
ICi ci = getCi();
ISession session = getCommand().getSession();
IModelService modelsvc = (IModelService) session.getService(IModelService.class);
ICcb ccb = (ICcb) session.getService(ICcb.class);
ICmdbTransaction tx = ccb.getTx(session);
{
final IValue alias;
IValue dispExpr = change.getParamValue("dispExpr");
if (dispExpr == null || "".equals(dispExpr.getAsString())) {
errors.rejectValue(change.getParamExpr("dispExpr"),
"ERROR_CANNOTBEEMPTY", "Cannot be empty");
alias = null;
} else {
// derive an alias
IType xsstring = modelsvc.getType("xs:string");
String s = dispExpr.getAsString();
s = s.replaceAll("\\W", " ");
String t = "";
for (String p : s.split(" ")) {
p = p.substring(0, 1).toUpperCase() +
(p.length() > 1 ? p.substring(1) : "");
t += p;
}
s = t;
if ("".equals(s)) {
errors.rejectValue(change.getParamExpr("dispExpr"),
"ERROR_CANNOTBEEMPTY", "Too few letters" );
alias = null;
} else {
s = s.substring(0, 1).toLowerCase() +
(s.length() > 1 ? s.substring(1) : "");
IValue prefix = change.getParamValue("prefix");
if (!prefix.isNullValue() && !"".equals(prefix.getAsString()) ) {
s = prefix.getAsString() + "_" + s;
}
alias = xsstring.parseString(s);
}
}
IValue description = change.getParamValue("description");
final IType type; {
IValue _type = change.getParamValue("type");
String s = _type.getAsString();
if (s.startsWith("ID:") && s.length() > 3) {
s = s.substring(3);
ItemId typeId = new ItemId(s);
type = modelsvc.getType(typeId);
} else if (!s.startsWith("ID:") && s.length() > 0) {
type = modelsvc.getType(s);
} else {
type = null;
}
}
if (type == null) {
errors.rejectValue(change.getParamExpr("type"),
"ERROR_CANNOTBEEMPTY", "Cannot be empty" );
}
final IType reftype; {
IReferenceService refsvc = (IReferenceService) session.getService(IReferenceService.class);
IValue _refype = change.getParamValue("typeref");
if (_refype == null) {
reftype = null;
} else {
String s = _refype.getAsString();
if (s.startsWith("ID:") && s.length() > 3) {
s = s.substring(3);
ItemId reftypeId = new ItemId(s);
reftype = refsvc.getRefType(reftypeId);
} else if (!s.startsWith("ID:") && s.length() > 0) {
reftype = refsvc.getRefType(s);
} else {
reftype = null;
}
}
}
Multiplicity mult = null;
{
int minI = -1; {
// these arrives as strings
IValue min = change.getParamValue("mult.min");
if (min == null || min.getAsJavaObject() == null || min.getAsJavaObject().equals("")) {
errors.rejectValue(change.getParamExpr("mult.min"),
"ERROR_CANNOTBEEMPTY", "Lower bound can not be empty" );
}
else {
String minS = (String) min.getAsJavaObject();
try {
minI = Integer.parseInt(minS);
} catch (NumberFormatException e) {
errors.rejectValue(change.getParamExpr("mult.min"),
"REJECT", "Invalid number for lower bound: " + minS);
}
}
}
int maxI = -1; {
// these arrives as strings
IValue max = change.getParamValue("mult.maxInf");
if (max == null) {
max = change.getParamValue("mult.max");
}
if (max == null || max.getAsJavaObject() == null || max.getAsJavaObject().equals("")) {
errors.rejectValue(change.getParamExpr("mult.max"),
"ERROR_CANNOTBEEMPTY", "Upper bound cannot be empty");
}
else {
String maxS = (String) max.getAsJavaObject();
try {
maxI = "n".equalsIgnoreCase(maxS) ? Multiplicity.UNBOUND :
Integer.parseInt(maxS);
} catch (NumberFormatException e) {
errors.rejectValue(change.getParamExpr("multMax"),
"REJECT", "Invalid number for upper bound: " + maxS);
}
}
}
if (!errors.hasErrors()) {
mult = new Multiplicity(minI, maxI);
}
}
if (errors.hasErrors()) {
return;
}
ICiModifiable tpl = tx.getTemplate(ci);
IAttributeModifiable newAttr = tpl.createAttribute(alias.getAsString(),
type, reftype,
mult.getMin(), mult.getMax(), null);
newAttr.setDisplayNameExpression(dispExpr.getAsString());
newAttr.setDescription(description.getAsString());
ITicket ticket = ccb.submitTx(tx);
IRfcResult result = ccb.waitForTx(ticket);
if (result.isRejected()) {
errors.rejectValue(change.getChangeExpr(), "REJECT",
new String[] {result.getRejectCause()},
result.getRejectCause());
return;
}
// clear the attributes...
for (Iterator<Entry<String, Object /*IValue */>> entryIter = getFormParams().entrySet().iterator();
entryIter.hasNext(); ) {
change.getChangeExpr();
Entry<String, Object /*IValue*/> entry = entryIter.next();
if (entry.getKey().startsWith("addAttr(")) {
entryIter.remove();
}
}
errors.rejectValue(change.getChangeExpr(), "SUCCESS",
new String[] {"Attribute added"},
"Attribute added");
handleNavigationalChange(errors);
}
}
private void recallMem(FormChange change, BindException errors) {
String alias = change.getArgs().get(0);
MemoryObject mem = (MemoryObject) getCommand().getGlobals().get("mem");
ICi memci = mem.getCi();
// put the stored CI's values
handleRecallFrom(memci, errors);
}
private void deleteAttr(FormChange change, BindException errors) {
String alias = change.getArgs().get(0);
ICi ci = getCi();
ISession session = getCommand().getSession();
ICcb ccb = (ICcb) session.getService(ICcb.class);
ICmdbTransaction tx = ccb.getTx(session);
{
for (IAttribute attr : ci.getAttributes()) {
if (alias.equals(attr.getAlias())) {
IAttributeModifiable tpl = tx.getAttributeTemplate(attr);
tpl.delete();
}
}
}
ITicket ticket = ccb.submitTx(tx);
IRfcResult result = ccb.waitForTx(ticket);
if (result.isRejected()) {
errors.rejectValue(change.getChangeExpr(), "REJECT",
new String[] {result.getRejectCause()},
result.getRejectCause());
}
}
private void removeValue(FormChange change, BindException errors) {
ItemId attrId = new ItemId(change.getArgs().get(0));
ISession session = getCommand().getSession();
ICcb ccb = (ICcb) session.getService(ICcb.class);
ICmdbTransaction tx = ccb.getTx(session);
{
ICi ci = getCi();
IAttribute attr = ci.getAttributeWithId(attrId);
IAttributeModifiable tpl = tx.getAttributeTemplate(attr);
tpl.delete();
}
ITicket ticket = ccb.submitTx(tx);
IRfcResult result = ccb.waitForTx(ticket);
if (result.isRejected()) {
errors.rejectValue(change.getChangeExpr(), "REJECT", result.getRejectCause());
}
}
private void addValue(FormChange change, BindException errors) {
ISession session = getCommand().getSession();
IModelService modelsvc = (IModelService) session.getService(IModelService.class);
ICcb ccb = (ICcb) session.getService(ICcb.class);
final ICi ci = getCi();
final String attrKey = change.getArgs().get(0);
final IAttribute attr = getAttribute(attrKey);
IValue newValue = null; {
newValue = (IValue) getFormParams().get(attrKey);
if ( !(newValue instanceof ICi) && newValue != null ) {
String s = newValue.getAsString();
if (s.startsWith("ID:") && s.length() > 3) {
s = s.substring(3);
ItemId typeId = new ItemId(s);
newValue = modelsvc.find(typeId);
} else if (!s.startsWith("ID:")) {
// a simple type is what whar we should bind to...
// must fetch the ``type'' we are adding
IType attrType = null; {
for (IAttribute addable : ci.getAddableAttributes()) {
if (addable.getAlias().equals(attr.getAlias())) {
attrType = addable.getValueType();
break;
}
}
}
newValue = attrType.fromValue(newValue);
}
}
}
if (newValue == null) {
errors.rejectValue(change.getChangeExpr(), "NOVALUE",
"A value must must be selected");
} else {
ICmdbTransaction tx = ccb.getTx(session);
// set the value on the exisiting attribute
// IAttributeModifiable attrtpl = tx.getAttributeTemplate(attr);
// attrtpl.setValue(newValue);
// add a new slot
ICiModifiable citpl = tx.getTemplate(ci);
IAttributeModifiable newSlot = citpl.addAttribute(attr.getAlias());
newSlot.setValue(newValue);
ITicket ticket = ccb.submitTx(tx);
IRfcResult result = ccb.waitForTx(ticket);
if (result.isRejected()) {
errors.rejectValue(change.getChangeExpr(), "REJECT",
new String[] {result.getRejectCause()},
result.getRejectCause());
} else {
getFormParams().remove(attrKey);
}
}
}
private void handleRecallFrom(ICi memci, BindException errors) {
IModelService modelsvc = (IModelService) getCommand().getSession()
.getService(IModelService.class);
IType xsstring = modelsvc.getType("xs:string");
IType xsbool = modelsvc.getType("xs:boolean");
final ICi ci = getCi();
Set<String> processed = new HashSet<String>();
// {{{ copy
// copy all attributes compatible, from the memory stored
// CI to the current, hold by this action
for (IAttribute memAttr : memci.getAttributes()) {
String attrAlias = memAttr.getAlias();
if (processed.contains(attrAlias))
continue;
processed.add(attrAlias);
List<IAttribute> memAttrs = memci.getAttributesWithAlias(attrAlias);
List<IAttribute> attrs = ci.getAttributesWithAlias(attrAlias);
if (memAttr.getMaxOccurs() == 1 && memAttr.getMinOccurs() == 1 && attrs.size() == 1) {
// single valued
IAttribute attr = attrs.get(0);
memAttr = memAttrs.get(0);
setFormParam(attr, memAttr.getValue());
} else {
// replace all attributes already set
Iterator<IAttribute> memAttrIter = memAttrs.iterator();
Iterator<IAttribute> attrIter = attrs.iterator();
ISession session = getCommand().getSession();
ICcb ccb = (ICcb) session.getService(ICcb.class);
ICmdbTransaction tx = ccb.getTx(session);
while (memAttrIter.hasNext()) {
IAttribute recallAttr = memAttrIter.next();
final IAttributeModifiable attrtpl; {
if (attrIter.hasNext()) {
// replace the current value
IAttribute replaceAttr = attrIter.next();
attrtpl = tx.getAttributeTemplate(replaceAttr);
} else {
if (canAdd(ci, recallAttr)) {
ICiModifiable citpl = tx.getTemplate(ci);
attrtpl = citpl.addAttribute(recallAttr.getAlias());
} else {
attrtpl = null;
}
}
}
if (attrtpl != null) {
attrtpl.setValue(recallAttr.getValue());
}
}
while (attrIter.hasNext()) {
IAttribute deleteAttr = attrIter.next();
IAttributeModifiable deltpl = tx.getAttributeTemplate(deleteAttr);
deltpl.delete();
}
ITicket ticket = ccb.submitTx(tx);
IRfcResult result = ccb.waitForTx(ticket);
if (result.isRejected()) {
errors.rejectValue(null, "REJECT",
new String[] {result.getRejectCause()},
result.getRejectCause());
}
}
}
// }}}
//setFormParam("ALIAS", xsstring, memci.getAlias());
setFormParam("DISPEXPR", xsstring, memci.getDisplayNameExpression());
setFormParam("DESCR", xsstring, memci.getDescription());
setFormParam("BLUEPRINT", xsbool, memci.isBlueprint());
}
private boolean canAdd(final ICi ci, final IAttribute recallAttr) {
boolean canAdd = false;
for (IAttribute addable : ci.getAddableAttributes()) {
if (addable.getAlias().equals(recallAttr.getAlias())) {
canAdd = true;
break;
}
}
return canAdd;
}
private void setFormParam(String key, IType type, Object value) {
IValue v = type.parseString(value.toString());
if (getFormParams().containsKey(key)) {
getFormParams().put(key, v != null ? v : type.getNullValue());
}
}
private void setFormParam(IAttribute attr, IValue value) {
String key = "ATTR"+attr.getId();
if ( !getFormParams().containsKey(key) ) {
if (value == null) {
IModelService modelsvc = (IModelService) getCommand().getSession()
.getService(IModelService.class);
IType xsstring = modelsvc.getType("xs:string");
IType vt = attr.getValueType();
if (vt == null) {
value = xsstring.parseString("");
} else {
value = vt.getNullValue();
}
}
}
getFormParams().put(key, value);
}
private IAttribute getAttribute(String attrKey) {
ICi ci = getCi();
for (IAttribute attr : ci.getAttributes()) {
if (attrKey.equals("ATTR"+attr.getId())) {
return attr;
}
if (attrKey.equals(attr.getAlias())) {
return attr;
}
}
for (IAttribute attr : ci.getAddableAttributes()) {
if (attrKey.equals(attr.getAlias())) {
return attr;
}
}
return null;
}
private IValue getSelectValue(IAttribute attr, IValue value) {
IModelService modelsvc = (IModelService) getCommand().getSession()
.getService(IModelService.class);
if (value != null) {
String s = value.getAsString();
if (s.startsWith("ID:") && s.length() > 3) {
s = s.substring(3);
ItemId typeId = new ItemId(s);
value = modelsvc.find(typeId);
} else if (!s.startsWith("ID:")) {
// a simple type is what whar we should bind to...
// must fetch the ``type'' we are adding
IType attrType = null; {
for (IAttribute addable : attr.getOwner().getAddableAttributes()) {
if (addable.getAlias().equals(attr.getAlias())) {
attrType = addable.getValueType();
break;
}
}
}
value = attrType.fromValue(value);
}
}
return value;
}
}