1 package io.apicurio.registry.maven;
2
3 import com.fasterxml.jackson.databind.JsonNode;
4 import com.fasterxml.jackson.databind.ObjectMapper;
5 import io.apicurio.registry.content.ContentHandle;
6 import io.apicurio.registry.content.TypedContent;
7 import io.apicurio.registry.content.refs.ExternalReference;
8 import io.apicurio.registry.content.refs.ReferenceFinder;
9 import io.apicurio.registry.maven.refs.IndexedResource;
10 import io.apicurio.registry.maven.refs.ReferenceIndex;
11 import io.apicurio.registry.rest.client.RegistryClient;
12 import io.apicurio.registry.rest.client.models.*;
13 import io.apicurio.registry.types.ArtifactType;
14 import io.apicurio.registry.types.ContentTypes;
15 import io.apicurio.registry.types.provider.ArtifactTypeUtilProvider;
16 import io.apicurio.registry.types.provider.DefaultArtifactTypeUtilProviderImpl;
17 import io.vertx.core.Vertx;
18 import org.apache.commons.io.FileUtils;
19 import org.apache.maven.plugin.MojoExecutionException;
20 import org.apache.maven.plugin.MojoFailureException;
21 import org.apache.maven.plugins.annotations.Mojo;
22 import org.apache.maven.plugins.annotations.Parameter;
23
24 import java.io.*;
25 import java.nio.charset.StandardCharsets;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.*;
30 import java.util.concurrent.ExecutionException;
31 import java.util.stream.Collectors;
32
33
34
35
36 @Mojo(name = "register")
37 public class RegisterRegistryMojo extends AbstractRegistryMojo {
38
39
40
41
42 @Parameter(required = false)
43 List<ExistingReference> existingReferences;
44
45
46
47
48 @Parameter(required = true)
49 List<RegisterArtifact> artifacts;
50
51
52
53
54 @Parameter(property = "skipRegister", defaultValue = "false")
55 boolean skip;
56
57
58
59
60
61 @Parameter(property = "dryRun", defaultValue = "false")
62 boolean dryRun;
63
64 private RegisterArtifact.AvroAutoRefsNamingStrategy avroAutoRefsNamingStrategy;
65
66 DefaultArtifactTypeUtilProviderImpl utilProviderFactory = new DefaultArtifactTypeUtilProviderImpl(true);
67
68
69
70
71 protected boolean validate() throws MojoExecutionException {
72 if (skip) {
73 getLog().info("register is skipped.");
74 return false;
75 }
76
77 if (artifacts == null || artifacts.isEmpty()) {
78 getLog().warn("No artifacts are configured for registration.");
79 return false;
80 }
81
82 if (existingReferences == null) {
83 existingReferences = new ArrayList<>();
84 }
85
86 int idx = 0;
87 int errorCount = 0;
88 for (RegisterArtifact artifact : artifacts) {
89 if (artifact.getGroupId() == null) {
90 getLog().error(String.format(
91 "GroupId is required when registering an artifact. Missing from artifacts[%d].",
92 idx));
93 errorCount++;
94 }
95 if (artifact.getArtifactId() == null) {
96 getLog().error(String.format(
97 "ArtifactId is required when registering an artifact. Missing from artifacts[%s].",
98 idx));
99 errorCount++;
100 }
101 if (artifact.getFile() == null) {
102 getLog().error(String.format(
103 "File is required when registering an artifact. Missing from artifacts[%s].", idx));
104 errorCount++;
105 } else if (!artifact.getFile().exists()) {
106 getLog().error(
107 String.format("Artifact file to register is configured but file does not exist: %s",
108 artifact.getFile().getPath()));
109 errorCount++;
110 }
111
112 idx++;
113 }
114
115 if (errorCount > 0) {
116 throw new MojoExecutionException(
117 "Invalid configuration of the Register Artifact(s) mojo. See the output log for details.");
118 }
119 return true;
120 }
121
122 @Override
123 protected void executeInternal() throws MojoExecutionException {
124 int errorCount = 0;
125 if (validate()) {
126 Vertx vertx = createVertx();
127 RegistryClient registryClient = createClient(vertx);
128
129 for (RegisterArtifact artifact : artifacts) {
130 String groupId = artifact.getGroupId();
131 String artifactId = artifact.getArtifactId();
132 try {
133 if (artifact.getAutoRefs() != null && artifact.getAutoRefs()) {
134
135
136 ReferenceIndex index = createIndex(artifact);
137 addExistingReferencesToIndex(registryClient, index, existingReferences);
138 addExistingReferencesToIndex(registryClient, index, artifact.getExistingReferences());
139 Stack<RegisterArtifact> registrationStack = new Stack<>();
140
141 this.avroAutoRefsNamingStrategy = artifact.getAvroAutoRefsNamingStrategy();
142 registerWithAutoRefs(registryClient, artifact, index, registrationStack);
143 } else {
144 List<ArtifactReference> references = new ArrayList<>();
145
146 if (hasReferences(artifact)) {
147 references = processArtifactReferences(registryClient, artifact.getReferences());
148 }
149 registerArtifact(registryClient, artifact, references);
150 }
151 } catch (Exception e) {
152 errorCount++;
153 getLog().error(String.format("Exception while registering artifact [%s] / [%s]", groupId,
154 artifactId), e);
155 }
156
157 }
158
159 if (errorCount > 0) {
160 throw new MojoExecutionException("Errors while registering artifacts ...");
161 }
162 }
163 }
164
165 private VersionMetaData registerWithAutoRefs(RegistryClient registryClient, RegisterArtifact artifact,
166 ReferenceIndex index, Stack<RegisterArtifact> registrationStack) throws IOException,
167 ExecutionException, InterruptedException, MojoExecutionException, MojoFailureException {
168 if (loopDetected(artifact, registrationStack)) {
169 throw new MojoExecutionException(
170 "Artifact reference loop detected (not supported): " + printLoop(registrationStack));
171 }
172 registrationStack.push(artifact);
173
174
175 ContentHandle artifactContent = readContent(artifact.getFile());
176 String artifactContentType = getContentTypeByExtension(artifact.getFile().getName());
177
178 if (artifact.getContentType() == null) {
179 artifact.setContentType(artifactContentType);
180 }
181 TypedContent typedArtifactContent = TypedContent.create(artifactContent, artifactContentType);
182
183
184 ArtifactTypeUtilProvider provider = this.utilProviderFactory
185 .getArtifactTypeProvider(artifact.getArtifactType());
186 ReferenceFinder referenceFinder = provider.getReferenceFinder();
187 var referenceArtifactIdentifierExtractor = provider.getReferenceArtifactIdentifierExtractor();
188 Set<ExternalReference> externalReferences = referenceFinder
189 .findExternalReferences(typedArtifactContent);
190
191
192 List<ArtifactReference> registeredReferences = new ArrayList<>(externalReferences.size());
193 for (ExternalReference externalRef : externalReferences) {
194 IndexedResource iresource = index.lookup(externalRef.getResource(),
195 Paths.get(artifact.getFile().toURI()));
196
197
198 if (iresource == null) {
199 throw new MojoExecutionException("Reference could not be resolved. From: "
200 + artifact.getFile().getName() + " To: " + externalRef.getFullReference());
201 }
202
203
204 if (!iresource.isRegistered()) {
205 String groupId = artifact.getGroupId();
206
207 String artifactId = referenceArtifactIdentifierExtractor.extractArtifactId(externalRef.getResource());
208 if (ArtifactType.AVRO.equals(iresource.getType())) {
209 if (avroAutoRefsNamingStrategy == RegisterArtifact.AvroAutoRefsNamingStrategy.USE_AVRO_NAMESPACE) {
210 groupId = referenceArtifactIdentifierExtractor.extractGroupId(externalRef.getResource());
211 artifactId = referenceArtifactIdentifierExtractor.extractArtifactId(externalRef.getResource());
212 }
213 if (avroAutoRefsNamingStrategy == RegisterArtifact.AvroAutoRefsNamingStrategy.INHERIT_PARENT_GROUP) {
214 groupId = artifact.getGroupId();
215 artifactId = iresource.getResourceName();
216 }
217 }
218 File localFile = getLocalFile(iresource.getPath());
219 RegisterArtifact refArtifact = buildFromRoot(artifact, artifactId, groupId);
220 refArtifact.setArtifactType(iresource.getType());
221 refArtifact.setVersion(null);
222 refArtifact.setFile(localFile);
223 refArtifact.setContentType(getContentTypeByExtension(localFile.getName()));
224 try {
225 var car = registerWithAutoRefs(registryClient, refArtifact, index, registrationStack);
226 iresource.setRegistration(car);
227 } catch (IOException | ExecutionException | InterruptedException e) {
228 throw new RuntimeException(e);
229 }
230 }
231
232 var reference = new ArtifactReference();
233 reference.setName(externalRef.getFullReference());
234 reference.setVersion(iresource.getRegistration().getVersion());
235 reference.setGroupId(iresource.getRegistration().getGroupId());
236 reference.setArtifactId(iresource.getRegistration().getArtifactId());
237 registeredReferences.add(reference);
238 }
239 registeredReferences.sort((ref1, ref2) -> ref1.getName().compareTo(ref2.getName()));
240
241 registrationStack.pop();
242 return registerArtifact(registryClient, artifact, registeredReferences);
243 }
244
245 private VersionMetaData registerArtifact(RegistryClient registryClient, RegisterArtifact artifact,
246 List<ArtifactReference> references) throws FileNotFoundException, ExecutionException,
247 InterruptedException, MojoExecutionException, MojoFailureException {
248 if (artifact.getFile() != null) {
249 return registerArtifact(registryClient, artifact, new FileInputStream(artifact.getFile()),
250 references);
251 } else {
252 return getArtifactVersionMetadata(registryClient, artifact);
253 }
254 }
255
256 private VersionMetaData getArtifactVersionMetadata(RegistryClient registryClient,
257 RegisterArtifact artifact) {
258 String groupId = artifact.getGroupId();
259 String artifactId = artifact.getArtifactId();
260 String version = artifact.getVersion();
261
262 VersionMetaData amd = registryClient.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId)
263 .versions().byVersionExpression(version).get();
264 getLog().info(String.format("Successfully processed artifact [%s] / [%s]. GlobalId is [%d]", groupId,
265 artifactId, amd.getGlobalId()));
266
267 return amd;
268 }
269
270 private VersionMetaData registerArtifact(RegistryClient registryClient, RegisterArtifact artifact,
271 InputStream artifactContent, List<ArtifactReference> references)
272 throws ExecutionException, InterruptedException, MojoFailureException, MojoExecutionException {
273 String groupId = artifact.getGroupId();
274 String artifactId = artifact.getArtifactId();
275 String version = artifact.getVersion();
276 String type = artifact.getArtifactType();
277 Boolean canonicalize = artifact.getCanonicalize();
278 Boolean isDraft = artifact.getIsDraft();
279 String ct = artifact.getContentType() == null ? ContentTypes.APPLICATION_JSON
280 : artifact.getContentType();
281 String data = null;
282 try {
283 if (artifact.getMinify() != null && artifact.getMinify()) {
284 ObjectMapper objectMapper = new ObjectMapper();
285 JsonNode jsonNode = objectMapper.readValue(artifactContent, JsonNode.class);
286 data = jsonNode.toString();
287 } else {
288 data = new String(artifactContent.readAllBytes(), StandardCharsets.UTF_8);
289 }
290 } catch (IOException e) {
291 throw new RuntimeException(e);
292 }
293
294 CreateArtifact createArtifact = new CreateArtifact();
295 createArtifact.setArtifactId(artifactId);
296 createArtifact.setArtifactType(type);
297
298 CreateVersion createVersion = new CreateVersion();
299 createVersion.setVersion(version);
300 createVersion.setIsDraft(isDraft);
301 createArtifact.setFirstVersion(createVersion);
302
303 VersionContent content = new VersionContent();
304 content.setContent(data);
305 content.setContentType(ct);
306 content.setReferences(references.stream().map(r -> {
307 ArtifactReference ref = new ArtifactReference();
308 ref.setArtifactId(r.getArtifactId());
309 ref.setGroupId(r.getGroupId());
310 ref.setVersion(r.getVersion());
311 ref.setName(r.getName());
312 return ref;
313 }).collect(Collectors.toList()));
314 createVersion.setContent(content);
315
316 try {
317 var vmd = registryClient.groups().byGroupId(groupId).artifacts().post(createArtifact, config -> {
318 if (artifact.getIfExists() != null) {
319 config.queryParameters.ifExists = IfArtifactExists
320 .forValue(artifact.getIfExists().value());
321 if (dryRun) {
322 config.queryParameters.dryRun = true;
323 }
324 }
325 config.queryParameters.canonical = canonicalize;
326 });
327
328 getLog().info(String.format("Successfully registered artifact [%s] / [%s]. GlobalId is [%d]",
329 groupId, artifactId, vmd.getVersion().getGlobalId()));
330
331
332 return vmd.getVersion();
333 } catch (RuleViolationProblemDetails | ProblemDetails e) {
334
335
336 if (Boolean.TRUE.equals(artifact.getIsDraft()) && e.getResponseStatusCode() == 409) {
337 try {
338 registryClient.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId)
339 .versions().byVersionExpression(version).content()
340 .put(content, config -> {
341
342 });
343 getLog().info(String.format("Successfully updated artifact [%s] / [%s].",
344 groupId, artifactId));
345
346 return registryClient.groups().byGroupId(groupId).artifacts().byArtifactId(artifactId).versions().byVersionExpression(version).get();
347 } catch (RuleViolationProblemDetails | ProblemDetails pd) {
348 logAndThrow(pd);
349 return null;
350 }
351 } else {
352 logAndThrow(e);
353 return null;
354 }
355 }
356 }
357
358 private static boolean hasReferences(RegisterArtifact artifact) {
359 return artifact.getReferences() != null && !artifact.getReferences().isEmpty();
360 }
361
362 private List<ArtifactReference> processArtifactReferences(RegistryClient registryClient,
363 List<RegisterArtifactReference> referencedArtifacts) throws FileNotFoundException,
364 ExecutionException, InterruptedException, MojoExecutionException, MojoFailureException {
365 List<ArtifactReference> references = new ArrayList<>();
366 for (RegisterArtifactReference artifact : referencedArtifacts) {
367 List<ArtifactReference> nestedReferences = new ArrayList<>();
368
369
370 if (hasReferences(artifact)) {
371 nestedReferences = processArtifactReferences(registryClient, artifact.getReferences());
372 }
373 final VersionMetaData artifactMetaData = registerArtifact(registryClient, artifact,
374 nestedReferences);
375 references.add(buildReferenceFromMetadata(artifactMetaData, artifact.getName()));
376 }
377 return references;
378 }
379
380 public void setArtifacts(List<RegisterArtifact> artifacts) {
381 this.artifacts = artifacts;
382 }
383
384 public void setSkip(boolean skip) {
385 this.skip = skip;
386 }
387
388 private static ArtifactReference buildReferenceFromMetadata(VersionMetaData metaData,
389 String referenceName) {
390 ArtifactReference reference = new ArtifactReference();
391 reference.setName(referenceName);
392 reference.setArtifactId(metaData.getArtifactId());
393 reference.setGroupId(metaData.getGroupId());
394 reference.setVersion(metaData.getVersion());
395 return reference;
396 }
397
398 private static boolean isFileAllowedInIndex(File file) {
399 return file.isFile() && (
400 file.getName().toLowerCase().endsWith(".json") ||
401 file.getName().toLowerCase().endsWith(".yml") ||
402 file.getName().toLowerCase().endsWith(".yaml") ||
403 file.getName().toLowerCase().endsWith(".xml") ||
404 file.getName().toLowerCase().endsWith(".xsd") ||
405 file.getName().toLowerCase().endsWith(".wsdl") ||
406 file.getName().toLowerCase().endsWith(".graphql") ||
407 file.getName().toLowerCase().endsWith(".avsc") ||
408 file.getName().toLowerCase().endsWith(".proto")
409 );
410 }
411
412
413
414
415
416
417 private static ReferenceIndex createIndex(RegisterArtifact artifact) {
418 File file = artifact.getFile();
419 ReferenceIndex index = new ReferenceIndex(file.getParentFile().toPath());
420 if (artifact.getProtoPaths() != null) {
421 artifact.getProtoPaths().forEach(path -> index.addSchemaPath(path.toPath()));
422 }
423
424 HashSet<File> roots = new HashSet<>();
425 if (artifact.getProtoPaths() != null) {
426 roots.addAll(artifact.getProtoPaths());
427 } else {
428 roots.add(file.getParentFile());
429 }
430
431 Collection<File> allFiles = new HashSet<>();
432 for (File root : roots) {
433 allFiles.addAll(FileUtils.listFiles(root, null, true));
434 allFiles.stream().filter(RegisterRegistryMojo::isFileAllowedInIndex).forEach(f -> {
435 index.index(f.toPath(), readContent(f));
436 });
437 }
438
439 return index;
440 }
441
442 private void addExistingReferencesToIndex(RegistryClient registryClient, ReferenceIndex index,
443 List<ExistingReference> existingReferences) throws ExecutionException, InterruptedException {
444 if (existingReferences != null && !existingReferences.isEmpty()) {
445 for (ExistingReference ref : existingReferences) {
446 VersionMetaData vmd;
447 if (ref.getVersion() == null || "LATEST".equalsIgnoreCase(ref.getVersion())) {
448 vmd = registryClient.groups().byGroupId(ref.getGroupId()).artifacts()
449 .byArtifactId(ref.getArtifactId()).versions().byVersionExpression("branch=latest")
450 .get();
451 } else {
452 vmd = new VersionMetaData();
453 vmd.setGroupId(ref.getGroupId());
454 vmd.setArtifactId(ref.getArtifactId());
455 vmd.setVersion(ref.getVersion());
456 }
457 index.index(ref.getResourceName(), vmd);
458 }
459 }
460 }
461
462 protected static ContentHandle readContent(File file) {
463 try {
464 return ContentHandle.create(Files.readAllBytes(file.toPath()));
465 } catch (IOException e) {
466 throw new RuntimeException("Failed to read schema file: " + file, e);
467 }
468 }
469
470 protected static RegisterArtifact buildFromRoot(RegisterArtifact rootArtifact, String artifactId, String groupId) {
471 RegisterArtifact nestedSchema = new RegisterArtifact();
472 nestedSchema.setCanonicalize(rootArtifact.getCanonicalize());
473 nestedSchema.setArtifactId(artifactId);
474 nestedSchema.setGroupId(groupId == null ? rootArtifact.getGroupId() : groupId);
475 nestedSchema.setContentType(rootArtifact.getContentType());
476 nestedSchema.setArtifactType(rootArtifact.getArtifactType());
477 nestedSchema.setMinify(rootArtifact.getMinify());
478 nestedSchema.setContentType(rootArtifact.getContentType());
479 nestedSchema.setIfExists(rootArtifact.getIfExists());
480 nestedSchema.setAutoRefs(rootArtifact.getAutoRefs());
481 return nestedSchema;
482 }
483
484 private static File getLocalFile(Path path) {
485 return path.toFile();
486 }
487
488
489
490
491
492
493
494 private static boolean loopDetected(RegisterArtifact artifact,
495 Stack<RegisterArtifact> registrationStack) {
496 for (RegisterArtifact stackArtifact : registrationStack) {
497 if (artifact.getFile().equals(stackArtifact.getFile())) {
498 return true;
499 }
500 }
501 return false;
502 }
503
504 private static String printLoop(Stack<RegisterArtifact> registrationStack) {
505 return registrationStack.stream().map(artifact -> artifact.getFile().getName())
506 .collect(Collectors.joining(" -> "));
507 }
508
509 }