Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 9 additions & 35 deletions src/main/java/land/oras/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,11 @@
* Class for config
*/
@NullUnmarked
public final class Config {
public final class Config extends Descriptor {

private final String mediaType;
private final String digest;
private final long size;

/**
* Annotations for the layer
* Can be nullable due to serialization
*/
private final @Nullable Map<String, String> annotations;

/**
* The base 64 encoded data
*/
Expand All @@ -56,24 +49,21 @@ public final class Config {
* @param size The size
*/
private Config(String mediaType, String digest, long size, @Nullable String data, Annotations annotations) {
this.mediaType = mediaType;
super(
mediaType,
!annotations.configAnnotations().isEmpty() ? Map.copyOf(annotations.configAnnotations()) : null);
this.digest = digest;
this.size = size;
this.data = data;
// Config annotation are generally empty since not default annotations are added by ORAS
if (!annotations.configAnnotations().isEmpty()) {
this.annotations = Map.copyOf(annotations.configAnnotations());
} else {
this.annotations = null;
}
}

/**
* Get the media type
* @return The media type
* Get the annotations
* @return The annotations
*/
public String getMediaType() {
return mediaType;
@Override
public @Nullable Map<String, String> getAnnotations() {
return annotations;
}

/**
Expand Down Expand Up @@ -134,22 +124,6 @@ public byte[] getDataBytes() {
return data;
}

/**
* Get the annotations
* @return The annotations
*/
public @Nullable Map<String, String> getAnnotations() {
return annotations;
}

/**
* Return the JSON representation of the config
* @return The JSON string
*/
public String toJson() {
return JsonUtils.toJson(this);
}

/**
* Create a config from a JSON string
* @param json The JSON string
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/land/oras/Descriptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*-
* =LICENSE=
* ORAS Java SDK
* ===
* Copyright (C) 2024 - 2025 ORAS
* ===
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =LICENSEEND=
*/

package land.oras;

import java.util.Collections;
import java.util.Map;
import land.oras.utils.JsonUtils;
import org.jspecify.annotations.Nullable;

/**
* Abstract class for descriptor
*/
public abstract sealed class Descriptor permits Config, Manifest, Layer {

/**
* The media type of the layer
*/
protected final String mediaType;

/**
* Annotations for the layer
*/
protected final @Nullable Map<String, String> annotations;

protected Descriptor(String mediaType, Map<String, String> annotations) {
this.mediaType = mediaType;
this.annotations = annotations;
}

/**
* Get the annotations
* @return The annotations
*/
public Map<String, String> getAnnotations() {
if (annotations == null) {
return Map.of();
}
return Collections.unmodifiableMap(annotations);
}

/**
* Get the media type
* @return The media type
*/
public final String getMediaType() {
return mediaType;
}

/**
* Return the JSON representation of this descriptor
* @return The JSON string
*/
public final String toJson() {
return JsonUtils.toJson(this);
}
}
46 changes: 3 additions & 43 deletions src/main/java/land/oras/Layer.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.Collections;
import java.util.Map;
import land.oras.exception.OrasException;
import land.oras.utils.Const;
Expand All @@ -37,12 +36,7 @@
* Class for layer
*/
@NullMarked
public final class Layer {

/**
* The media type of the layer
*/
private final String mediaType;
public final class Layer extends Descriptor {

/**
* The digest of the layer
Expand All @@ -64,11 +58,6 @@ public final class Layer {
*/
private final transient @Nullable Path blobPath;

/**
* Annotations for the layer
*/
private final @Nullable Map<String, String> annotations;

/**
* Constructor that can directly set the data
* Not adapted for large blob due to memory usage but convenient for small data
Expand All @@ -82,12 +71,11 @@ private Layer(
long size,
@Nullable String data,
@Nullable Map<String, String> annotations) {
this.mediaType = mediaType;
super(mediaType, annotations);
this.digest = digest;
this.size = size;
this.data = data;
this.blobPath = null;
this.annotations = annotations;
}

/**
Expand All @@ -98,20 +86,11 @@ private Layer(
* @param blobPath The path to the blob
*/
private Layer(String mediaType, String digest, long size, Path blobPath, Map<String, String> annotations) {
this.mediaType = mediaType;
super(mediaType, annotations);
this.digest = digest;
this.size = size;
this.data = null;
this.blobPath = blobPath;
this.annotations = annotations;
}

/**
* Get the media type
* @return The media type
*/
public String getMediaType() {
return mediaType;
}

/**
Expand Down Expand Up @@ -146,17 +125,6 @@ public long getSize() {
return blobPath;
}

/**
* Get the annotations
* @return The annotations
*/
public Map<String, String> getAnnotations() {
if (annotations == null) {
return Map.of();
}
return Collections.unmodifiableMap(annotations);
}

/**
* Create a new layer with annotations
* @param annotations The annotations
Expand Down Expand Up @@ -192,14 +160,6 @@ public byte[] getDataBytes() {
throw new OrasException("No data or blob path set");
}

/**
* Return the JSON representation of the manifest
* @return The JSON string
*/
public String toJson() {
return JsonUtils.toJson(this);
}

/**
* Create a layer from a JSON string
* @param json The JSON string
Expand Down
34 changes: 2 additions & 32 deletions src/main/java/land/oras/Manifest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,13 @@
* Class for manifest
*/
@NullUnmarked
public final class Manifest {
public final class Manifest extends Descriptor {

private final int schemaVersion;
private final String mediaType;
private final String artifactType;
private final Config config;
private final Subject subject;
private final List<Layer> layers;
private final Map<String, String> annotations;

/**
* The manifest descriptor
Expand All @@ -63,14 +61,13 @@ private Manifest(
List<Layer> layers,
Annotations annotations,
String json) {
super(mediaType, Map.copyOf(annotations.manifestAnnotations()));
this.schemaVersion = schemaVersion;
this.mediaType = mediaType;
this.artifactType = artifactType != null ? artifactType.getMediaType() : null;
this.descriptor = descriptor;
this.config = config;
this.subject = subject;
this.layers = layers;
this.annotations = Map.copyOf(annotations.manifestAnnotations());
this.json = json;
}

Expand All @@ -82,14 +79,6 @@ public int getSchemaVersion() {
return schemaVersion;
}

/**
* Get the media type
* @return The media type
*/
public String getMediaType() {
return mediaType;
}

/**
* Get the artifact type
* @return The artifact type
Expand Down Expand Up @@ -137,17 +126,6 @@ public List<Layer> getLayers() {
return layers != null ? Collections.unmodifiableList(layers) : List.of();
}

/**
* Get the annotations
* @return The annotations
*/
public Map<String, String> getAnnotations() {
if (annotations == null) {
return Map.of();
}
return annotations;
}

/**
* Return a new manifest with the given artifact type
* @param artifactType The artifact type
Expand Down Expand Up @@ -266,14 +244,6 @@ private Manifest withJson(String json) {
return this;
}

/**
* Return the JSON representation of the manifest
* @return The JSON string
*/
public String toJson() {
return JsonUtils.toJson(this);
}

/**
* Create a manifest from a JSON string
* @param json The JSON string
Expand Down
25 changes: 23 additions & 2 deletions src/main/java/land/oras/OCILayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,31 @@ public String getImageLayoutVersion() {
}

/**
* Copy the container ref from registry into oci-layout
* Copy the direct container ref from registry into oci-layout
* @param registry The registry
* @param containerRef The container
*/
public void copy(Registry registry, ContainerRef containerRef) {
copy(registry, containerRef, false);
}

/**
* Copy the container ref from registry into oci-layout
* @param registry The registry
* @param containerRef The container
* @param recursive True if references should be copied
*/
public void copy(Registry registry, ContainerRef containerRef, boolean recursive) {

try {

// Create blobs directory if needed
Files.createDirectories(getBlobPath());

// Write oci layout JSON
Files.writeString(getOciLayoutPath(), toJson());
if (!Files.exists(getOciLayoutPath())) {
Files.writeString(getOciLayoutPath(), toJson());
}

Map<String, String> headers = registry.getHeaders(containerRef);
String contentType = headers.get(Const.CONTENT_TYPE_HEADER.toLowerCase());
Expand All @@ -119,6 +131,15 @@ public void copy(Registry registry, ContainerRef containerRef) {
Manifest manifest = registry.getManifest(containerRef);
writeManifest(manifest);

if (recursive) {
LOG.debug("Recursively copy referrers");
Referrers referrers = registry.getReferrers(containerRef.withDigest(manifestDigest), null);
for (ManifestDescriptor referer : referrers.getManifests()) {
LOG.info("Copy reference {}", referer.getDigest());
copy(registry, containerRef.withDigest(referer.getDigest()), recursive);
}
}

// Write the index.json containing this manifest
Index index = Index.fromManifests(List.of(manifest.getDescriptor()));
writeIndex(index);
Expand Down
Loading