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 }