Package ch.agent.crnickl.impl

Source Code of ch.agent.crnickl.impl.DatabaseBackendImpl

/*
*   Copyright 2012-2013 Hauser Olsson GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ch.agent.crnickl.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

import ch.agent.core.KeyedException;
import ch.agent.core.KeyedMessage;
import ch.agent.crnickl.T2DBException;
import ch.agent.crnickl.T2DBMsg;
import ch.agent.crnickl.T2DBMsg.D;
import ch.agent.crnickl.api.AttributeDefinition;
import ch.agent.crnickl.api.Chronicle;
import ch.agent.crnickl.api.DBObject;
import ch.agent.crnickl.api.DBObjectType;
import ch.agent.crnickl.api.DatabaseConfiguration;
import ch.agent.crnickl.api.MessageListener;
import ch.agent.crnickl.api.NamingPolicy;
import ch.agent.crnickl.api.Property;
import ch.agent.crnickl.api.Schema;
import ch.agent.crnickl.api.Series;
import ch.agent.crnickl.api.Surrogate;
import ch.agent.crnickl.api.UpdatableChronicle;
import ch.agent.crnickl.api.UpdatableProperty;
import ch.agent.crnickl.api.UpdatableSchema;
import ch.agent.crnickl.api.UpdatableSeries;
import ch.agent.crnickl.api.UpdatableValueType;
import ch.agent.crnickl.api.UpdateEvent;
import ch.agent.crnickl.api.UpdateEventOperation;
import ch.agent.crnickl.api.UpdateEventPublisher;
import ch.agent.crnickl.api.ValueType;
import ch.agent.t2.time.Range;
import ch.agent.t2.time.TimeIndex;
import ch.agent.t2.timeseries.Observation;
import ch.agent.t2.timeseries.TimeAddressable;
import ch.agent.t2.timeseries.TimeSeriesFactory;

/**
* Default implementation of {@link DatabaseBackend}.
*
* @author Jean-Paul Vetterli
*
*/
public abstract class DatabaseBackendImpl implements DatabaseBackend {

  private int hashCode = -1;
 
  private DatabaseCache cache;
  private UpdateEventPublisher eventHub;
  private NameSpace topChronicle;
  private MessageListener messageListener;
  private PermissionChecker permissionChecker;
  private SchemaUpdatePolicy sup;
  private ChronicleUpdatePolicyExtension eupx;
  private ChronicleUpdatePolicy eup;
  private NamingPolicy nm;
  private boolean strictNameSpaceMode;
  private Map<String, ValueAccessMethods<?>> am;
 
  /**
   * Construct a {@link DatabaseBackend}.
   *
   * @param name the name of the database
   */
  public DatabaseBackendImpl(String name) {
    topChronicle = new NameSpace(name, String.format("%s (%s)", getClass().getSimpleName(), name), new SurrogateImpl(this, DBObjectType.CHRONICLE, null));
    setMessageListener(null);
    nm = new NamingPolicy();
    am = new HashMap<String, ValueAccessMethods<?>>();
  }

  private MessageListener getDefaultMessageListener() {
    return new MessageListener() {
      @Override
      public void setFilterLevel(Level level) {
        if (!level.equals(Level.OFF))
          throw new UnsupportedOperationException("the default message listener ignores all messages");
      }
      @Override
      public boolean isListened(Level level) {
        return false;
      }
      @Override
      public void log(Level level, KeyedMessage msg) {}
      @Override
      public void log(Level level, String text) {}
      @Override
      public void log(Exception e) {}
    };
  }
 
  private PermissionChecker getDefaultPermissionChecker() {
    return new PermissionChecker() {
     
      @Override
      public void check(Permission permission, Surrogate surrogate) throws T2DBException {
      }
     
      @Override
      public boolean check(Permission permission, Surrogate surrogate, boolean permissionRequired) throws T2DBException {
        return true;
      }
     
      @Override
      public void check(Permission permission, DBObject dBObject) throws T2DBException {
      }
     
      @Override
      public boolean check(Permission permission, DBObject dBObject, boolean permissionRequired) throws T2DBException {
        return true;
      }
    };
  }
 
  @SuppressWarnings("unchecked")
  @Override
  public <T>ValueAccessMethods<T> getAccessMethods(ValueType<T> valueType) {
    ValueAccessMethods<?> accessMethods = am.get(valueType.getExternalRepresentation());
    return (ValueAccessMethods<T>) accessMethods;
  }

  @Override
  public <T>void setAccessMethods(String valueTypeExternalRepresentation, ValueAccessMethods<T> accessMethods) {
    am.put(valueTypeExternalRepresentation, (ValueAccessMethods<?>)accessMethods);
  }
 
  @Override
  public boolean isStrictNameSpaceMode() {
    return strictNameSpaceMode;
  }
 
  @Override
  public void setStrictNameSpaceMode(boolean strictNameSpaceMode) {
    if (strictNameSpaceMode != this.strictNameSpaceMode)
      getCache().clear(); // avoid a mixture of strict and loose names in the cache
    this.strictNameSpaceMode = strictNameSpaceMode;
  }

  /**
   * Check the validity of a surrogate for this database and a given database object type.
   *
   * @param surrogate a surrogate
   * @param required a database object type
   * @throws T2DBException
   */
  protected void checkSurrogate(Surrogate surrogate, DBObjectType required) throws T2DBException {
    if (surrogate.inConstruction() || !surrogate.getDBObjectType().equals(required))
      throw T2DBMsg.exception(D.D02102, surrogate.getDBObjectType().name(), required.name());
    if (!surrogate.getDBObjectType().equals(required) && surrogate.getDatabase() != this)
      throw T2DBMsg.exception(D.D02103, surrogate.getDatabase().getTopChronicle().getName(true),
          this.getTopChronicle().getName(true));
  }

  protected boolean isChronicleUpdatePolicyExtensionMandatory() {
    return false;
  }
 
  protected boolean isChronicleUpdatePolicyExtensionAllowed() {
    return true;
  }
 
  @Override
  public void configure(DatabaseConfiguration configuration) throws T2DBException {
    String parameter = configuration.getParameter(DatabaseBackend.DB_PARAM_Int_MAX_GAP, false);
    int maxGap = -1;
    try {
      if (parameter != null)
        maxGap = new Integer(parameter);
    } catch (Exception e) {
      throw T2DBMsg.exception(e, D.D00108, DatabaseBackend.DB_PARAM_Int_MAX_GAP, parameter);
    }
    if (maxGap > -1)
      TimeSeriesFactory.getInstance().setMaxGap(maxGap);
   
    int cacheSize = 0;
    float cacheLoadFactor = 0f;
    parameter = configuration.getParameter(DatabaseBackend.DB_PARAM_Int_CACHE_SIZE, false);
    try {
      cacheSize = parameter == null ? DB_PARAM_Int_CACHE_SIZE_DEFAULT : new Integer(parameter);
    } catch (Exception e) {
      throw T2DBMsg.exception(e, D.D00108, DatabaseBackend.DB_PARAM_Int_CACHE_SIZE, parameter);
    }
    parameter = configuration.getParameter(DatabaseBackend.DB_PARAM_Float_CACHE_LOAD_FACTOR, false);
    try {
      cacheLoadFactor = parameter == null ? DB_PARAM_Float_CACHE_LOAD_FACTOR_DEFAULT : new Float(parameter);
    } catch (Exception e) {
      throw T2DBMsg.exception(e, D.D00108, DatabaseBackend.DB_PARAM_Float_CACHE_LOAD_FACTOR, parameter);
    }
    if (cacheSize > 0) {
      if (cacheLoadFactor > 0f)
        cache = new DatabaseCacheImpl(this, cacheSize, cacheLoadFactor);
      else
        cache = new DatabaseCacheImpl(this, cacheSize);
    }
   
    parameter = configuration.getParameter(DatabaseBackend.DB_PARAM_Boolean_STRICT_NAME_SPACE, false);
    try {
      if (parameter == null)
        setStrictNameSpaceMode(DB_PARAM_Boolean_STRICT_NAME_SPACE_DEFAULT);
      else
        setStrictNameSpaceMode(new Boolean(parameter));
    } catch (Exception e) {
      throw T2DBMsg.exception(e, D.D00108, DatabaseBackend.DB_PARAM_Boolean_STRICT_NAME_SPACE, parameter);
    }
   
    eupx = null;
    parameter = configuration.getParameter(DatabaseBackend.DB_PARAM_Class_ChronicleUpdatePolicyExtension, false);
    try {
      if (parameter != null && ((String) parameter).length() > 0) {
        if (!isChronicleUpdatePolicyExtensionAllowed())
          throw T2DBMsg.exception(D.D00107);
        Class<?> extClass;
        extClass = Class.forName((String)parameter);
        eupx = (ChronicleUpdatePolicyExtension) extClass.newInstance();
      }
    } catch (Exception e) {
      throw T2DBMsg.exception(e, D.D00108, DatabaseBackend.DB_PARAM_Class_ChronicleUpdatePolicyExtension, parameter);
    }
   
    if (eupx == null && isChronicleUpdatePolicyExtensionMandatory())
      throw T2DBMsg.exception(D.D00109, DatabaseBackend.DB_PARAM_Class_ChronicleUpdatePolicyExtension);
   
    permissionChecker = null;
    parameter = configuration.getParameter(DatabaseBackend.DB_PARAM_Class_PermissionChecker, false);
    try {
      if (parameter != null && ((String) parameter).length() > 0) {
        Class<?> extClass;
        extClass = Class.forName((String)parameter);
        permissionChecker = (PermissionChecker) extClass.newInstance();
      }
    } catch (Exception e) {
      throw T2DBMsg.exception(e, D.D00108, DatabaseBackend.DB_PARAM_Class_PermissionChecker, parameter);
    }
    validateNameSpace();
  }
 
  @Override
  public boolean isBuiltIn(AttributeDefinition<?> def) {
    return def.getNumber() <= MAX_MAGIC_NR;
  }

  @Override
  public DatabaseCache getCache() {
    return cache;
  }
 
  @Override
  public NamingPolicy getNamingPolicy() {
    return nm;
  }

  @Override
  public ChronicleUpdatePolicy getChronicleUpdatePolicy() {
    if (eup == null)
      eup = new ChronicleUpdatePolicyImpl(this, eupx);
    return eup;
  }
 
  @Override
  public SchemaUpdatePolicy getSchemaUpdatePolicy() {
    if (sup == null)
      sup = new SchemaUpdatePolicyImpl(this);
    return sup;
  }
 
  @Override
  public UpdateEventPublisher getUpdateEventPublisher() {
    if (eventHub == null)
      eventHub = new UpdateEventPublisherImpl();
    return eventHub;
  }
 
  /**
   * Short cut method to publish a deferred event.
   * @param event an update event
   */
  protected void publish(UpdateEvent event) {
    getUpdateEventPublisher().publish(event, false);
  }
 
  @Override
  public void setMessageListener(MessageListener listener) {
    this.messageListener = listener == null ? getDefaultMessageListener() : listener;
    if (cache!= null)
      ((DatabaseCacheImpl) cache).setMessageListener(messageListener);
  }

  @Override
  public MessageListener getMessageListener() {
    return messageListener;
  }

  /**
   * Return the permission checker. The result is never null.
   * The default permission checker allows all operations.
   *
   * @return the permission checker
   */
  protected PermissionChecker getPermissionChecker() {
    if (permissionChecker == null)
      permissionChecker = getDefaultPermissionChecker();
    return permissionChecker;
  }

  @Override
  public boolean check(Permission permission, DBObject dBObject,  boolean permissionRequired) throws T2DBException {
    return getPermissionChecker().check(permission, dBObject, permissionRequired);
  }

  @Override
  public void check(Permission permission, DBObject dBObjectthrows T2DBException {
    getPermissionChecker().check(permission, dBObject);
  }

  @Override
  public boolean check(Permission permission, Surrogate surrogate, boolean permissionRequired) throws T2DBException {
    return getPermissionChecker().check(permission, surrogate, permissionRequired);
  }

  @Override
  public void check(Permission permission, Surrogate surrogate) throws T2DBException {
    getPermissionChecker().check(permission, surrogate);
  }

  private void validateNameSpace() throws T2DBException {
    String name = getTopChronicle().getName(true);
    name = getNamingPolicy().checkSimpleName(name, false);
    if (!isStrictNameSpaceMode()) {
      Collection<Chronicle> topMembers = null;
      try {
        topMembers = topChronicle.getMembers();
      } catch (T2DBException e) {
        // probably the database has not been set up yet, so nothing wrong
        topMembers = new ArrayList<Chronicle>();
      }
      // check that there is no top member named like the name space
      for (Chronicle en : topMembers) {
        if (en.getName(false).equals(name)){
          throw T2DBMsg.exception(D.D00110, name);
        }
      }
    }
  }
 
  @Override
  public Chronicle getTopChronicle() {
    if (topChronicle == null)
      throw new IllegalStateException("bug: name space not available");
    return topChronicle;
  }
 
  /**
   * {@inheritDoc}
   * <p>
   * When an chronicle exists for the name, throw an exception if reading it is
   * not permitted.
   */
  @Override
  public Chronicle getChronicle(String name, boolean mustExist) throws T2DBException {
    return getTopChronicle().findChronicle(name, mustExist);
  }
 
  @Override
  public <T> UpdatableSeries<T> getUpdatableSeries(String name, boolean mustExist) throws T2DBException {
    String[] es = getNamingPolicy().split(name);
    String chronicleName = es[0];
    String seriesName = es[1];
    UpdatableSeries<T> series = null;
    if (chronicleName == null) {
      throw T2DBMsg.exception(D.D50116, name);
    } else {
      Chronicle ent = getChronicle(chronicleName, mustExist);
      if (ent != null) {
        UpdatableChronicle updEnt = ent.edit();
        series = updEnt.updateSeries(seriesName);
        if (series == null && mustExist)
          series = updEnt.createSeries(seriesName);
      }
    }
    return series;
  }

  @Override
  public <T> Series<T> getSeries(String name, boolean mustExist) throws T2DBException {
    Series<T> series = null;
    String[] split = getNamingPolicy().split(name);
    if (split[0] == null)
      throw T2DBMsg.exception(D.D50116, name);
    Chronicle chronicle = getChronicle(split[0], mustExist);
    if (chronicle != null) {
      Series<T>[] s = chronicle.getSeries(new String[]{split[1]}, null, mustExist);
      series = s[0];
    }
    if (series == null && mustExist)
      throw T2DBMsg.exception(D.D50106, name);
    return series;
  }

  @Override
  public Chronicle getChronicle(Surrogate surrogate) throws T2DBException {
    checkSurrogate(surrogate, DBObjectType.CHRONICLE);
    Chronicle chronicle = new ChronicleImpl(surrogate);
    chronicle.getName(false); // side-effect: make sure it exists
    return chronicle;
  }
 
  @Override
  public <T> Range getRange(Series<T> series) throws T2DBException {
    return ((ValueTypeImpl<T>) series.getValueType()).getAccessMethods().getRange(series);
  }

  @Override
  public <T> long getValues(Series<T> series, Range range, TimeAddressable<T> ts) throws T2DBException {
    return ((ValueTypeImpl<T>) series.getValueType()).getAccessMethods().getValues(series, range, ts);
  }
 
  @Override
  public <T> Observation<T> getFirstObservation(Series<T> series, TimeIndex time) throws T2DBException {
    return ((ValueTypeImpl<T>) series.getValueType()).getAccessMethods().getFirst(series, time);
  }

  @Override
  public <T> Observation<T> getLastObservation(Series<T> series, TimeIndex time) throws T2DBException {
    return ((ValueTypeImpl<T>) series.getValueType()).getAccessMethods().getLast(series, time);
  }
 
  @Override
  public <T>boolean update(UpdatableSeries<T> series, Range range) throws T2DBException {
    boolean done = ((ValueTypeImpl<T>) series.getValueType()).getAccessMethods().updateSeries(series, range, getChronicleUpdatePolicy());
    if (done)
      publish(new UpdateEventImpl(UpdateEventOperation.MODIFY, series));
    return done;
  }

  @Override
  public <T>boolean deleteValue(UpdatableSeries<T> series, TimeIndex t) throws T2DBException {
    boolean done = ((ValueTypeImpl<T>) series.getValueType()).getAccessMethods().deleteValue(series, t, getChronicleUpdatePolicy());
    if (done)
      publish(new UpdateEventImpl(UpdateEventOperation.MODIFY, series));
    return done;
  }
 
  @Override
  public <T>long update(UpdatableSeries<T> series, TimeAddressable<T> values) throws T2DBException {
    long count = ((ValueTypeImpl<T>) series.getValueType()).getAccessMethods().updateValues(series, values, getChronicleUpdatePolicy());
    if (count > 0) {
      UpdateEventImpl event = new UpdateEventImpl(UpdateEventOperation.MODIFY, series);
      if (getMessageListener().isListened(Level.FINER))
        event.withComment(values.toString());
      publish(event);
    }
    return count;
  }

  /**
   * {@inheritDoc}
   * <p>
   * This method consolidates the chain of schemas by following links to the
   * parent schema until there is no parent. The result is also known as
   * "runtime schema".
   * <p>
   * Historical note. In a previous version of this system, chronicle level
   * attributes were merged into series attributes. This is not done any more.
   *
   */
  @Override
  public Schema getSchema(Surrogate surrogate) throws T2DBException {
    checkSurrogate(surrogate, DBObjectType.SCHEMA);
    return getUpdatableSchema(surrogate).resolve();
  }
 
  @Override
  public Property<?> getProperty(String name, boolean mustExist) throws T2DBException {
    Property<?> prop = getProperty(name);
    if (prop == null && mustExist)
      throw T2DBMsg.exception(D.D20109, name);
    return prop;
  }
 
  @Override
  public Property<?> getSymbolBuiltInProperty() throws T2DBException {
    return getCache().lookUpProperty(DatabaseBackend.BUILTIN_PROP_SYMBOL);
  }
 
  @Override
  public Property<?> getTimeDomainBuiltInProperty() throws T2DBException {
    return getCache().lookUpProperty(DatabaseBackend.BUILTIN_PROP_TIME_DOMAIN);
  }

  @Override
  public Property<?> getTypeBuiltInProperty() throws T2DBException {
    return getCache().lookUpProperty(DatabaseBackend.BUILTIN_PROP_TYPE);
  }

  @Override
  public Property<?> getSparsityBuiltInProperty() throws T2DBException {
    return getCache().lookUpProperty(DatabaseBackend.BUILTIN_PROP_SPARSITY);
  }
 
  @Override
  public Collection<Schema> getSchemas(String pattern) throws T2DBException {
    Collection<Surrogate> keys = getSchemaSurrogates(pattern);
    Collection<Schema> result = new ArrayList<Schema>(keys.size());
    for (Surrogate surrogate : keys) {
      try {
        result.add(getSchema(surrogate));
      } catch(KeyedException e) {
        // log but continue, else no chance to inspect the problem
        getMessageListener().log(e);
      }
    }
    return result;
  }
 
  @Override
  public Collection<UpdatableSchema> getUpdatableSchemas(String pattern) throws T2DBException {
    Collection<Surrogate> keys = getSchemaSurrogates(pattern);
    Collection<UpdatableSchema> result = new ArrayList<UpdatableSchema>(keys.size());
    for (Surrogate surrogate : keys) {
      result.add(getUpdatableSchema(surrogate));
    }
    return result;
  }
 
  @Override
  public UpdatableSchema getUpdatableSchema(Schema schema) throws T2DBException {
    if (schema instanceof UpdatableSchema)
      throw new IllegalArgumentException("schema is an UpdatableSchema"); // bug: should not have been called
    return getUpdatableSchema(schema.getSurrogate());
  }

  @Override
  public <T> UpdatableValueType<T> createValueType(String name, boolean restricted, String scannerClassOrKeyword) throws T2DBException {
    return new UpdatableValueTypeImpl<T>(name, restricted, scannerClassOrKeyword, null, 
        new SurrogateImpl(this, DBObjectType.VALUE_TYPE, null));
  }
 
  @Override
  public <T> UpdatableProperty<T> createProperty(String name,  ValueType<T> valueType, boolean indexed) throws T2DBException {
    Surrogate  surrogate = new SurrogateImpl(this, DBObjectType.PROPERTY, null);
    return new UpdatablePropertyImpl<T>(name, valueType, indexed, surrogate);
  }
 
  @Override
  public UpdatableSchema createSchema(String name, String nameOfBase) throws T2DBException {
    if (getSchemas(name).size() > 0)
      throw T2DBMsg.exception(D.D30108, name);
    UpdatableSchema base = null;
    if (nameOfBase != null && nameOfBase.length() > 0) {
      Collection<UpdatableSchema> list = getUpdatableSchemas(nameOfBase);
      if (list.size() != 1)
        throw T2DBMsg.exception(D.D30109, name, nameOfBase);
      base = list.iterator().next();
    }
    Surrogate surrogate = new SurrogateImpl(this, DBObjectType.SCHEMA, null);
    return new UpdatableSchemaImpl(name, base, null, null, surrogate);
  }
 
  @Override
  public int hashCode() {
    if (hashCode >= 0)
      return  hashCode;
    else
      return toString().hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    return toString().equals(obj.toString());
  }

  @Override
  public String toString() {
    try {
      return getTopChronicle().getName(true);
    } catch (Exception e){
      throw new RuntimeException("bug", e);
    }
  }

}
TOP

Related Classes of ch.agent.crnickl.impl.DatabaseBackendImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.