1   /*
2    * Copyright 2023 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package com.linecorp.centraldogma.xds.internal;
18  
19  import static com.google.common.collect.ImmutableList.toImmutableList;
20  
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Objects;
25  
26  import com.google.common.collect.ImmutableMap;
27  import com.google.common.hash.Hashing;
28  import com.google.protobuf.Message;
29  
30  import com.linecorp.centraldogma.common.Revision;
31  
32  import io.envoyproxy.controlplane.cache.ResourceVersionResolver;
33  import io.envoyproxy.controlplane.cache.Resources;
34  import io.envoyproxy.controlplane.cache.SnapshotResources;
35  import io.envoyproxy.controlplane.cache.VersionedResource;
36  
37  final class CentralDogmaSnapshotResources<T extends Message> extends SnapshotResources<T> {
38  
39      public static <T extends Message> SnapshotResources<T> create(
40              Iterable<T> resources, Revision revision) {
41          final ImmutableMap.Builder<String, VersionedResource<T>> versionedResourcesMap = ImmutableMap.builder();
42          final ImmutableMap.Builder<String, T> resourcesMap = ImmutableMap.builder();
43          for (T resource : resources) {
44              final String resourceName = Resources.getResourceName(resource);
45              versionedResourcesMap.put(resourceName, VersionedResource.create(resource));
46              resourcesMap.put(resourceName, resource);
47          }
48          return new CentralDogmaSnapshotResources<>(versionedResourcesMap.build(), resourcesMap.build(),
49                                                     Integer.toString(revision.major()));
50      }
51  
52      private final Map<String, VersionedResource<T>> versionedResources;
53      private final Map<String, T> resources;
54      private final ResourceVersionResolver resourceVersionResolver;
55  
56      private CentralDogmaSnapshotResources(
57              Map<String, VersionedResource<T>> versionedResources, ImmutableMap<String, T> resources,
58              String allResourceVersion) {
59          this.versionedResources = versionedResources;
60          this.resources = resources;
61          resourceVersionResolver = resourceNames -> {
62              if (resourceNames.isEmpty()) { // All resources
63                  return allResourceVersion;
64              }
65              if (resourceNames.size() == 1) {
66                  final VersionedResource<T> versionedResource = versionedResources.get(resourceNames.get(0));
67                  if (versionedResource == null) {
68                      return "";
69                  }
70                  return versionedResource.version();
71              }
72              final ArrayList<String> sorted = new ArrayList<>(resourceNames);
73              sorted.sort(String::compareTo);
74  
75              final List<VersionedResource<T>> collected = sorted.stream().map(versionedResources::get)
76                                                                 .filter(Objects::nonNull)
77                                                                 .distinct()
78                                                                 .collect(toImmutableList());
79              if (collected.isEmpty()) {
80                  // There is no resource with the given names.
81                  return "";
82              }
83              if (collected.size() == 1) {
84                  return collected.get(0).version();
85              }
86              if (collected.size() == versionedResources.size()) {
87                  // All resources are included.
88                  return allResourceVersion;
89              }
90  
91              return Hashing.sha256().hashInt(collected.hashCode()).toString();
92          };
93      }
94  
95      @Override
96      public Map<String, VersionedResource<T>> versionedResources() {
97          return versionedResources;
98      }
99  
100     @Override
101     public Map<String, T> resources() {
102         return resources;
103     }
104 
105     @Override
106     public ResourceVersionResolver resourceVersionResolver() {
107         return resourceVersionResolver;
108     }
109 }