/*
* 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.ui.gwt.desktop.server.service.change;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.tomcat.util.buf.CharChunk.CharInputChannel;
import org.onecmdb.core.IRFC;
import org.onecmdb.core.IRfcResult;
import org.onecmdb.core.IType;
import org.onecmdb.core.internal.ccb.rfc.RFCDestroy;
import org.onecmdb.core.internal.ccb.rfc.RFCModifyAlias;
import org.onecmdb.core.internal.ccb.rfc.RFCNewCi;
import org.onecmdb.core.internal.model.primitivetypes.SimpleTypeFactory;
import org.onecmdb.core.utils.bean.AttributeBean;
import org.onecmdb.core.utils.bean.CiBean;
import org.onecmdb.core.utils.bean.ValueBean;
import org.onecmdb.core.utils.graph.query.GraphQuery;
import org.onecmdb.core.utils.graph.query.selector.ItemAliasSelector;
import org.onecmdb.core.utils.graph.query.selector.ItemOffspringSelector;
import org.onecmdb.core.utils.graph.result.Graph;
import org.onecmdb.core.utils.graph.result.Template;
import org.onecmdb.core.utils.xml.BeanCompare;
import org.onecmdb.core.utils.xml.BeanRFCGenerator;
import org.onecmdb.core.utils.xml.BeanScope;
import org.onecmdb.core.utils.xml.IBeanScope;
import org.onecmdb.core.utils.xml.RfcContainer;
import org.onecmdb.ui.gwt.desktop.client.service.change.ChangeAttribute;
import org.onecmdb.ui.gwt.desktop.client.service.change.ChangeCI;
import org.onecmdb.ui.gwt.desktop.client.service.change.ChangeItem;
import org.onecmdb.ui.gwt.desktop.client.service.change.ChangeValue;
public class ReconciliationEngine implements IBeanScope {
private ICIMDR local;
private ICIMDR base;
private ICIMDR remote;
private String token;
private BeanCompare cmp;
private HashMap<String, CiBean> localBeanMap = new HashMap<String, CiBean>();
private HashSet<String> processedBeans = new HashSet<String>();
// The resulting ChnageItems...
List<ChangeItem> result = new ArrayList<ChangeItem>();
// Statistics
private HashSet<String> remoteBeansReferenced = new HashSet<String>();
private HashSet<String> simpleTypesUsed = new HashSet<String>();
private HashSet<String> externalReferences = new HashSet<String>();
private Graph remoteResult;
private HashMap<String, CiBean> remoteBeanCache = new HashMap<String, CiBean>();
class RFCTarget {
public CiBean bean;
public AttributeBean attr;
public ValueBean value;
}
public ReconciliationEngine(String token, ICIMDR local, ICIMDR base, ICIMDR remote) {
this.local = local;
this.base = base;
this.remote = remote;
this.token = token;
BeanRFCGenerator gen = new BeanRFCGenerator();
gen.setScope(this);
gen.setRfcContainer(new RfcContainer());
this.cmp = new BeanCompare();
this.cmp.setRfcGenerator(gen);
}
public List<ChangeItem> getResult() {
return result;
}
private List<CiBean> getBeans(ICIMDR mdr) {
// Fetch all templates...
GraphQuery query = new GraphQuery();
ItemOffspringSelector templates = new ItemOffspringSelector("template", "Root");
templates.setMatchTemplate(true);
templates.setPrimary(true);
/*
ItemOffspringSelector references = new ItemOffspringSelector("reference", "Reference");
references.setMatchTemplate(true);
references.setPrimary(true);
*/
ItemOffspringSelector instances = new ItemOffspringSelector("instance", "Root");
instances.setMatchTemplate(false);
instances.setPrimary(true);
query.addSelector(templates);
query.addSelector(instances);
//query.addSelector(references);
Graph g = mdr.query(token, query);
List<CiBean> beans = new ArrayList<CiBean>();
for (Template t : g.getNodes()) {
if (t.getOffsprings() == null) {
continue;
}
for (CiBean bean : t.getOffsprings()) {
beans.add(bean);
}
}
return(beans);
}
protected List<IRFC> compare() {
List<CiBean> beans = getBeans(local);
if (beans.size() > 0) {
GraphQuery remoteQ = new GraphQuery();
List<String> aliases = new ArrayList<String>();
for (CiBean bean: beans) {
if (localBeanMap.containsKey(bean.getAlias())) {
// Error, duplicated alias...
getResult().add(toChanges(bean, ChangeItem.STATUS_ERROR_DUPLICATED, ""));
}
localBeanMap.put(bean.getAlias(), bean);
// Query remote ...
aliases.add(bean.getAlias());
}
ItemAliasSelector sel = new ItemAliasSelector("alias", "Root");
sel.setAliases(aliases);
sel.setPrimary(true);
remoteQ.addSelector(sel);
// Query remote...
if (remote != null) {
remoteResult = remote.query(token, remoteQ);
remoteResult.buildMap();
}
}
//result.addAll(compareBeans(g.fetchNode(templates.getId()).getOffsprings()));
// Process all beans...
for (CiBean bean : localBeanMap.values()) {
processBean(bean.getAlias());
}
// Check for deletes
List<CiBean> baseBeans = getBeans(base);
for (CiBean bean: baseBeans) {
if (localBeanMap.containsKey(bean.getAlias())) {
continue;
}
// Check if the remote contains this.
CiBean remoteBean = remote.getCI(token, bean.getAlias());
if (remoteBean != null) {
this.cmp.getRfcGenerator().removeCi(bean);
}
}
List<IRFC> rfcs = cmp.getRfcGenerator().getRfcContainer().getOrderedRfcs();
return(rfcs);
}
public List<ChangeItem> reconciliate() {
HashMap<String, ChangeItem> items = new HashMap<String, ChangeItem>();
List<IRFC> rfcs = compare();
for (IRFC rfc : rfcs) {
RFCTarget target = getBeanFromRFC(rfc);
if (target == null) {
continue;
}
CiBean bean = target.bean;
if (bean == null) {
continue;
}
if (rfc instanceof RFCNewCi) {
if (items.containsKey(bean.getAlias())) {
items.remove(bean);
}
ChangeItem item = toChanges(bean, ChangeItem.STATUS_NEW, "");
items.put(bean.getAlias(), item);
//updateBeanSummary(bean, item);
continue;
}
if (rfc instanceof RFCDestroy && target.attr == null && target.value == null) {
if (items.containsKey(bean.getAlias())) {
items.remove(bean.getAlias());
}
ChangeItem item = toChanges(bean, ChangeItem.STATUS_DELETE, "");
items.put(bean.getAlias(), item);
//updateBeanSummary(bean, item);
continue;
}
ChangeItem item = items.get(bean.getAlias());
if (item == null) {
item = toChanges(bean, ChangeItem.STATUS_MODIFIED, "");
}
if (item.getStatus().equals(ChangeItem.STATUS_DELETE)
|| item.getStatus().equals(ChangeItem.STATUS_NEW)) {
continue;
}
String summary = item.get("summary", "");
if (rfc instanceof RFCDestroy) {
if (target.attr != null) {
summary = summary + "<br/> Delete attribute " + target.attr.getAlias();
} else if (target.value != null) {
summary = summary + "<br/> Delete value " + target.value.getAlias();
}
} else {
summary = summary + "<br/>" + rfc.getSummary();
}
item.set("summary", summary);
items.put(bean.getAlias(), item);
}
for (String alias : items.keySet()) {
CiBean bean = getBean(alias);
if (bean != null) {
updateBeanSummary(bean, items.get(alias));
}
}
getResult().addAll(items.values());
return(result);
}
private void updateBeanSummary(CiBean bean, ChangeItem item) {
StringBuffer b = new StringBuffer();
String summary = item.get("summary");
if (summary != null) {
b.append(summary);
b.append("<br><hr><br>");
}
b.append("<br/>Display name : " + handleNull(bean.getDisplayName()) + "<br/>");
b.append("Template : " +handleNull(bean.getDerivedFrom()) + "<br/><br/>");
if (bean.isTemplate()) {
b.append("<b>Attributes</b><br>");
b.append("<table>");
b.append("<tr>");
b.append("<td><b>" + "Name" + "</b></td>");
b.append("<td><b>" + "Alias" + "</b></td>");
b.append("<td><b>" + "Type" + "</b></td>");
b.append("<td><b>" + "Ref. Type" + "</b></td>");
b.append("</tr>");
for (AttributeBean aBean : bean.getAttributes()) {
b.append("<tr>");
b.append("<td>" + handleNull(aBean.getDisplayName()) + "</td>");
b.append("<td>" + handleNull(aBean.getAlias()) + "</td>");
b.append("<td>" + handleNull(aBean.getType()) + "</td>");
b.append("<td>" + handleNull(aBean.getRefType()) + "</td>");
b.append("<tr>");
}
b.append("</table>");
}
// Validate Attributes.
CiBean templateBase = null;
CiBean templateRemote = null;
if (!bean.isTemplate()) {
// Validate Attributes.
templateBase = getBaseBean(bean.getDerivedFrom());
templateRemote = getRemoteBean(bean.getDerivedFrom());
}
b.append("<b>Values</b><br>");
b.append("<table>");
for (ValueBean vBean : bean.getAttributeValues()) {
if (!attributeExists(templateBase, templateRemote, vBean)) {
item.setStatus(ChangeCI.STATUS_ERROR_MISSING_ATTRIBUTE);
b.append("<tr>");
b.append("<td><i>" + "Not defined in template" + "</i></td>");
b.append("<td><b>" + handleNull(vBean.getAlias()) + "</b></td>");
b.append("<td>" + handleNull(vBean.getValue()) + "</td>");
b.append("<tr>");
} else {
b.append("<tr>");
b.append("<td><b>" + handleNull(vBean.getAlias()) + "</b></td>");
b.append("<td>" + handleNull(vBean.getValue()) + "</td>");
b.append("<tr>");
}
}
b.append("</table>");
item.set("summary", b.toString());
}
private boolean attributeExists(CiBean templateBase, CiBean templateRemote,
ValueBean bean) {
if (templateRemote == null && templateBase == null) {
return(true);
}
if (templateRemote != null) {
if (templateRemote.getAttribute(bean.getAlias()) != null) {
return(true);
}
}
if (templateBase != null) {
if (templateBase.getAttribute(bean.getAlias()) != null) {
return(true);
}
}
return(false);
}
private String handleNull(String value) {
if (value == null) {
return("");
}
return(value);
}
private RFCTarget getBeanFromRFC(IRFC rfc) {
if (rfc instanceof RFCNewCi) {
for (IRFC child : rfc.getRfcs()) {
if (child instanceof RFCModifyAlias) {
String alias = ((RFCModifyAlias)child).getNewAlias();
RFCTarget target = new RFCTarget();
target.bean = localBeanMap.get(alias);
return(target);
}
}
return(null);
}
if (rfc.getTargetId() != null) {
Long id = rfc.getTargetId();
// Try find value.
RFCTarget found = null;
for (CiBean bean : localBeanMap.values()) {
for (AttributeBean a : bean.getAttributes()) {
if (id.equals(a.getId())) {
found = new RFCTarget();
found.bean = bean;
found.attr = a;
break;
}
}
if (found != null) {
return(found);
}
for (ValueBean v : bean.getAttributeValues()) {
if (id.equals(v.getId())) {
found = new RFCTarget();
found.bean = bean;
found.value = v;
break;
}
}
if (found != null) {
return(found);
}
}
for (CiBean bean : getBeans(base)) {
for (AttributeBean a : bean.getAttributes()) {
if (id.equals(a.getId())) {
found = new RFCTarget();
found.bean = bean;
found.attr = a;
break;
}
}
if (found != null) {
return(found);
}
for (ValueBean v : bean.getAttributeValues()) {
if (id.equals(v.getId())) {
found = new RFCTarget();
found.bean = bean;
found.value = v;
break;
}
}
if (found != null) {
return(found);
}
}
}
String alias = rfc.getTargetAlias();
RFCTarget target = new RFCTarget();
target.bean = getBean(alias);
return(target);
}
public CiBean getBean(String alias) {
if (alias == null) {
return(null);
}
CiBean bean = localBeanMap.get(alias);
if (bean != null) {
return(bean);
}
bean = remoteBeanCache .get(alias);
if (bean != null) {
return(remoteBeanCache.get(alias));
}
bean = remote.getCI(token, alias);
if (bean != null) {
remoteBeanCache.put(alias, bean);
return(bean);
}
bean = base.getCI(token, alias);
if (bean != null) {
return(bean);
}
return(null);
}
protected List<ChangeItem> compareBeans(List<CiBean> localBeans) {
List<ChangeItem> result = new ArrayList<ChangeItem>();
for (CiBean localBean : localBeans) {
processBean(localBean.getAlias());
}
return(result);
}
private ChangeCI toChanges(CiBean bean, String status, String info) {
ChangeCI change = new ChangeCI();
change.setStatus(status);
change.set("info", info);
if (bean == null) {
change.set("alias", "Unknown");
change.set("derivedFrom", "Unknown");
} else {
change.set("alias", bean.getAlias());
change.set("derivedFrom", bean.getDerivedFrom());
change.set("type", bean.isTemplate() ? "Template" : "Instance");
}
change.set("include", true);
/*
if (status.equals(ChangeCI.STATUS_NEW)) {
// Validate Attributes.
CiBean template = getBaseBean(bean.getDerivedFrom());
if (template == null) {
template = getRemoteBean(bean.getDerivedFrom());
}
if (template == null) {
// Should not exists, then we should have recived error already.
} else {
for (ValueBean v : bean.getAttributeValues()) {
AttributeBean a = template.getAttribute(v.getAlias());
if (a == null) {
change.setStatus(ChangeCI.STATUS_ERROR_MISSING_ATTRIBUTE);
change.setSu
}
}
}
}
*/
return(change);
}
protected CiBean match(ICIMDR mdr, CiBean bean) {
CiBean result = mdr.getCI(token, bean.getAlias());
return(result);
}
public CiBean getLocalBean(String alias) {
if (this.local == null) {
return(null);
}
return(this.local.getCI(token, alias));
}
public CiBean getBaseBean(String alias) {
if (this.base == null) {
return(null);
}
return(this.base.getCI(token, alias));
}
public CiBean getRemoteBean(String alias) {
if (this.remote == null) {
return(null);
}
if (remoteResult != null) {
CiBean bean = this.remoteResult.findOffspringAlias(alias);
if (bean != null) {
return(bean);
}
}
return(this.remote.getCI(token, alias));
}
public void processBean(String alias) {
if (processedBeans.contains(alias)) {
return;
}
CiBean local = localBeanMap.get(alias);
if (local == null) {
return;
}
processedBeans.add(alias);
CiBean baseBean = match(base, local);
CiBean remoteBean = null;
if (remoteResult != null) {
remoteBean = remoteResult.findOffspringAlias(local.getAlias());
}
// Check if we have the same parent..
if (remoteBean != null) {
/*
if (!remoteBean.getDerivedFrom().equals(local.getDerivedFrom())) {
getResult().add(toChanges(local, ChangeItem.STATUS_ERROR_PARENT_MISSMATCH, "Local <i>" + local.getDerivedFrom() + "</i> != <i>" + remoteBean.getDerivedFrom() + "</i>"));
return;
}
*/
}
this.cmp.compare(baseBean, remoteBean, local);
}
public void referenceBean(CiBean source, String how, String alias) {
CiBean bean = localBeanMap.get(alias);
if (bean == null) {
// validate against remotProvider...
if (remote != null) {
CiBean remoteBean = remote.getCI(this.token, alias);
if (remoteBean != null) {
remoteBeansReferenced.add(alias);
return;
}
}
// Check if primitive type...
IType type = SimpleTypeFactory.getInstance().toType(alias);
if (type != null) {
simpleTypesUsed.add(alias);
} else {
//log.warn("Bean '" + beanName + "' is not resolved!");
externalReferences.add(alias);
if ("value".equals(how)) {
getResult().add(toChanges(source, ChangeItem.STATUS_ERROR_MISSING_INSTANCE, alias + " not found(" + how + ")"));
} else {
getResult().add(toChanges(source, ChangeItem.STATUS_ERROR_MISSING_TEMPLATE, alias + " not found(" + how + ")"));
}
}
}
}
public IRfcResult commit(List<ChangeItem> items) {
List<CiBean> localBeans = local.getCI(token);
List<CiBean> baseBeans = base.getCI(token);
HashMap<String, CiBean> localBeanMap = new HashMap<String, CiBean>();
HashMap<String, CiBean> baseBeanMap = new HashMap<String, CiBean>();
for (CiBean bean : localBeans) {
localBeanMap.put(bean.getAlias(), bean);
// Reset id on local bean.
bean.setId(null);
}
for (CiBean bean : baseBeans) {
baseBeanMap.put(bean.getAlias(), bean);
}
for (ChangeItem item : items) {
Boolean b = item.get("include");
if (b != null && !b.booleanValue()) {
localBeanMap.remove(item.get("alias"));
baseBeanMap.remove(item.get("alias"));
}
}
IRfcResult result = remote.update(token, localBeanMap.values().toArray(new CiBean[0]), baseBeanMap.values().toArray(new CiBean[0]));
return(result);
}
public IRfcResult delete(List<ChangeItem> items) {
List<CiBean> localBeans = local.getCI(token);
List<CiBean> baseBeans = base.getCI(token);
HashMap<String, CiBean> localBeanMap = new HashMap<String, CiBean>();
HashMap<String, CiBean> baseBeanMap = new HashMap<String, CiBean>();
for (CiBean bean : localBeans) {
localBeanMap.put(bean.getAlias(), bean);
// Reset id on local bean.
bean.setId(null);
}
for (CiBean bean : baseBeans) {
baseBeanMap.put(bean.getAlias(), bean);
}
for (ChangeItem item : items) {
Boolean b = item.get("include");
if (b != null && !b.booleanValue()) {
localBeanMap.remove(item.get("alias"));
baseBeanMap.remove(item.get("alias"));
}
}
IRfcResult result = remote.update(token, new CiBean[0], baseBeanMap.values().toArray(new CiBean[0]));
return(result);
}
}