1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.server.internal.storage.project;
18
19 import static com.linecorp.centraldogma.server.internal.storage.project.ProjectInitializer.INTERNAL_PROJECT_DOGMA;
20 import static com.linecorp.centraldogma.server.metadata.MetadataService.METADATA_JSON;
21 import static java.util.Objects.requireNonNull;
22
23 import java.io.File;
24 import java.util.concurrent.Executor;
25 import java.util.concurrent.atomic.AtomicReference;
26
27 import javax.annotation.Nullable;
28
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import com.fasterxml.jackson.core.JsonParseException;
33 import com.fasterxml.jackson.databind.JsonMappingException;
34 import com.fasterxml.jackson.databind.JsonNode;
35 import com.google.common.collect.ImmutableMap;
36
37 import com.linecorp.centraldogma.common.Author;
38 import com.linecorp.centraldogma.common.CentralDogmaException;
39 import com.linecorp.centraldogma.common.Change;
40 import com.linecorp.centraldogma.common.Entry;
41 import com.linecorp.centraldogma.common.Markup;
42 import com.linecorp.centraldogma.common.ProjectExistsException;
43 import com.linecorp.centraldogma.common.ProjectNotFoundException;
44 import com.linecorp.centraldogma.common.Query;
45 import com.linecorp.centraldogma.common.RepositoryExistsException;
46 import com.linecorp.centraldogma.common.Revision;
47 import com.linecorp.centraldogma.internal.Jackson;
48 import com.linecorp.centraldogma.internal.Util;
49 import com.linecorp.centraldogma.server.internal.storage.repository.DefaultMetaRepository;
50 import com.linecorp.centraldogma.server.internal.storage.repository.RepositoryCache;
51 import com.linecorp.centraldogma.server.internal.storage.repository.cache.CachingRepositoryManager;
52 import com.linecorp.centraldogma.server.internal.storage.repository.git.GitRepositoryManager;
53 import com.linecorp.centraldogma.server.metadata.Member;
54 import com.linecorp.centraldogma.server.metadata.PerRolePermissions;
55 import com.linecorp.centraldogma.server.metadata.ProjectMetadata;
56 import com.linecorp.centraldogma.server.metadata.ProjectRole;
57 import com.linecorp.centraldogma.server.metadata.RepositoryMetadata;
58 import com.linecorp.centraldogma.server.metadata.UserAndTimestamp;
59 import com.linecorp.centraldogma.server.storage.project.Project;
60 import com.linecorp.centraldogma.server.storage.repository.MetaRepository;
61 import com.linecorp.centraldogma.server.storage.repository.Repository;
62 import com.linecorp.centraldogma.server.storage.repository.RepositoryManager;
63
64 public class DefaultProject implements Project {
65
66 private static final Logger logger = LoggerFactory.getLogger(DefaultProject.class);
67
68 private final String name;
69 private final long creationTimeMillis;
70 private final Author author;
71 final RepositoryManager repos;
72 private final AtomicReference<MetaRepository> metaRepo = new AtomicReference<>();
73
74
75
76
77 DefaultProject(File rootDir, Executor repositoryWorker, Executor purgeWorker,
78 @Nullable RepositoryCache cache) {
79 requireNonNull(rootDir, "rootDir");
80 requireNonNull(repositoryWorker, "repositoryWorker");
81
82 if (!rootDir.exists()) {
83 throw new ProjectNotFoundException(rootDir.toString());
84 }
85
86 name = rootDir.getName();
87 repos = newRepoManager(rootDir, repositoryWorker, purgeWorker, cache);
88
89 boolean success = false;
90 try {
91 createReservedRepos(System.currentTimeMillis());
92 final UserAndTimestamp creation = metadataCreation();
93 if (creation != null) {
94 creationTimeMillis = creation.timestampMillis();
95 author = Author.ofEmail(creation.user());
96 } else {
97 creationTimeMillis = repos.get(REPO_DOGMA).creationTimeMillis();
98 author = repos.get(REPO_DOGMA).author();
99 }
100 success = true;
101 } finally {
102 if (!success) {
103 repos.close(() -> new CentralDogmaException("failed to initialize internal repositories"));
104 }
105 }
106 }
107
108
109
110
111 DefaultProject(File rootDir, Executor repositoryWorker, Executor purgeWorker,
112 long creationTimeMillis, Author author, @Nullable RepositoryCache cache) {
113 requireNonNull(rootDir, "rootDir");
114 requireNonNull(repositoryWorker, "repositoryWorker");
115
116 if (rootDir.exists()) {
117 throw new ProjectExistsException(rootDir.getName());
118 }
119
120 name = rootDir.getName();
121 repos = newRepoManager(rootDir, repositoryWorker, purgeWorker, cache);
122
123 boolean success = false;
124 try {
125 createReservedRepos(creationTimeMillis);
126 initializeMetadata(creationTimeMillis, author);
127 this.creationTimeMillis = creationTimeMillis;
128 this.author = author;
129 success = true;
130 } finally {
131 if (!success) {
132 repos.close(() -> new CentralDogmaException("failed to initialize internal repositories"));
133 }
134 }
135 }
136
137 @Nullable
138 private UserAndTimestamp metadataCreation() {
139 if (name.equals(INTERNAL_PROJECT_DOGMA)) {
140 return null;
141 }
142 final Entry<JsonNode> metadata = repos.get(REPO_DOGMA)
143 .get(Revision.HEAD, Query.ofJson(METADATA_JSON))
144 .join();
145 try {
146 return Jackson.treeToValue(metadata.content(), ProjectMetadata.class)
147 .creation();
148 } catch (JsonParseException | JsonMappingException e) {
149 logger.warn("Failed to retrieve creation in {} file. project: {}", METADATA_JSON, name);
150 return null;
151 }
152 }
153
154 private RepositoryManager newRepoManager(File rootDir, Executor repositoryWorker, Executor purgeWorker,
155 @Nullable RepositoryCache cache) {
156
157 final GitRepositoryManager gitRepos =
158 new GitRepositoryManager(this, rootDir, repositoryWorker, purgeWorker, cache);
159 return cache == null ? gitRepos : new CachingRepositoryManager(gitRepos, cache);
160 }
161
162 private void createReservedRepos(long creationTimeMillis) {
163 if (!repos.exists(REPO_DOGMA)) {
164 try {
165 repos.create(REPO_DOGMA, creationTimeMillis, Author.SYSTEM);
166 } catch (RepositoryExistsException ignored) {
167
168 }
169 }
170 if (!repos.exists(REPO_META)) {
171 try {
172 repos.create(REPO_META, creationTimeMillis, Author.SYSTEM);
173 } catch (RepositoryExistsException ignored) {
174
175 }
176 }
177 }
178
179 private void initializeMetadata(long creationTimeMillis, Author author) {
180
181 if (name.equals(INTERNAL_PROJECT_DOGMA)) {
182 return;
183 }
184
185 final Repository dogmaRepo = repos.get(REPO_DOGMA);
186 final Revision headRev = dogmaRepo.normalizeNow(Revision.HEAD);
187 if (!dogmaRepo.exists(headRev, METADATA_JSON).join()) {
188 logger.info("Initializing metadata: {}", name);
189
190 final UserAndTimestamp userAndTimestamp = UserAndTimestamp.of(author);
191 final RepositoryMetadata repo = new RepositoryMetadata(REPO_META, userAndTimestamp,
192 PerRolePermissions.ofInternal());
193 final Member member = new Member(author, ProjectRole.OWNER, userAndTimestamp);
194 final ProjectMetadata metadata = new ProjectMetadata(name,
195 ImmutableMap.of(repo.id(), repo),
196 ImmutableMap.of(member.id(), member),
197 ImmutableMap.of(),
198 userAndTimestamp, null);
199
200 dogmaRepo.commit(headRev, creationTimeMillis, Author.SYSTEM,
201 "Initialize metadata", "", Markup.PLAINTEXT,
202 Change.ofJsonUpsert(METADATA_JSON, Jackson.valueToTree(metadata))).join();
203 }
204 }
205
206 @Override
207 public String name() {
208 return name;
209 }
210
211 @Override
212 public long creationTimeMillis() {
213 return creationTimeMillis;
214 }
215
216 @Override
217 public Author author() {
218 return author;
219 }
220
221 @Override
222 public MetaRepository metaRepo() {
223 MetaRepository metaRepo = this.metaRepo.get();
224 if (metaRepo != null) {
225 return metaRepo;
226 }
227
228 metaRepo = new DefaultMetaRepository(repos.get(REPO_META));
229 if (this.metaRepo.compareAndSet(null, metaRepo)) {
230 return metaRepo;
231 } else {
232 return this.metaRepo.get();
233 }
234 }
235
236 @Override
237 public RepositoryManager repos() {
238 return repos;
239 }
240
241 @Override
242 public String toString() {
243 return Util.simpleTypeName(getClass()) + '(' + repos + ')';
244 }
245 }