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 javax.annotation.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       * Tokens which belong to this project.
73       */
74      private final Map<String, TokenRegistration> 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, TokenRegistration> tokens,
95                             @JsonProperty("appIds") @Nullable Map<String, TokenRegistration> 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 TokenRegistration}s of this project.
146      */
147     @JsonProperty
148     public Map<String, TokenRegistration> 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 TokenRegistration} of the specified application ID in this project.
207      */
208     @Nullable
209     public TokenRegistration tokenOrDefault(String appId, @Nullable TokenRegistration defaultToken) {
210         final TokenRegistration token = appIds.get(requireNonNull(appId, "appId"));
211         if (token != null) {
212             return token;
213         }
214         return defaultToken;
215     }
216 
217     @Override
218     public int weight() {
219         int weight = name().length();
220         for (RepositoryMetadata repo : repos.values()) {
221             weight += repo.weight();
222         }
223         for (Member member : members.values()) {
224             weight += member.weight();
225         }
226         for (TokenRegistration token : appIds.values()) {
227             weight += token.weight();
228         }
229 
230         return weight;
231     }
232 
233     @Override
234     public boolean equals(Object o) {
235         if (this == o) {
236             return true;
237         }
238         if (!(o instanceof ProjectMetadata)) {
239             return false;
240         }
241         final ProjectMetadata that = (ProjectMetadata) o;
242         return name.equals(that.name) &&
243                repos.equals(that.repos) &&
244                members.equals(that.members) &&
245                appIds.equals(that.appIds) &&
246                creation.equals(that.creation) &&
247                Objects.equals(removal, that.removal);
248     }
249 
250     @Override
251     public int hashCode() {
252         return Objects.hash(name, repos, members, appIds, creation, removal);
253     }
254 
255     @Override
256     public String toString() {
257         return MoreObjects.toStringHelper(this)
258                           .add("name", name())
259                           .add("repos", repos())
260                           .add("members", members())
261                           .add("appIds", appIds())
262                           .add("creation", creation())
263                           .add("removal", removal())
264                           .toString();
265     }
266 
267     /**
268      * Returns a new {@link ProjectMetadata} without the Dogma repository.
269      */
270     public ProjectMetadata withoutDogmaRepo() {
271         if (!repos().containsKey(Project.REPO_DOGMA)) {
272             return this;
273         }
274         final Map<String, RepositoryMetadata> filtered =
275                 repos().entrySet().stream().filter(entry -> !Project.REPO_DOGMA.equals(entry.getKey()))
276                        .collect(toImmutableMap(Entry::getKey, Entry::getValue));
277         return new ProjectMetadata(name(),
278                                    filtered,
279                                    members(),
280                                    null,
281                                    appIds(),
282                                    creation(),
283                                    removal());
284     }
285 }