Skip to content

Commit

Permalink
load devfile schema for validation by it's apiVersion (#14834)
Browse files Browse the repository at this point in the history
Signed-off-by: Michal Vala <mvala@redhat.com>
  • Loading branch information
sparkoo authored Oct 11, 2019
1 parent 3fdf3c1 commit ae2500f
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@
*/
package org.eclipse.che.api.workspace.server.devfile;

import java.util.Collections;
import java.util.List;

public class Constants {

private Constants() {}

public static final String SCHEMA_LOCATION = "schema/devfile.json";
public static final String SCHEMAS_LOCATION = "schema/";

public static final String SCHEMA_FILENAME = "devfile.json";

public static final String CURRENT_API_VERSION = "1.0.0";

public static final List<String> SUPPORTED_VERSIONS =
Collections.singletonList(CURRENT_API_VERSION);

public static final String EDITOR_COMPONENT_TYPE = "cheEditor";

public static final String PLUGIN_COMPONENT_TYPE = "chePlugin";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package org.eclipse.che.api.workspace.server.devfile;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
Expand Down Expand Up @@ -52,7 +53,7 @@ public DevfileService(DevfileSchemaProvider schemaCachedProvider) {
})
public Response getSchema() throws ServerException {
try {
return Response.ok(schemaCachedProvider.getSchemaContent()).build();
return Response.ok(schemaCachedProvider.getSchemaContent(CURRENT_API_VERSION)).build();
} catch (IOException e) {
throw new ServerException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,57 @@
*/
package org.eclipse.che.api.workspace.server.devfile.schema;

import static org.eclipse.che.api.workspace.server.devfile.Constants.SCHEMA_LOCATION;
import static org.eclipse.che.api.workspace.server.devfile.Constants.SCHEMAS_LOCATION;
import static org.eclipse.che.api.workspace.server.devfile.Constants.SCHEMA_FILENAME;
import static org.eclipse.che.api.workspace.server.devfile.Constants.SUPPORTED_VERSIONS;
import static org.eclipse.che.commons.lang.IoUtil.getResource;
import static org.eclipse.che.commons.lang.IoUtil.readAndCloseQuietly;

import java.io.IOException;
import java.io.StringReader;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Singleton;

/** Loads a schema content and stores it in soft reference. */
@Singleton
public class DevfileSchemaProvider {

private SoftReference<String> schemaRef = new SoftReference<>(null);
private Map<String, SoftReference<String>> schemas = new HashMap<>();

public String getSchemaContent() throws IOException {
String schema = schemaRef.get();
if (schema == null) {
schema = loadFile();
schemaRef = new SoftReference<>(schema);
public String getSchemaContent(String version) throws IOException {
if (schemas.containsKey(version)) {
String schema = schemas.get(version).get();
if (schema != null) {
return schema;
} else {
return loadAndPut(version);
}
} else {
return loadAndPut(version);
}
return schema;
}

public StringReader getAsReader() throws IOException {
return new StringReader(getSchemaContent());
public StringReader getAsReader(String version) throws IOException {
return new StringReader(getSchemaContent(version));
}

private String loadFile() throws IOException {
return readAndCloseQuietly(getResource(SCHEMA_LOCATION));
private String loadFile(String version) throws IOException {
try {
return readAndCloseQuietly(getResource(SCHEMAS_LOCATION + version + "/" + SCHEMA_FILENAME));
} catch (IOException ioe) {
throw new IOException(
String.format(
"Unable to load devfile schema with version '%s'. Supported versions are '%s'",
version, SUPPORTED_VERSIONS),
ioe);
}
}

private String loadAndPut(String version) throws IOException {
final String schema = loadFile(version);
schemas.put(version, new SoftReference<>(schema));
return schema;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
package org.eclipse.che.api.workspace.server.devfile.validator;

import static java.lang.String.format;
import static org.eclipse.che.api.workspace.server.devfile.Constants.SUPPORTED_VERSIONS;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.json.JsonReader;
Expand All @@ -37,7 +40,7 @@ public class DevfileSchemaValidator {
private final JsonValidationService service = JsonValidationService.newInstance();
private ObjectMapper yamlMapper;
private ObjectMapper jsonMapper;
private JsonSchema schema;
private Map<String, JsonSchema> schemasByVersion;
private ErrorMessageComposer errorMessageComposer;

@Inject
Expand All @@ -46,7 +49,10 @@ public DevfileSchemaValidator(DevfileSchemaProvider schemaProvider) {
this.jsonMapper = new ObjectMapper();
this.errorMessageComposer = new ErrorMessageComposer();
try {
this.schema = service.readSchema(schemaProvider.getAsReader());
this.schemasByVersion = new HashMap<>();
for (String version : SUPPORTED_VERSIONS) {
this.schemasByVersion.put(version, service.readSchema(schemaProvider.getAsReader(version)));
}
} catch (IOException e) {
throw new RuntimeException("Unable to read devfile json schema for validation.", e);
}
Expand Down Expand Up @@ -75,6 +81,19 @@ private void validate(JsonNode contentNode) throws DevfileFormatException {
try {
List<Problem> validationErrors = new ArrayList<>();
ProblemHandler handler = ProblemHandler.collectingTo(validationErrors);
if (!contentNode.hasNonNull("apiVersion")) {
throw new DevfileFormatException(
"Devfile schema validation failed. Error: The object must have a property whose name is \"apiVersion\".");
}
String apiVersion = contentNode.get("apiVersion").asText();

if (!schemasByVersion.containsKey(apiVersion)) {
throw new DevfileFormatException(
String.format(
"Version '%s' of the devfile is not supported. Supported versions are '%s'.",
apiVersion, SUPPORTED_VERSIONS));
}
JsonSchema schema = schemasByVersion.get(apiVersion);
try (JsonReader reader =
service.createReader(
new StringReader(jsonMapper.writeValueAsString(contentNode)), schema, handler)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,8 @@
"additionalProperties": false,
"properties": {
"apiVersion": {
"type": "string",
"title": "Devfile API Version",
"examples": [
"1.0.0"
]
"const": "1.0.0",
"title": "Devfile API Version"
},
"metadata": {
"type": "object",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package org.eclipse.che.api.workspace.server.devfile;

import static com.jayway.restassured.RestAssured.given;
import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION;
import static org.everrest.assured.JettyHttpServer.*;
import static org.testng.Assert.assertEquals;

Expand Down Expand Up @@ -54,6 +55,7 @@ public void shouldRetrieveSchema() throws Exception {
.get(SECURE_PATH + "/devfile");

assertEquals(response.getStatusCode(), 200);
assertEquals(response.getBody().asString(), schemaProvider.getSchemaContent());
assertEquals(
response.getBody().asString(), schemaProvider.getSchemaContent(CURRENT_API_VERSION));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

package org.eclipse.che.api.workspace.server.devfile.schema;

import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

import java.io.IOException;
import java.io.StringReader;
import org.testng.annotations.Test;

public class DevfileSchemaProviderTest {

private final DevfileSchemaProvider devfileSchemaProvider = new DevfileSchemaProvider();

@Test
public void shouldGetProperDevfileSchemaContent() throws IOException {
String content = devfileSchemaProvider.getSchemaContent(CURRENT_API_VERSION);
assertNotNull(content);
assertTrue(content.contains("This schema describes the structure of the devfile object"));
}

@Test
public void shouldGetProperDevfileSchemaContentAsReader() throws IOException {
StringReader contentReader = devfileSchemaProvider.getAsReader(CURRENT_API_VERSION);
assertNotNull(contentReader);

StringBuilder contentBuilder = new StringBuilder();
int c;
while ((c = contentReader.read()) != -1) {
contentBuilder.append((char) c);
}
assertTrue(
contentBuilder
.toString()
.contains("This schema describes the structure of the devfile object"));
}

@Test(expectedExceptions = IOException.class)
public void shouldThrowExceptionWhenInvalidVersionRequested() throws IOException {
devfileSchemaProvider.getSchemaContent("this_is_clearly_not_a_valid_schema_version");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.testng.Assert.fail;

import java.io.IOException;
import org.eclipse.che.api.workspace.server.devfile.Constants;
import org.eclipse.che.api.workspace.server.devfile.exception.DevfileFormatException;
import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider;
import org.testng.annotations.BeforeClass;
Expand Down Expand Up @@ -85,6 +86,24 @@ public void shouldThrowExceptionOnValidationOfNonValidDevfile(
fail("DevfileFormatException expected to be thrown but is was not");
}

@Test
public void shouldThrowExceptionWhenDevfileHasUnsupportedApiVersion() throws Exception {
try {
String devfile =
"---\n" + "apiVersion: 111.111\n" + "metadata:\n" + " name: test-invalid-apiversion\n";
schemaValidator.validateYaml(devfile);
} catch (DevfileFormatException e) {
assertEquals(
e.getMessage(),
"Version '111.111' of the devfile is not supported. "
+ "Supported versions are '"
+ Constants.SUPPORTED_VERSIONS
+ "'.");
return;
}
fail("DevfileFormatException expected to be thrown but is was not");
}

@DataProvider
public Object[][] invalidDevfiles() {
return new Object[][] {
Expand Down

0 comments on commit ae2500f

Please sign in to comment.