/* * This file is part of the OpenSCADA project * Copyright (C) 2006-2011 TH4 SYSTEMS GmbH (http://th4-systems.com) * * OpenSCADA is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenSCADA 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 version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenSCADA. If not, see * <http://opensource.org/licenses/lgpl-3.0.html> for a copy of the LGPLv3 License. */ package org.openscada.ca.ui.util; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.openscada.ca.DiffEntry; import org.openscada.ca.DiffEntry.Operation; import org.openscada.ca.FactoryInformation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DiffController { private final static Logger logger = LoggerFactory.getLogger ( DiffController.class ); private Map<String, Map<String, Map<String, String>>> localData; private Set<String> ignoreFactories = new HashSet<String> (); private Map<String, Set<String>> ignoreFields = new HashMap<String, Set<String>> (); private Map<String, Map<String, Map<String, String>>> remoteData = Collections.emptyMap (); public void setLocalData ( final Map<String, Map<String, Map<String, String>>> localData ) { this.localData = localData; } public Set<String> getIgnoreFactories () { return this.ignoreFactories; } public Map<String, Set<String>> getIgnoreFields () { return this.ignoreFields; } public long setRemoteData ( final Collection<FactoryInformation> remoteData ) { final Map<String, Map<String, Map<String, String>>> data = new HashMap<String, Map<String, Map<String, String>>> (); final long count = ConfigurationHelper.convert ( remoteData, data ); setRemoteData ( data ); return count; } public void setRemoteData ( final Map<String, Map<String, Map<String, String>>> data ) { this.remoteData = data; } public List<DiffEntry> merge ( final IProgressMonitor monitor ) { try { monitor.beginTask ( Messages.DiffController_TaskName, this.localData.size () + this.remoteData.size () ); return processMerge ( monitor ); } finally { monitor.done (); } } private List<DiffEntry> processMerge ( final IProgressMonitor monitor ) { final List<DiffEntry> result = new LinkedList<DiffEntry> (); for ( final Map.Entry<String, Map<String, Map<String, String>>> factoryEntry : this.localData.entrySet () ) { // ignore from factory list if ( this.ignoreFactories.contains ( factoryEntry.getKey () ) ) { continue; } // if the target does not contain our factory if ( !this.remoteData.containsKey ( factoryEntry.getKey () ) ) { addAll ( factoryEntry.getKey (), result, factoryEntry.getValue (), DiffEntry.Operation.ADD ); } else { final Map<String, Map<String, String>> remoteFactory = this.remoteData.get ( factoryEntry.getKey () ); for ( final Map.Entry<String, Map<String, String>> cfgEntry : factoryEntry.getValue ().entrySet () ) { if ( !remoteFactory.containsKey ( cfgEntry.getKey () ) ) { result.add ( new DiffEntry ( factoryEntry.getKey (), cfgEntry.getKey (), Operation.ADD, cfgEntry.getValue () ) ); } else { // modify detection is only needed once final Map<String, String> remoteData = remoteFactory.get ( cfgEntry.getKey () ); if ( !isEqual ( cfgEntry.getValue (), remoteData ) ) { logger.debug ( "Detected update" ); //$NON-NLS-1$ logger.debug ( "From: {}", remoteData ); //$NON-NLS-1$ logger.debug ( "To: {}", cfgEntry.getValue () ); //$NON-NLS-1$ final DiffEntry diffEntry = makeDiffEntry ( factoryEntry.getKey (), cfgEntry.getKey (), remoteData, cfgEntry.getValue () ); if ( diffEntry != null ) { result.add ( diffEntry ); } } } } } monitor.worked ( 1 ); } for ( final Map.Entry<String, Map<String, Map<String, String>>> factoryEntry : this.remoteData.entrySet () ) { // ignore from factory list if ( this.ignoreFactories.contains ( factoryEntry.getKey () ) ) { continue; } // if the target does not contain our factory if ( !this.localData.containsKey ( factoryEntry.getKey () ) ) { addAll ( factoryEntry.getKey (), result, factoryEntry.getValue (), DiffEntry.Operation.DELETE ); } else { final Map<String, Map<String, String>> localFactory = this.localData.get ( factoryEntry.getKey () ); for ( final Map.Entry<String, Map<String, String>> cfgEntry : factoryEntry.getValue ().entrySet () ) { if ( !localFactory.containsKey ( cfgEntry.getKey () ) ) { result.add ( new DiffEntry ( factoryEntry.getKey (), cfgEntry.getKey (), Operation.DELETE, cfgEntry.getValue () ) ); } } } monitor.worked ( 1 ); } return result; } private DiffEntry makeDiffEntry ( final String factoryId, final String configurationId, final Map<String, String> remoteData, final Map<String, String> localData ) { final Set<String> ignoreFields = this.ignoreFields != null ? this.ignoreFields.get ( factoryId ) : null; if ( ignoreFields == null || ignoreFields.isEmpty () ) { // nothing to ignore so we can perform an UPDATE_SET operation return new DiffEntry ( factoryId, configurationId, Operation.UPDATE_SET, remoteData, localData ); } // from here on we need to check for field updates final Map<String, String> newData = new HashMap<String, String> (); final Map<String, String> oldData = new HashMap<String, String> (); // check for updates or additions for ( final Map.Entry<String, String> entry : localData.entrySet () ) { if ( ignoreFields.contains ( entry.getKey () ) ) { continue; } // check if the entry differs if ( entry.getValue () != null && remoteData.containsKey ( entry.getKey () ) ) { if ( !entry.getValue ().equals ( remoteData.get ( entry.getKey () ) ) ) { oldData.put ( entry.getKey (), remoteData.get ( entry.getKey () ) ); newData.put ( entry.getKey (), entry.getValue () ); } } else { oldData.put ( entry.getKey (), null ); newData.put ( entry.getKey (), entry.getValue () ); } } // check for removals for ( final Map.Entry<String, String> entry : remoteData.entrySet () ) { final String key = entry.getKey (); if ( !localData.containsKey ( key ) && !ignoreFields.contains ( key ) ) { oldData.put ( key, entry.getValue () ); newData.put ( key, null ); } } // we have no changes if ( newData.isEmpty () ) { return null; } return new DiffEntry ( factoryId, configurationId, Operation.UPDATE_DIFF, oldData, newData ); } /** * Check of two data sets are equal * @param localData the local file data * @param remoteData the remote server data * @return the result */ private boolean isEqual ( final Map<String, String> localData, final Map<String, String> remoteData ) { return remoteData.equals ( localData ); } private void addAll ( final String factoryId, final Collection<DiffEntry> result, final Map<String, Map<String, String>> value, final Operation operation ) { for ( final Map.Entry<String, Map<String, String>> cfgEntry : value.entrySet () ) { result.add ( new DiffEntry ( factoryId, cfgEntry.getKey (), operation, cfgEntry.getValue () ) ); } } public void addIgnoreFactory ( final String factoryId ) { this.ignoreFactories.add ( factoryId ); } public void setIgnoreFactories ( final Set<String> factories ) { this.ignoreFactories = new HashSet<String> ( factories ); } public void addIgnoreEntry ( final String factoryId, final String fieldName ) { Set<String> fields = this.ignoreFields.get ( factoryId ); if ( fields == null ) { fields = new HashSet<String> (); this.ignoreFields.put ( factoryId, fields ); } fields.add ( fieldName ); } public void setIgnoreFields ( final Map<String, Set<String>> ignoreFields ) { if ( ignoreFields != null ) { this.ignoreFields = new HashMap<String, Set<String>> ( ignoreFields ); } else { this.ignoreFields = null; } } public Map<String, Map<String, Map<String, String>>> getLocalData () { return this.localData; } public Map<String, Map<String, Map<String, String>>> getRemoteData () { return this.remoteData; } public Set<String> makeKnownFactories () { final Set<String> result = new HashSet<String> (); result.addAll ( this.localData.keySet () ); result.addAll ( this.remoteData.keySet () ); return result; } }