github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/data/data_manager.go (about)

     1  // Copyright © 2021 Kaleido, Inc.
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package data
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"time"
    24  
    25  	"github.com/kaleido-io/firefly/internal/config"
    26  	"github.com/kaleido-io/firefly/internal/i18n"
    27  	"github.com/kaleido-io/firefly/internal/log"
    28  	"github.com/kaleido-io/firefly/pkg/database"
    29  	"github.com/kaleido-io/firefly/pkg/dataexchange"
    30  	"github.com/kaleido-io/firefly/pkg/fftypes"
    31  	"github.com/karlseguin/ccache"
    32  )
    33  
    34  type Manager interface {
    35  	CheckDatatype(ctx context.Context, ns string, datatype *fftypes.Datatype) error
    36  	ValidateAll(ctx context.Context, data []*fftypes.Data) (valid bool, err error)
    37  	GetMessageData(ctx context.Context, msg *fftypes.Message, withValue bool) (data []*fftypes.Data, foundAll bool, err error)
    38  	ResolveInputData(ctx context.Context, ns string, inData fftypes.InputData) (fftypes.DataRefs, error)
    39  	VerifyNamespaceExists(ctx context.Context, ns string) error
    40  
    41  	UploadJSON(ctx context.Context, ns string, data *fftypes.Data) (*fftypes.Data, error)
    42  	UploadBLOB(ctx context.Context, ns string, reader io.Reader) (*fftypes.Data, error)
    43  }
    44  
    45  type dataManager struct {
    46  	blobStore
    47  
    48  	database          database.Plugin
    49  	exchange          dataexchange.Plugin
    50  	validatorCache    *ccache.Cache
    51  	validatorCacheTTL time.Duration
    52  }
    53  
    54  func NewDataManager(ctx context.Context, di database.Plugin, dx dataexchange.Plugin) (Manager, error) {
    55  	if di == nil || dx == nil {
    56  		return nil, i18n.NewError(ctx, i18n.MsgInitializationNilDepError)
    57  	}
    58  	dm := &dataManager{
    59  		database:          di,
    60  		exchange:          dx,
    61  		validatorCacheTTL: config.GetDuration(config.ValidatorCacheTTL),
    62  		blobStore: blobStore{
    63  			database: di,
    64  			exchange: dx,
    65  		},
    66  	}
    67  	dm.validatorCache = ccache.New(
    68  		// We use a LRU cache with a size-aware max
    69  		ccache.Configure().
    70  			MaxSize(config.GetByteSize(config.ValidatorCacheSize)),
    71  	)
    72  	return dm, nil
    73  }
    74  
    75  func (dm *dataManager) CheckDatatype(ctx context.Context, ns string, datatype *fftypes.Datatype) error {
    76  	_, err := newJSONValidator(ctx, ns, datatype)
    77  	return err
    78  }
    79  
    80  func (dm *dataManager) VerifyNamespaceExists(ctx context.Context, ns string) error {
    81  	err := fftypes.ValidateFFNameField(ctx, ns, "namespace")
    82  	if err != nil {
    83  		return err
    84  	}
    85  	namespace, err := dm.database.GetNamespace(ctx, ns)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	if namespace == nil {
    90  		return i18n.NewError(ctx, i18n.MsgNamespaceNotExist)
    91  	}
    92  	return nil
    93  }
    94  
    95  // getValidatorForDatatype only returns database errors - not found (of all kinds) is a nil
    96  func (dm *dataManager) getValidatorForDatatype(ctx context.Context, ns string, validator fftypes.ValidatorType, datatypeRef *fftypes.DatatypeRef) (Validator, error) {
    97  	if validator == "" {
    98  		validator = fftypes.ValidatorTypeJSON
    99  	}
   100  
   101  	if ns == "" || datatypeRef == nil || datatypeRef.Name == "" || datatypeRef.Version == "" {
   102  		log.L(ctx).Warnf("Invalid datatype reference '%s:%s:%s'", validator, ns, datatypeRef)
   103  		return nil, nil
   104  	}
   105  
   106  	key := fmt.Sprintf("%s:%s:%s", validator, ns, datatypeRef)
   107  	if cached := dm.validatorCache.Get(key); cached != nil {
   108  		cached.Extend(dm.validatorCacheTTL)
   109  		return cached.Value().(Validator), nil
   110  	}
   111  
   112  	datatype, err := dm.database.GetDatatypeByName(ctx, ns, datatypeRef.Name, datatypeRef.Version)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  	if datatype == nil {
   117  		return nil, nil
   118  	}
   119  	v, err := newJSONValidator(ctx, ns, datatype)
   120  	if err != nil {
   121  		log.L(ctx).Errorf("Invalid validator stored for '%s:%s:%s': %s", validator, ns, datatypeRef, err)
   122  		return nil, nil
   123  	}
   124  
   125  	dm.validatorCache.Set(key, v, dm.validatorCacheTTL)
   126  	return v, err
   127  }
   128  
   129  // GetMessageData looks for all the data attached to the message.
   130  // It only returns persistence errors.
   131  // For all cases where the data is not found (or the hashes mismatch)
   132  func (dm *dataManager) GetMessageData(ctx context.Context, msg *fftypes.Message, withValue bool) (data []*fftypes.Data, foundAll bool, err error) {
   133  	// Load all the data - must all be present for us to send
   134  	data = make([]*fftypes.Data, 0, len(msg.Data))
   135  	foundAll = true
   136  	for i, dataRef := range msg.Data {
   137  		d, err := dm.resolveRef(ctx, msg.Header.Namespace, dataRef, withValue)
   138  		if err != nil {
   139  			return nil, false, err
   140  		}
   141  		if d == nil {
   142  			log.L(ctx).Warnf("Message %v data %d mising", msg.Header.ID, i)
   143  			foundAll = false
   144  			continue
   145  		}
   146  		data = append(data, d)
   147  	}
   148  	return data, foundAll, nil
   149  }
   150  
   151  func (dm *dataManager) ValidateAll(ctx context.Context, data []*fftypes.Data) (valid bool, err error) {
   152  	for _, d := range data {
   153  		if d.Datatype != nil {
   154  			v, err := dm.getValidatorForDatatype(ctx, d.Namespace, d.Validator, d.Datatype)
   155  			if err != nil {
   156  				return false, err
   157  			}
   158  			if v == nil {
   159  				log.L(ctx).Errorf("Datatype %s:%s:%s not found", d.Validator, d.Namespace, d.Datatype)
   160  				return false, err
   161  			}
   162  			err = v.ValidateValue(ctx, d.Value, d.Hash)
   163  			if err != nil {
   164  				return false, err
   165  			}
   166  		}
   167  	}
   168  	return true, nil
   169  }
   170  
   171  func (dm *dataManager) resolveRef(ctx context.Context, ns string, dataRef *fftypes.DataRef, withValue bool) (*fftypes.Data, error) {
   172  	if dataRef == nil || dataRef.ID == nil {
   173  		log.L(ctx).Warnf("data is nil")
   174  		return nil, nil
   175  	}
   176  	d, err := dm.database.GetDataByID(ctx, dataRef.ID, withValue)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	switch {
   181  	case d == nil || d.Namespace != ns:
   182  		log.L(ctx).Warnf("Data %s not found in namespace %s", dataRef.ID, ns)
   183  		return nil, nil
   184  	case d.Hash == nil || (dataRef.Hash != nil && *d.Hash != *dataRef.Hash):
   185  		log.L(ctx).Warnf("Data hash does not match. Hash=%v Expected=%v", d.Hash, dataRef.Hash)
   186  		return nil, nil
   187  	default:
   188  		return d, nil
   189  	}
   190  }
   191  
   192  func (dm *dataManager) checkValidatorType(ctx context.Context, validator fftypes.ValidatorType) error {
   193  	switch validator {
   194  	case "", fftypes.ValidatorTypeJSON:
   195  		return nil
   196  	default:
   197  		return i18n.NewError(ctx, i18n.MsgUnknownValidatorType, validator)
   198  	}
   199  }
   200  
   201  func (dm *dataManager) validateAndStore(ctx context.Context, ns string, validator fftypes.ValidatorType, datatype *fftypes.DatatypeRef, value fftypes.Byteable) (*fftypes.Data, error) {
   202  	// If a datatype is specified, we need to verify the payload conforms
   203  	if datatype != nil {
   204  		if err := dm.checkValidatorType(ctx, validator); err != nil {
   205  			return nil, err
   206  		}
   207  		if datatype == nil || datatype.Name == "" || datatype.Version == "" {
   208  			return nil, i18n.NewError(ctx, i18n.MsgDatatypeNotFound, datatype)
   209  		}
   210  		v, err := dm.getValidatorForDatatype(ctx, ns, validator, datatype)
   211  		if err != nil {
   212  			return nil, err
   213  		}
   214  		if v == nil {
   215  			return nil, i18n.NewError(ctx, i18n.MsgDatatypeNotFound, datatype)
   216  		}
   217  		err = v.ValidateValue(ctx, value, nil)
   218  		if err != nil {
   219  			return nil, err
   220  		}
   221  	} else {
   222  		validator = ""
   223  	}
   224  
   225  	// Ok, we're good to generate the full data payload and save it
   226  	data := &fftypes.Data{
   227  		Validator: validator,
   228  		Datatype:  datatype,
   229  		Namespace: ns,
   230  		Value:     value,
   231  	}
   232  	err := data.Seal(ctx)
   233  	if err == nil {
   234  		err = dm.database.UpsertData(ctx, data, false, false)
   235  	}
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  	return data, nil
   240  }
   241  
   242  func (dm *dataManager) validateAndStoreInlined(ctx context.Context, ns string, value *fftypes.DataRefOrValue) (*fftypes.DataRef, error) {
   243  	data, err := dm.validateAndStore(ctx, ns, value.Validator, value.Datatype, value.Value)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	// Return a ref to the newly saved data
   249  	return &fftypes.DataRef{
   250  		ID:   data.ID,
   251  		Hash: data.Hash,
   252  	}, nil
   253  }
   254  
   255  func (dm *dataManager) UploadJSON(ctx context.Context, ns string, data *fftypes.Data) (*fftypes.Data, error) {
   256  	return dm.validateAndStore(ctx, ns, data.Validator, data.Datatype, data.Value)
   257  }
   258  
   259  func (dm *dataManager) ResolveInputData(ctx context.Context, ns string, inData fftypes.InputData) (refs fftypes.DataRefs, err error) {
   260  
   261  	refs = make(fftypes.DataRefs, len(inData))
   262  	for i, dataOrValue := range inData {
   263  		switch {
   264  		case dataOrValue.ID != nil:
   265  			// If an ID is supplied, then it must be a reference to existing data
   266  			d, err := dm.resolveRef(ctx, ns, &dataOrValue.DataRef, false /* do not need the value */)
   267  			if err != nil {
   268  				return nil, err
   269  			}
   270  			if d == nil {
   271  				return nil, i18n.NewError(ctx, i18n.MsgDataReferenceUnresolvable, i)
   272  			}
   273  			refs[i] = &fftypes.DataRef{
   274  				ID:   d.ID,
   275  				Hash: d.Hash,
   276  			}
   277  		case dataOrValue.Value != nil:
   278  			// We've got a Value, so we can validate + store it
   279  			if refs[i], err = dm.validateAndStoreInlined(ctx, ns, dataOrValue); err != nil {
   280  				return nil, err
   281  			}
   282  		default:
   283  			// We have neither - this must be a mistake
   284  			return nil, i18n.NewError(ctx, i18n.MsgDataMissing, i)
   285  		}
   286  	}
   287  	return refs, nil
   288  }