1   /*
2    * Copyright 2017 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.internal.storage.repository;
18  
19  import java.util.Collections;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Set;
23  import java.util.concurrent.locks.Lock;
24  import java.util.concurrent.locks.ReentrantLock;
25  
26  import javax.annotation.Nullable;
27  
28  import com.fasterxml.jackson.databind.JsonNode;
29  import com.google.common.collect.ImmutableList;
30  import com.google.common.collect.ImmutableSet;
31  
32  import com.linecorp.centraldogma.common.Entry;
33  import com.linecorp.centraldogma.common.Revision;
34  import com.linecorp.centraldogma.internal.Jackson;
35  import com.linecorp.centraldogma.server.mirror.Mirror;
36  import com.linecorp.centraldogma.server.mirror.MirrorCredential;
37  import com.linecorp.centraldogma.server.storage.project.Project;
38  import com.linecorp.centraldogma.server.storage.repository.MetaRepository;
39  import com.linecorp.centraldogma.server.storage.repository.Repository;
40  
41  public final class DefaultMetaRepository extends RepositoryWrapper implements MetaRepository {
42  
43      public static final String PATH_CREDENTIALS = "/credentials.json";
44  
45      public static final String PATH_MIRRORS = "/mirrors.json";
46  
47      public static final Set<String> metaRepoFiles = ImmutableSet.of(PATH_CREDENTIALS, PATH_MIRRORS);
48  
49      private static final String PATH_CREDENTIALS_AND_MIRRORS = PATH_CREDENTIALS + ',' + PATH_MIRRORS;
50  
51      private final Lock mirrorLock = new ReentrantLock();
52  
53      /**
54       * The revision number of the /credentials.json and /mirrors.json who generated {@link #mirrors}.
55       */
56      private int mirrorRev = -1;
57  
58      /**
59       * The repositories of the parent {@link Project} at the moment when {@link #mirrors} is generated.
60       */
61      private Set<String> mirrorRepos = Collections.emptySet();
62  
63      @Nullable
64      private Set<Mirror> mirrors;
65  
66      public DefaultMetaRepository(Repository repo) {
67          super(repo);
68      }
69  
70      @Override
71      public Set<Mirror> mirrors() {
72          mirrorLock.lock();
73          try {
74              final int headRev = normalizeNow(Revision.HEAD).major();
75              final Set<String> repos = parent().repos().list().keySet();
76              if (headRev > mirrorRev || !mirrorRepos.equals(repos)) {
77                  mirrors = loadMirrors(headRev);
78                  mirrorRev = headRev;
79                  mirrorRepos = repos;
80              }
81  
82              return mirrors;
83          } finally {
84              mirrorLock.unlock();
85          }
86      }
87  
88      private Set<Mirror> loadMirrors(int rev) {
89          // TODO(trustin): Asynchronization
90          final Map<String, Entry<?>> entries =
91                  find(new Revision(rev), PATH_CREDENTIALS_AND_MIRRORS, Collections.emptyMap()).join();
92  
93          if (!entries.containsKey(PATH_MIRRORS)) {
94              return Collections.emptySet();
95          }
96  
97          final JsonNode mirrorsJson = (JsonNode) entries.get(PATH_MIRRORS).content();
98          if (!mirrorsJson.isArray()) {
99              throw new RepositoryMetadataException(
100                     PATH_MIRRORS + " must be an array: " + mirrorsJson.getNodeType());
101         }
102 
103         if (mirrorsJson.size() == 0) {
104             return Collections.emptySet();
105         }
106 
107         try {
108             final List<MirrorCredential> credentials = loadCredentials(entries);
109             final ImmutableSet.Builder<Mirror> mirrors = ImmutableSet.builder();
110 
111             for (JsonNode m : mirrorsJson) {
112                 final MirrorConfig c = Jackson.treeToValue(m, MirrorConfig.class);
113                 if (c == null) {
114                     throw new RepositoryMetadataException(PATH_MIRRORS + " contains null.");
115                 }
116                 final Mirror mirror = c.toMirror(parent(), credentials);
117                 if (mirror != null) {
118                     mirrors.add(mirror);
119                 }
120             }
121 
122             return mirrors.build();
123         } catch (RepositoryMetadataException e) {
124             throw e;
125         } catch (Exception e) {
126             throw new RepositoryMetadataException("failed to load the mirror configuration", e);
127         }
128     }
129 
130     private static List<MirrorCredential> loadCredentials(Map<String, Entry<?>> entries) throws Exception {
131         final Entry<?> e = entries.get(PATH_CREDENTIALS);
132         if (e == null) {
133             return Collections.emptyList();
134         }
135 
136         final JsonNode credentialsJson = (JsonNode) e.content();
137         if (!credentialsJson.isArray()) {
138             throw new RepositoryMetadataException(
139                     PATH_CREDENTIALS + " must be an array: " + credentialsJson.getNodeType());
140         }
141 
142         if (credentialsJson.size() == 0) {
143             return Collections.emptyList();
144         }
145 
146         final ImmutableList.Builder<MirrorCredential> builder = ImmutableList.builder();
147         for (JsonNode c : credentialsJson) {
148             final MirrorCredential credential = Jackson.treeToValue(c, MirrorCredential.class);
149             if (credential == null) {
150                 throw new RepositoryMetadataException(PATH_CREDENTIALS + " contains null.");
151             }
152             builder.add(credential);
153         }
154 
155         return builder.build();
156     }
157 }