/*
* 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.hd.ui.data;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.openscada.hd.Query;
import org.openscada.hd.QueryListener;
import org.openscada.hd.QueryParameters;
import org.openscada.hd.QueryState;
import org.openscada.hd.Value;
import org.openscada.hd.ValueInformation;
import org.openscada.hd.client.Connection;
import org.openscada.utils.beans.AbstractPropertyChange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AbstractQueryBuffer extends AbstractPropertyChange
{
private static final Logger logger = LoggerFactory.getLogger ( QueryBuffer.class );
public static final String PROP_STATE = "state";
public static final String PROP_QUERY_PARAMETERS = "queryParameters";
public static final String PROP_REQUEST_PARAMETERS = "requestParameters";
public static final String PROP_VALUE_TYPES = "valueTypes";
public static final String PROP_FILLED = "filled";
public static final String PROP_PERCENT_FILLED = "percentFilled";
protected final Set<QueryListener> listeners = new HashSet<QueryListener> ();
protected final String itemId;
private QueryParameters requestParameters;
private QueryParameters queryParameters;
protected Query query;
private QueryState state;
private Set<String> valueTypes;
private ValueInformation[] valueInformation;
private HashMap<String, Value[]> values;
private int filled;
private double percentFilled;
private Double lastFilled;
private static double FILLED_DELTA = 1.0;
public AbstractQueryBuffer ( final String itemId )
{
this.itemId = itemId;
}
public QueryState getState ()
{
return this.state;
}
public String getItemId ()
{
return this.itemId;
}
/*
public Connection getConnection ()
{
return this.connection;
}
*/
public QueryParameters getRequestParameters ()
{
return this.requestParameters;
}
public QueryParameters getQueryParameters ()
{
return this.queryParameters;
}
public Set<String> getValueTypes ()
{
return this.valueTypes;
}
public int getFilled ()
{
return this.filled;
}
public double getPercentFilled ()
{
return this.percentFilled;
}
/**
* Return the current value information
* @return the current value information
*/
public ValueInformation[] getValueInformation ()
{
return this.valueInformation;
}
/**
* Return the current values
* @return the current values
*/
public Map<String, Value[]> getValues ()
{
return new HashMap<String, Value[]> ( this.values );
}
protected synchronized void updateData ( final int index, final Map<String, Value[]> values, final ValueInformation[] valueInformation )
{
final int count = valueInformation.length;
int filled = this.filled;
for ( int i = 0; i < count; i++ )
{
if ( this.valueInformation[i + index] == null )
{
filled++;
}
this.valueInformation[i + index] = valueInformation[i];
}
for ( final String type : this.valueTypes )
{
final Value[] src = values.get ( type );
final Value[] dst = this.values.get ( type );
System.arraycopy ( src, 0, dst, index, count );
}
// update stats
setFilled ( filled );
fireDataChange ( index, values, valueInformation );
}
private void setFilled ( final int filled )
{
final int oldFilled = this.filled;
this.filled = filled;
firePropertyChange ( PROP_FILLED, oldFilled, filled );
final double percentFilled = (double)filled / (double)this.queryParameters.getEntries ();
setPercentFilled ( percentFilled );
}
private void setPercentFilled ( final double percentFilled )
{
final double oldPercentFilled = this.percentFilled;
this.percentFilled = percentFilled;
if ( this.lastFilled == null || Math.abs ( this.lastFilled - percentFilled ) > FILLED_DELTA )
{
this.lastFilled = percentFilled;
firePropertyChange ( PROP_PERCENT_FILLED, oldPercentFilled, percentFilled );
}
}
protected synchronized void updateParameters ( final QueryParameters parameters, final Set<String> valueTypes )
{
final int count = parameters.getEntries ();
this.valueInformation = new ValueInformation[count];
this.values = new HashMap<String, Value[]> ();
for ( final String valueType : valueTypes )
{
this.values.put ( valueType, new Value[count] );
}
fireParameterChange ( parameters, valueTypes );
setQueryParameters ( parameters );
setValueTypes ( valueTypes );
setFilled ( 0 );
}
private void setValueTypes ( final Set<String> valueTypes )
{
logger.debug ( "Set value types: {}", valueTypes ); //$NON-NLS-1$
final Set<String> oldValueTypes = this.valueTypes;
this.valueTypes = valueTypes;
firePropertyChange ( PROP_VALUE_TYPES, oldValueTypes, valueTypes );
}
private void setQueryParameters ( final QueryParameters parameters )
{
final QueryParameters oldParameters = this.queryParameters;
this.queryParameters = parameters;
firePropertyChange ( PROP_QUERY_PARAMETERS, oldParameters, parameters );
}
protected void setRequestParameters ( final QueryParameters parameters )
{
logger.debug ( "Setting request parameters: {}", parameters ); //$NON-NLS-1$
final QueryParameters oldParameters = this.requestParameters;
this.requestParameters = parameters;
firePropertyChange ( PROP_REQUEST_PARAMETERS, oldParameters, parameters );
}
protected synchronized void updateState ( final QueryState state )
{
final QueryState oldState = this.state;
this.state = state;
fireStateChange ( state );
firePropertyChange ( PROP_STATE, oldState, state );
}
private void fireDataChange ( final int index, final Map<String, Value[]> values, final ValueInformation[] valueInformation )
{
for ( final QueryListener listener : this.listeners )
{
listener.updateData ( index, values, valueInformation );
}
}
private void fireParameterChange ( final QueryParameters parameters, final Set<String> valueTypes )
{
for ( final QueryListener listener : this.listeners )
{
listener.updateParameters ( parameters, valueTypes );
}
}
private void fireStateChange ( final QueryState state )
{
for ( final QueryListener listener : this.listeners )
{
listener.updateState ( state );
}
}
public void close ()
{
if ( this.query != null )
{
this.query.close ();
this.query = null;
}
}
public synchronized void changeProperties ( final QueryParameters parameters )
{
logger.info ( "Request parameter change - new: {}, old: {}", new Object[] { parameters, this.requestParameters } ); //$NON-NLS-1$
if ( !this.requestParameters.equals ( parameters ) )
{
setRequestParameters ( parameters );
if ( this.query != null )
{
this.query.changeParameters ( parameters );
}
}
else
{
logger.info ( "Ignore change request since there is no change" ); //$NON-NLS-1$
}
}
public synchronized void addQueryListener ( final QueryListener listener )
{
if ( !this.listeners.add ( listener ) )
{
return;
}
listener.updateState ( this.state );
if ( this.queryParameters != null )
{
listener.updateParameters ( this.queryParameters, this.valueTypes );
transmitKnownData ( listener );
}
}
private void transmitKnownData ( final QueryListener listener )
{
if ( this.valueInformation == null || this.valueInformation.length == 0 )
{
return;
}
int start = 0;
int count = 0;
for ( int i = 0; i < this.valueInformation.length; i++ )
{
if ( this.valueInformation[i] == null )
{
if ( count > 0 )
{
// send now
sendCache ( listener, start, count );
count = 0;
}
}
else
{
if ( count == 0 )
{
start = i;
}
count++;
}
}
if ( count > 0 )
{
sendCache ( listener, start, count );
}
}
private void sendCache ( final QueryListener listener, final int start, final int count )
{
logger.info ( "Sending cache: start:{} - count:{}", new Object[] { start, count } ); //$NON-NLS-1$
final ValueInformation[] info = new ValueInformation[count];
System.arraycopy ( this.valueInformation, start, info, 0, count );
final Map<String, Value[]> values = new HashMap<String, Value[]> ();
for ( final String type : this.valueTypes )
{
final Value[] src = this.values.get ( type );
final Value[] dst = new Value[count];
System.arraycopy ( src, start, dst, 0, count );
values.put ( type, dst );
}
listener.updateData ( start, this.values, info );
}
public synchronized void removeQueryListener ( final QueryListener listener )
{
this.listeners.remove ( listener );
}
protected synchronized void createQuery ( final Connection connection, final String itemId )
{
close ();
this.query = connection.createQuery ( itemId, this.requestParameters, new QueryListener () {
@Override
public void updateState ( final QueryState state )
{
AbstractQueryBuffer.this.updateState ( state );
}
@Override
public void updateParameters ( final QueryParameters parameters, final Set<String> valueTypes )
{
AbstractQueryBuffer.this.updateParameters ( parameters, valueTypes );
}
@Override
public void updateData ( final int index, final Map<String, Value[]> values, final ValueInformation[] valueInformation )
{
AbstractQueryBuffer.this.updateData ( index, values, valueInformation );
}
}, true );
}
}