github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/blobserver/cond/cond.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  /*
    18  Package cond registers the "cond" conditional blobserver storage type
    19  to select routing of get/put operations on blobs to other storage
    20  targets as a function of their content.
    21  
    22  Currently only the "isSchema" predicate is defined.
    23  
    24  Example usage:
    25  
    26    "/bs-and-maybe-also-index/": {
    27  	"handler": "storage-cond",
    28  	"handlerArgs": {
    29  		"write": {
    30  			"if": "isSchema",
    31  			"then": "/bs-and-index/",
    32  			"else": "/bs/"
    33  		},
    34  		"read": "/bs/"
    35  	}
    36    }
    37  */
    38  package cond
    39  
    40  import (
    41  	"bytes"
    42  	"errors"
    43  	"fmt"
    44  	"io"
    45  	"net/http"
    46  	"time"
    47  
    48  	"camlistore.org/pkg/blob"
    49  	"camlistore.org/pkg/blobserver"
    50  	"camlistore.org/pkg/context"
    51  	"camlistore.org/pkg/jsonconfig"
    52  	"camlistore.org/pkg/schema"
    53  )
    54  
    55  const buffered = 8
    56  
    57  // A storageFunc selects a destination for a given blob. It may consume from src but must always return
    58  // a newSrc that is identical to the original src passed in.
    59  type storageFunc func(br blob.Ref, src io.Reader) (dest blobserver.Storage, newSrc io.Reader, err error)
    60  
    61  type condStorage struct {
    62  	storageForReceive storageFunc
    63  	read              blobserver.Storage
    64  	remove            blobserver.Storage
    65  
    66  	ctx *http.Request // optional per-request context
    67  }
    68  
    69  func (sto *condStorage) StorageGeneration() (initTime time.Time, random string, err error) {
    70  	if gener, ok := sto.read.(blobserver.Generationer); ok {
    71  		return gener.StorageGeneration()
    72  	}
    73  	err = blobserver.GenerationNotSupportedError(fmt.Sprintf("blobserver.Generationer not implemented on %T", sto.read))
    74  	return
    75  }
    76  
    77  func (sto *condStorage) ResetStorageGeneration() error {
    78  	if gener, ok := sto.read.(blobserver.Generationer); ok {
    79  		return gener.ResetStorageGeneration()
    80  	}
    81  	return blobserver.GenerationNotSupportedError(fmt.Sprintf("blobserver.Generationer not implemented on %T", sto.read))
    82  }
    83  
    84  func newFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (storage blobserver.Storage, err error) {
    85  	sto := &condStorage{}
    86  
    87  	receive := conf.OptionalStringOrObject("write")
    88  	read := conf.RequiredString("read")
    89  	remove := conf.OptionalString("remove", "")
    90  	if err := conf.Validate(); err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	if receive != nil {
    95  		sto.storageForReceive, err = buildStorageForReceive(ld, receive)
    96  		if err != nil {
    97  			return
    98  		}
    99  	}
   100  
   101  	sto.read, err = ld.GetStorage(read)
   102  	if err != nil {
   103  		return
   104  	}
   105  
   106  	if remove != "" {
   107  		sto.remove, err = ld.GetStorage(remove)
   108  		if err != nil {
   109  			return
   110  		}
   111  	}
   112  	return sto, nil
   113  }
   114  
   115  func buildStorageForReceive(ld blobserver.Loader, confOrString interface{}) (storageFunc, error) {
   116  	// Static configuration from a string
   117  	if s, ok := confOrString.(string); ok {
   118  		sto, err := ld.GetStorage(s)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  		f := func(br blob.Ref, src io.Reader) (blobserver.Storage, io.Reader, error) {
   123  			return sto, src, nil
   124  		}
   125  		return f, nil
   126  	}
   127  
   128  	conf := jsonconfig.Obj(confOrString.(map[string]interface{}))
   129  
   130  	ifStr := conf.RequiredString("if")
   131  	// TODO: let 'then' and 'else' point to not just strings but either
   132  	// a string or a JSON object with another condition, and then
   133  	// call buildStorageForReceive on it recursively
   134  	thenTarget := conf.RequiredString("then")
   135  	elseTarget := conf.RequiredString("else")
   136  	if err := conf.Validate(); err != nil {
   137  		return nil, err
   138  	}
   139  	thenSto, err := ld.GetStorage(thenTarget)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	elseSto, err := ld.GetStorage(elseTarget)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	switch ifStr {
   149  	case "isSchema":
   150  		return isSchemaPicker(thenSto, elseSto), nil
   151  	}
   152  	return nil, fmt.Errorf("cond: unsupported 'if' type of %q", ifStr)
   153  }
   154  
   155  func isSchemaPicker(thenSto, elseSto blobserver.Storage) storageFunc {
   156  	return func(br blob.Ref, src io.Reader) (dest blobserver.Storage, newSrc io.Reader, err error) {
   157  		var buf bytes.Buffer
   158  		blob, err := schema.BlobFromReader(br, io.TeeReader(src, &buf))
   159  		newSrc = io.MultiReader(bytes.NewReader(buf.Bytes()), src)
   160  		if err != nil || blob.Type() == "" {
   161  			return elseSto, newSrc, nil
   162  		}
   163  		return thenSto, newSrc, nil
   164  	}
   165  }
   166  
   167  func (sto *condStorage) ReceiveBlob(br blob.Ref, src io.Reader) (sb blob.SizedRef, err error) {
   168  	destSto, src, err := sto.storageForReceive(br, src)
   169  	if err != nil {
   170  		return
   171  	}
   172  	return blobserver.Receive(destSto, br, src)
   173  }
   174  
   175  func (sto *condStorage) RemoveBlobs(blobs []blob.Ref) error {
   176  	if sto.remove != nil {
   177  		return sto.remove.RemoveBlobs(blobs)
   178  	}
   179  	return errors.New("cond: Remove not configured")
   180  }
   181  
   182  func (sto *condStorage) Fetch(b blob.Ref) (file io.ReadCloser, size uint32, err error) {
   183  	if sto.read != nil {
   184  		return sto.read.Fetch(b)
   185  	}
   186  	err = errors.New("cond: Read not configured")
   187  	return
   188  }
   189  
   190  func (sto *condStorage) StatBlobs(dest chan<- blob.SizedRef, blobs []blob.Ref) error {
   191  	if sto.read != nil {
   192  		return sto.read.StatBlobs(dest, blobs)
   193  	}
   194  	return errors.New("cond: Read not configured")
   195  }
   196  
   197  func (sto *condStorage) EnumerateBlobs(ctx *context.Context, dest chan<- blob.SizedRef, after string, limit int) error {
   198  	if sto.read != nil {
   199  		return sto.read.EnumerateBlobs(ctx, dest, after, limit)
   200  	}
   201  	return errors.New("cond: Read not configured")
   202  }
   203  
   204  func init() {
   205  	blobserver.RegisterStorageConstructor("cond", blobserver.StorageConstructor(newFromConfig))
   206  }