1/*
2Copyright (c) 2015 VMware, Inc. All Rights Reserved.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package property
18
19import (
20	"context"
21	"errors"
22
23	"github.com/vmware/govmomi/vim25"
24	"github.com/vmware/govmomi/vim25/methods"
25	"github.com/vmware/govmomi/vim25/mo"
26	"github.com/vmware/govmomi/vim25/soap"
27	"github.com/vmware/govmomi/vim25/types"
28)
29
30// Collector models the PropertyCollector managed object.
31//
32// For more information, see:
33// http://pubs.vmware.com/vsphere-60/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvmodl.query.PropertyCollector.html
34//
35type Collector struct {
36	roundTripper soap.RoundTripper
37	reference    types.ManagedObjectReference
38}
39
40// DefaultCollector returns the session's default property collector.
41func DefaultCollector(c *vim25.Client) *Collector {
42	p := Collector{
43		roundTripper: c,
44		reference:    c.ServiceContent.PropertyCollector,
45	}
46
47	return &p
48}
49
50func (p Collector) Reference() types.ManagedObjectReference {
51	return p.reference
52}
53
54// Create creates a new session-specific Collector that can be used to
55// retrieve property updates independent of any other Collector.
56func (p *Collector) Create(ctx context.Context) (*Collector, error) {
57	req := types.CreatePropertyCollector{
58		This: p.Reference(),
59	}
60
61	res, err := methods.CreatePropertyCollector(ctx, p.roundTripper, &req)
62	if err != nil {
63		return nil, err
64	}
65
66	newp := Collector{
67		roundTripper: p.roundTripper,
68		reference:    res.Returnval,
69	}
70
71	return &newp, nil
72}
73
74// Destroy destroys this Collector.
75func (p *Collector) Destroy(ctx context.Context) error {
76	req := types.DestroyPropertyCollector{
77		This: p.Reference(),
78	}
79
80	_, err := methods.DestroyPropertyCollector(ctx, p.roundTripper, &req)
81	if err != nil {
82		return err
83	}
84
85	p.reference = types.ManagedObjectReference{}
86	return nil
87}
88
89func (p *Collector) CreateFilter(ctx context.Context, req types.CreateFilter) error {
90	req.This = p.Reference()
91
92	_, err := methods.CreateFilter(ctx, p.roundTripper, &req)
93	if err != nil {
94		return err
95	}
96
97	return nil
98}
99
100func (p *Collector) WaitForUpdates(ctx context.Context, v string) (*types.UpdateSet, error) {
101	req := types.WaitForUpdatesEx{
102		This:    p.Reference(),
103		Version: v,
104	}
105
106	res, err := methods.WaitForUpdatesEx(ctx, p.roundTripper, &req)
107	if err != nil {
108		return nil, err
109	}
110
111	return res.Returnval, nil
112}
113
114func (p *Collector) RetrieveProperties(ctx context.Context, req types.RetrieveProperties) (*types.RetrievePropertiesResponse, error) {
115	req.This = p.Reference()
116	return methods.RetrieveProperties(ctx, p.roundTripper, &req)
117}
118
119// Retrieve loads properties for a slice of managed objects. The dst argument
120// must be a pointer to a []interface{}, which is populated with the instances
121// of the specified managed objects, with the relevant properties filled in. If
122// the properties slice is nil, all properties are loaded.
123func (p *Collector) Retrieve(ctx context.Context, objs []types.ManagedObjectReference, ps []string, dst interface{}) error {
124	if len(objs) == 0 {
125		return errors.New("object references is empty")
126	}
127
128	var propSpec *types.PropertySpec
129	var objectSet []types.ObjectSpec
130
131	for _, obj := range objs {
132		// Ensure that all object reference types are the same
133		if propSpec == nil {
134			propSpec = &types.PropertySpec{
135				Type: obj.Type,
136			}
137
138			if ps == nil {
139				propSpec.All = types.NewBool(true)
140			} else {
141				propSpec.PathSet = ps
142			}
143		} else {
144			if obj.Type != propSpec.Type {
145				return errors.New("object references must have the same type")
146			}
147		}
148
149		objectSpec := types.ObjectSpec{
150			Obj:  obj,
151			Skip: types.NewBool(false),
152		}
153
154		objectSet = append(objectSet, objectSpec)
155	}
156
157	req := types.RetrieveProperties{
158		SpecSet: []types.PropertyFilterSpec{
159			{
160				ObjectSet: objectSet,
161				PropSet:   []types.PropertySpec{*propSpec},
162			},
163		},
164	}
165
166	res, err := p.RetrieveProperties(ctx, req)
167	if err != nil {
168		return err
169	}
170
171	if d, ok := dst.(*[]types.ObjectContent); ok {
172		*d = res.Returnval
173		return nil
174	}
175
176	return mo.LoadRetrievePropertiesResponse(res, dst)
177}
178
179// RetrieveWithFilter populates dst as Retrieve does, but only for entities matching the given filter.
180func (p *Collector) RetrieveWithFilter(ctx context.Context, objs []types.ManagedObjectReference, ps []string, dst interface{}, filter Filter) error {
181	if len(filter) == 0 {
182		return p.Retrieve(ctx, objs, ps, dst)
183	}
184
185	var content []types.ObjectContent
186
187	err := p.Retrieve(ctx, objs, filter.Keys(), &content)
188	if err != nil {
189		return err
190	}
191
192	objs = filter.MatchObjectContent(content)
193
194	if len(objs) == 0 {
195		return nil
196	}
197
198	return p.Retrieve(ctx, objs, ps, dst)
199}
200
201// RetrieveOne calls Retrieve with a single managed object reference.
202func (p *Collector) RetrieveOne(ctx context.Context, obj types.ManagedObjectReference, ps []string, dst interface{}) error {
203	var objs = []types.ManagedObjectReference{obj}
204	return p.Retrieve(ctx, objs, ps, dst)
205}
206