1   /*
2    * Copyright 2019 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.server.metadata;
18  
19  import static com.google.common.collect.ImmutableMap.toImmutableMap;
20  import static java.util.Objects.requireNonNull;
21  
22  import java.util.Map;
23  import java.util.Map.Entry;
24  import java.util.Objects;
25  
26  import org.jspecify.annotations.Nullable;
27  
28  import com.fasterxml.jackson.annotation.JsonCreator;
29  import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
30  import com.fasterxml.jackson.annotation.JsonInclude;
31  import com.fasterxml.jackson.annotation.JsonInclude.Include;
32  import com.fasterxml.jackson.annotation.JsonProperty;
33  import com.google.common.base.MoreObjects;
34  import com.google.common.collect.ImmutableMap;
35  
36  import com.linecorp.centraldogma.common.RepositoryNotFoundException;
37  import com.linecorp.centraldogma.server.storage.project.Project;
38  import com.linecorp.centraldogma.server.storage.repository.HasWeight;
39  
40  /**
41   * Specifies details of a {@link Project}.
42   */
43  @JsonIgnoreProperties(ignoreUnknown = true)
44  @JsonInclude(Include.NON_NULL)
45  public class ProjectMetadata implements Identifiable, HasWeight {
46  
47      public static final ProjectMetadata DOGMA_PROJECT_METADATA =
48              new ProjectMetadata("dogma",
49                                  ImmutableMap.of(),
50                                  ImmutableMap.of(),
51                                  null,
52                                  ImmutableMap.of(),
53                                  new UserAndTimestamp(User.SYSTEM.id()),
54                                  null);
55  
56      /**
57       * A project name.
58       */
59      private final String name;
60  
61      /**
62       * Repositories of this project.
63       */
64      private final Map<String, RepositoryMetadata> repos;
65  
66      /**
67       * Members of this project.
68       */
69      private final Map<String, Member> members;
70  
71      /**
72       * App identities which belong to this project.
73       */
74      private final Map<String, AppIdentityRegistration> appIds;
75  
76      /**
77       * Specifies when this project is created by whom.
78       */
79      private final UserAndTimestamp creation;
80  
81      /**
82       * Specifies when this project is removed by whom.
83       */
84      @Nullable
85      private final UserAndTimestamp removal;
86  
87      /**
88       * Creates a new instance.
89       */
90      @JsonCreator
91      public ProjectMetadata(@JsonProperty("name") String name,
92                             @JsonProperty("repos") Map<String, RepositoryMetadata> repos,
93                             @JsonProperty("members") Map<String, Member> members,
94                             @JsonProperty("tokens") @Nullable Map<String, AppIdentityRegistration> tokens,
95                             @JsonProperty("appIds") @Nullable Map<String, AppIdentityRegistration> appIds,
96                             @JsonProperty("creation") UserAndTimestamp creation,
97                             @JsonProperty("removal") @Nullable UserAndTimestamp removal) {
98          this.name = requireNonNull(name, "name");
99          this.repos = ImmutableMap.copyOf(requireNonNull(repos, "repos"));
100         this.members = ImmutableMap.copyOf(requireNonNull(members, "members"));
101         if (tokens == null && appIds == null) {
102             throw new IllegalArgumentException("tokens or appIds are required");
103         }
104 
105         if (appIds != null) {
106             this.appIds = ImmutableMap.copyOf(appIds);
107         } else {
108             this.appIds = ImmutableMap.copyOf(tokens);
109         }
110 
111         this.creation = requireNonNull(creation, "creation");
112         this.removal = removal;
113     }
114 
115     @Override
116     public String id() {
117         return name;
118     }
119 
120     /**
121      * Returns the project name.
122      */
123     @JsonProperty
124     public String name() {
125         return name;
126     }
127 
128     /**
129      * Returns the metadata of the repositories in this project.
130      */
131     @JsonProperty
132     public Map<String, RepositoryMetadata> repos() {
133         return repos;
134     }
135 
136     /**
137      * Returns the {@link Member}s of this project.
138      */
139     @JsonProperty
140     public Map<String, Member> members() {
141         return members;
142     }
143 
144     /**
145      * Returns the {@link AppIdentityRegistration}s of this project.
146      */
147     @JsonProperty
148     public Map<String, AppIdentityRegistration> appIds() {
149         return appIds;
150     }
151 
152     /**
153      * Returns who created this project when.
154      */
155     @JsonProperty
156     public UserAndTimestamp creation() {
157         return creation;
158     }
159 
160     /**
161      * Returns who removed this project when.
162      */
163     @Nullable
164     @JsonProperty
165     public UserAndTimestamp removal() {
166         return removal;
167     }
168 
169     /**
170      * Returns the {@link RepositoryMetadata} of the specified repository in this project.
171      */
172     public RepositoryMetadata repo(String repoName) {
173         final RepositoryMetadata repositoryMetadata =
174                 repos.get(requireNonNull(repoName, "repoName"));
175         if (repositoryMetadata != null) {
176             return repositoryMetadata;
177         }
178         throw RepositoryNotFoundException.of(name, repoName);
179     }
180 
181     /**
182      * Returns the {@link Member} of the specified ID in this project.
183      */
184     public Member member(String memberId) {
185         final Member member = memberOrDefault(memberId, null);
186         if (member != null) {
187             return member;
188         }
189         throw new MemberNotFoundException(memberId, name());
190     }
191 
192     /**
193      * Returns the {@link Member} of the specified ID in this project.
194      * {@code defaultMember} is returned if there is no such member.
195      */
196     @Nullable
197     public Member memberOrDefault(String memberId, @Nullable Member defaultMember) {
198         final Member member = members.get(requireNonNull(memberId, "memberId"));
199         if (member != null) {
200             return member;
201         }
202         return defaultMember;
203     }
204 
205     /**
206      * Returns the {@link AppIdentityRegistration} of the specified application ID in this project.
207      */
208     @Nullable
209     public AppIdentityRegistration appIdentityOrDefault(String appId,
210                                                         @Nullable AppIdentityRegistration defaultAppIdentity) {
211         final AppIdentityRegistration appIdentityRegistration = appIds.get(requireNonNull(appId, "appId"));
212         if (appIdentityRegistration != null) {
213             return appIdentityRegistration;
214         }
215         return defaultAppIdentity;
216     }
217 
218     @Override
219     public int weight() {
220         int weight = name().length();
221         for (RepositoryMetadata repo : repos.values()) {
222             weight += repo.weight();
223         }
224         for (Member member : members.values()) {
225             weight += member.weight();
226         }
227         for (AppIdentityRegistration appIdentityRegistration : appIds.values()) {
228             weight += appIdentityRegistration.weight();
229         }
230 
231         return weight;
232     }
233 
234     @Override
235     public boolean equals(Object o) {
236         if (this == o) {
237             return true;
238         }
239         if (!(o instanceof ProjectMetadata)) {
240             return false;
241         }
242         final ProjectMetadata that = (ProjectMetadata) o;
243         return name.equals(that.name) &&
244                repos.equals(that.repos) &&
245                members.equals(that.members) &&
246                appIds.equals(that.appIds) &&
247                creation.equals(that.creation) &&
248                Objects.equals(removal, that.removal);
249     }
250 
251     @Override
252     public int hashCode() {
253         return Objects.hash(name, repos, members, appIds, creation, removal);
254     }
255 
256     @Override
257     public String toString() {
258         return MoreObjects.toStringHelper(this)
259                           .add("name", name())
260                           .add("repos", repos())
261                           .add("members", members())
262                           .add("appIds", appIds())
263                           .add("creation", creation())
264                           .add("removal", removal())
265                           .toString();
266     }
267 
268     /**
269      * Returns a new {@link ProjectMetadata} without the Dogma repository.
270      */
271     public ProjectMetadata withoutDogmaRepo() {
272         if (!repos().containsKey(Project.REPO_DOGMA)) {
273             return this;
274         }
275         final Map<String, RepositoryMetadata> filtered =
276                 repos().entrySet().stream().filter(entry -> !Project.REPO_DOGMA.equals(entry.getKey()))
277                        .collect(toImmutableMap(Entry::getKey, Entry::getValue));
278         return new ProjectMetadata(name(),
279                                    filtered,
280                                    members(),
281                                    null,
282                                    appIds(),
283                                    creation(),
284                                    removal());
285     }
286 }