github.com/altipla-consulting/ravendb-go-client@v0.1.3/document_subscriptions.go (about)

     1  package ravendb
     2  
     3  import (
     4  	"io"
     5  	"reflect"
     6  	"sync"
     7  )
     8  
     9  // DocumentSubscriptions allows subscribing to changes in the store
    10  type DocumentSubscriptions struct {
    11  	store         *DocumentStore
    12  	subscriptions map[io.Closer]bool
    13  	mu            sync.Mutex // protects subscriptions
    14  }
    15  
    16  func newDocumentSubscriptions(store *DocumentStore) *DocumentSubscriptions {
    17  	return &DocumentSubscriptions{
    18  		store:         store,
    19  		subscriptions: map[io.Closer]bool{},
    20  	}
    21  }
    22  
    23  // Create creates a data subscription in a database. The subscription will expose all documents that match the specified subscription options for a given type.
    24  func (s *DocumentSubscriptions) Create(options *SubscriptionCreationOptions, database string) (string, error) {
    25  	if options == nil {
    26  		return "", newIllegalArgumentError("Cannot create a subscription if options is nil")
    27  	}
    28  
    29  	if options.Query == "" {
    30  		return "", newIllegalArgumentError("Cannot create a subscription if Query is empty string")
    31  	}
    32  
    33  	if database == "" {
    34  		database = s.store.GetDatabase()
    35  	}
    36  	requestExecutor := s.store.GetRequestExecutor(database)
    37  
    38  	command := newCreateSubscriptionCommand(s.store.GetConventions(), options, "")
    39  	if err := requestExecutor.ExecuteCommand(command, nil); err != nil {
    40  		return "", err
    41  	}
    42  
    43  	return command.Result.Name, nil
    44  }
    45  
    46  // CreateForType creates a data subscription in a database. The subscription will
    47  // expose all documents that match the specified subscription options for a given type.
    48  func (s *DocumentSubscriptions) CreateForType(clazz reflect.Type, options *SubscriptionCreationOptions, database string) (string, error) {
    49  	if options == nil {
    50  		options = &SubscriptionCreationOptions{}
    51  
    52  	}
    53  	creationOptions := &SubscriptionCreationOptions{
    54  		Name:         options.Name,
    55  		ChangeVector: options.ChangeVector,
    56  		Query:        options.Query,
    57  	}
    58  
    59  	opts := s.ensureCriteria(creationOptions, clazz, false)
    60  	return s.Create(opts, database)
    61  }
    62  
    63  // CreateForRevisions creates a data subscription in a database. The subscription will
    64  // expose all documents that match the specified subscription options for a given type.
    65  func (s *DocumentSubscriptions) CreateForRevisions(clazz reflect.Type, options *SubscriptionCreationOptions, database string) (string, error) {
    66  	if options == nil {
    67  		options = &SubscriptionCreationOptions{}
    68  	}
    69  	creationOptions := &SubscriptionCreationOptions{
    70  		Name:         options.Name,
    71  		ChangeVector: options.ChangeVector,
    72  		Query:        options.Query,
    73  	}
    74  
    75  	opts := s.ensureCriteria(creationOptions, clazz, true)
    76  	return s.Create(opts, database)
    77  }
    78  
    79  func (s *DocumentSubscriptions) ensureCriteria(criteria *SubscriptionCreationOptions, clazz reflect.Type, revisions bool) *SubscriptionCreationOptions {
    80  	if criteria == nil {
    81  		criteria = &SubscriptionCreationOptions{}
    82  	}
    83  
    84  	collectionName := s.store.GetConventions().getCollectionName(clazz)
    85  	if criteria.Query == "" {
    86  		if revisions {
    87  			criteria.Query = "from " + collectionName + " (Revisions = true) as doc"
    88  		} else {
    89  			criteria.Query = "from " + collectionName + " as doc"
    90  		}
    91  	}
    92  
    93  	return criteria
    94  }
    95  
    96  // GetSubscriptionWorker opens a subscription and starts pulling documents since
    97  // a last processed document for that subscription.
    98  // The connection options determine client and server cooperation rules like
    99  // document batch sizes or a timeout in a matter of which a client
   100  // needs to acknowledge that batch has been processed. The acknowledgment
   101  // is sent after all documents are processed by subscription's handlers.
   102  func (s *DocumentSubscriptions) GetSubscriptionWorker(clazz reflect.Type, options *SubscriptionWorkerOptions, database string) (*SubscriptionWorker, error) {
   103  	if err := s.store.assertInitialized(); err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	if options == nil {
   108  		return nil, newIllegalStateError("Cannot open a subscription if options are null")
   109  	}
   110  
   111  	subscription, err := NewSubscriptionWorker(clazz, options, false, s.store, database)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	fn := func(worker *SubscriptionWorker) {
   116  		s.mu.Lock()
   117  		delete(s.subscriptions, worker)
   118  		s.mu.Unlock()
   119  	}
   120  	subscription.onClosed = fn
   121  	s.mu.Lock()
   122  	s.subscriptions[subscription] = true
   123  	s.mu.Unlock()
   124  
   125  	return subscription, nil
   126  }
   127  
   128  // GetSubscriptionWorkerForRevisions opens a subscription and starts pulling documents
   129  // since a last processed document for that subscription.
   130  // The connection options determine client and server cooperation rules like document
   131  // batch sizes or a timeout in a matter of which a client
   132  // needs to acknowledge that batch has been processed. The acknowledgment is sent
   133  // after all documents are processed by subscription's handlers.
   134  func (s *DocumentSubscriptions) GetSubscriptionWorkerForRevisions(clazz reflect.Type, options *SubscriptionWorkerOptions, database string) (*SubscriptionWorker, error) {
   135  	subscription, err := NewSubscriptionWorker(clazz, options, true, s.store, database)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	fn := func(sender *SubscriptionWorker) {
   141  		s.mu.Lock()
   142  		delete(s.subscriptions, sender)
   143  		s.mu.Unlock()
   144  	}
   145  
   146  	subscription.onClosed = fn
   147  	s.mu.Lock()
   148  	s.subscriptions[subscription] = true
   149  	s.mu.Unlock()
   150  
   151  	return subscription, nil
   152  }
   153  
   154  // GetSubscriptions downloads a list of all existing subscriptions in a database.
   155  func (s *DocumentSubscriptions) GetSubscriptions(start int, take int, database string) ([]*SubscriptionState, error) {
   156  	if database == "" {
   157  		database = s.store.GetDatabase()
   158  	}
   159  	requestExecutor := s.store.GetRequestExecutor(database)
   160  
   161  	command := newGetSubscriptionsCommand(start, take)
   162  	if err := requestExecutor.ExecuteCommand(command, nil); err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	return command.Result, nil
   167  }
   168  
   169  // Delete deletes a subscription.
   170  func (s *DocumentSubscriptions) Delete(name string, database string) error {
   171  	if database == "" {
   172  		database = s.store.GetDatabase()
   173  	}
   174  	requestExecutor := s.store.GetRequestExecutor(database)
   175  
   176  	command := newDeleteSubscriptionCommand(name)
   177  	return requestExecutor.ExecuteCommand(command, nil)
   178  }
   179  
   180  // GetSubscriptionState returns subscription definition and it's current state
   181  func (s *DocumentSubscriptions) GetSubscriptionState(subscriptionName string, database string) (*SubscriptionState, error) {
   182  	if subscriptionName == "" {
   183  		return nil, newIllegalArgumentError("SubscriptionName cannot be null")
   184  	}
   185  
   186  	if database == "" {
   187  		database = s.store.GetDatabase()
   188  	}
   189  	requestExecutor := s.store.GetRequestExecutor(database)
   190  
   191  	command := newGetSubscriptionStateCommand(subscriptionName)
   192  	if err := requestExecutor.ExecuteCommand(command, nil); err != nil {
   193  		return nil, err
   194  	}
   195  	return command.Result, nil
   196  }
   197  
   198  // Close closes subscriptions
   199  func (s *DocumentSubscriptions) Close() error {
   200  	if len(s.subscriptions) == 0 {
   201  		return nil
   202  	}
   203  
   204  	var err error
   205  	for subscription := range s.subscriptions {
   206  		err2 := subscription.Close()
   207  		if err2 != nil {
   208  			err = err2
   209  		}
   210  	}
   211  	return err
   212  }
   213  
   214  // DropConnection forces server to close current client subscription connection to the server
   215  func (s *DocumentSubscriptions) DropConnection(name string, database string) error {
   216  	if database == "" {
   217  		database = s.store.GetDatabase()
   218  	}
   219  	requestExecutor := s.store.GetRequestExecutor(database)
   220  
   221  	command := newDropSubscriptionConnectionCommand(name)
   222  	return requestExecutor.ExecuteCommand(command, nil)
   223  }