chore: import upstream snapshot with attribution
Integration Tests - MySQL + Elasticsearch / Detect Changes (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / integration-tests-mysql-elasticsearch (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / integration-tests-postgres-elasticsearch-redis (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / integration-tests-postgres-opensearch (push) Has been cancelled
Java Checkstyle / java-checkstyle (push) Has been cancelled
Maven Collate Tests / maven-collate-ci (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests-status (push) Has been cancelled
Publish Package to Maven Central Repository / publish-maven-packages (push) Has been cancelled
OpenMetadata Service Unit Tests / Detect Changes (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests (push) Has been cancelled
OpenMetadata Service Unit Tests / k8s_operator-unit-tests (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / Detect Changes (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / integration-tests-mysql-elasticsearch (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / integration-tests-postgres-elasticsearch-redis (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / integration-tests-postgres-opensearch (push) Has been cancelled
Java Checkstyle / java-checkstyle (push) Has been cancelled
Maven Collate Tests / maven-collate-ci (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests-status (push) Has been cancelled
Publish Package to Maven Central Repository / publish-maven-packages (push) Has been cancelled
OpenMetadata Service Unit Tests / Detect Changes (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests (push) Has been cancelled
OpenMetadata Service Unit Tests / k8s_operator-unit-tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
config.stopBubbling = true
|
||||
lombok.log.fieldName = LOG
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2021 Collate
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>platform</artifactId>
|
||||
<groupId>org.open-metadata</groupId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<properties>
|
||||
<dropwizard.swagger.version>2.1.4-1</dropwizard.swagger.version>
|
||||
</properties>
|
||||
|
||||
<artifactId>common</artifactId>
|
||||
<name>OpenMetadata Common</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<!-- Required dependency for @Slf4j annotation -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<!--test dependencies-->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${org.junit.jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Build-time only (used by jsonschema2pojo-maven-plugin in openmetadata-spec).
|
||||
<scope>provided</scope> keeps it on compile classpath but excludes it (and all
|
||||
transitives — including Jackson 3.x: GHSA-2m67-wjpj-xhg9, CVE-2026-29062,
|
||||
GHSA-72hv-8253-57qq) from runtime / dist packaging. -->
|
||||
<dependency>
|
||||
<groupId>org.jsonschema2pojo</groupId>
|
||||
<artifactId>jsonschema2pojo-core</artifactId>
|
||||
<version>${jsonschema2pojo.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.dropwizard</groupId>
|
||||
<artifactId>dropwizard-jersey</artifactId>
|
||||
<version>${dropwizard.version}</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>log4j-over-slf4j</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
<version>1.17.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>${maven-source-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>${maven-javadoc-plugin.version}</version>
|
||||
<configuration>
|
||||
<doclint>none</doclint>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<gpgArguments>
|
||||
<arg>--pinentry-mode</arg>
|
||||
<arg>loopback</arg>
|
||||
</gpgArguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.theoryinpractise</groupId>
|
||||
<artifactId>googleformatter-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.openmetadata.annotations;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.sun.codemodel.JAnnotationUse;
|
||||
import com.sun.codemodel.JClass;
|
||||
import com.sun.codemodel.JDefinedClass;
|
||||
import com.sun.codemodel.JFieldVar;
|
||||
import com.sun.codemodel.JMethod;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.TreeMap;
|
||||
import org.jsonschema2pojo.AbstractAnnotator;
|
||||
|
||||
/** Add {@link Deprecated} annotation to generated Java classes */
|
||||
public class DeprecatedAnnotator extends AbstractAnnotator {
|
||||
|
||||
/** Add {@link Deprecated} annotation to property fields */
|
||||
@Override
|
||||
public void propertyField(
|
||||
JFieldVar field, JDefinedClass clazz, String propertyName, JsonNode propertyNode) {
|
||||
super.propertyField(field, clazz, propertyName, propertyNode);
|
||||
if (propertyNode.get("deprecated") != null && propertyNode.get("deprecated").asBoolean()) {
|
||||
field.annotate(Deprecated.class);
|
||||
}
|
||||
}
|
||||
|
||||
/** Add {@link Deprecated} annotation to getter methods */
|
||||
@Override
|
||||
public void propertyGetter(JMethod getter, JDefinedClass clazz, String propertyName) {
|
||||
super.propertyGetter(getter, clazz, propertyName);
|
||||
addDeprecatedAnnotationIfApplies(getter, propertyName);
|
||||
}
|
||||
|
||||
/** Add {@link Deprecated} annotation to setter methods */
|
||||
@Override
|
||||
public void propertySetter(JMethod setter, JDefinedClass clazz, String propertyName) {
|
||||
super.propertySetter(setter, clazz, propertyName);
|
||||
addDeprecatedAnnotationIfApplies(setter, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use reflection methods to access the {@link JDefinedClass} of the {@link JMethod} object. If
|
||||
* the {@link JMethod} is pointing to a field annotated with {@link Deprecated} then annotates
|
||||
* the {@link JMethod} object with {@link Deprecated}
|
||||
*/
|
||||
private void addDeprecatedAnnotationIfApplies(JMethod jMethod, String propertyName) {
|
||||
try {
|
||||
Field outerClassField = JMethod.class.getDeclaredField("outer");
|
||||
outerClassField.setAccessible(true);
|
||||
JDefinedClass outerClass = (JDefinedClass) outerClassField.get(jMethod);
|
||||
|
||||
TreeMap<String, JFieldVar> insensitiveFieldsMap =
|
||||
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
insensitiveFieldsMap.putAll(outerClass.fields());
|
||||
|
||||
if (insensitiveFieldsMap.containsKey(propertyName)
|
||||
&& insensitiveFieldsMap.get(propertyName).annotations().stream()
|
||||
.anyMatch(
|
||||
annotation ->
|
||||
Deprecated.class.getName().equals(getAnnotationClassName(annotation)))) {
|
||||
jMethod.annotate(Deprecated.class);
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAnnotationClassName(JAnnotationUse annotation) {
|
||||
try {
|
||||
Field clazzField = JAnnotationUse.class.getDeclaredField("clazz");
|
||||
clazzField.setAccessible(true);
|
||||
return ((JClass) clazzField.get(annotation)).fullName();
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.openmetadata.annotations;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.sun.codemodel.JAnnotationUse;
|
||||
import com.sun.codemodel.JClass;
|
||||
import com.sun.codemodel.JDefinedClass;
|
||||
import com.sun.codemodel.JFieldVar;
|
||||
import com.sun.codemodel.JMethod;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.TreeMap;
|
||||
import org.jsonschema2pojo.AbstractAnnotator;
|
||||
|
||||
/** Add {@link ExposedField} annotation to generated Java classes */
|
||||
public class ExposedAnnotator extends AbstractAnnotator {
|
||||
|
||||
/** Add {@link ExposedField} annotation property fields */
|
||||
@Override
|
||||
public void propertyField(
|
||||
JFieldVar field, JDefinedClass clazz, String propertyName, JsonNode propertyNode) {
|
||||
super.propertyField(field, clazz, propertyName, propertyNode);
|
||||
if (propertyNode.get("expose") != null && propertyNode.get("expose").asBoolean()) {
|
||||
field.annotate(ExposedField.class);
|
||||
}
|
||||
}
|
||||
|
||||
/** Add {@link ExposedField} annotation to getter methods */
|
||||
@Override
|
||||
public void propertyGetter(JMethod getter, JDefinedClass clazz, String propertyName) {
|
||||
super.propertyGetter(getter, clazz, propertyName);
|
||||
addMaskedFieldAnnotationIfApplies(getter, propertyName);
|
||||
}
|
||||
|
||||
/** Add {@link ExposedField} annotation to setter methods */
|
||||
@Override
|
||||
public void propertySetter(JMethod setter, JDefinedClass clazz, String propertyName) {
|
||||
super.propertySetter(setter, clazz, propertyName);
|
||||
addMaskedFieldAnnotationIfApplies(setter, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use reflection methods to access the {@link JDefinedClass} of the {@link JMethod} object. If
|
||||
* the {@link JMethod} is pointing to a field annotated with {@link ExposedField} then annotates
|
||||
* the {@link JMethod} object with {@link ExposedField}
|
||||
*/
|
||||
private void addMaskedFieldAnnotationIfApplies(JMethod jMethod, String propertyName) {
|
||||
try {
|
||||
Field outerClassField = JMethod.class.getDeclaredField("outer");
|
||||
outerClassField.setAccessible(true);
|
||||
JDefinedClass outerClass = (JDefinedClass) outerClassField.get(jMethod);
|
||||
|
||||
TreeMap<String, JFieldVar> insensitiveFieldsMap =
|
||||
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
insensitiveFieldsMap.putAll(outerClass.fields());
|
||||
|
||||
if (insensitiveFieldsMap.containsKey(propertyName)
|
||||
&& insensitiveFieldsMap.get(propertyName).annotations().stream()
|
||||
.anyMatch(
|
||||
annotation ->
|
||||
ExposedField.class.getName().equals(getAnnotationClassName(annotation)))) {
|
||||
jMethod.annotate(ExposedField.class);
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAnnotationClassName(JAnnotationUse annotation) {
|
||||
try {
|
||||
Field clazzField = JAnnotationUse.class.getDeclaredField("clazz");
|
||||
clazzField.setAccessible(true);
|
||||
return ((JClass) clazzField.get(annotation)).fullName();
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.openmetadata.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specifies that the field or method is exposed, i.e., if the serialization will take into account
|
||||
* those fields annotated with
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
public @interface ExposedField {}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.openmetadata.annotations;
|
||||
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
|
||||
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
|
||||
|
||||
public class IgnoreMaskedFieldAnnotationIntrospector extends JacksonAnnotationIntrospector {
|
||||
|
||||
@Override
|
||||
public boolean hasIgnoreMarker(AnnotatedMember m) {
|
||||
return super.hasIgnoreMarker(m) || _findAnnotation(m, MaskedField.class) != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.openmetadata.annotations;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.sun.codemodel.JAnnotationUse;
|
||||
import com.sun.codemodel.JClass;
|
||||
import com.sun.codemodel.JDefinedClass;
|
||||
import com.sun.codemodel.JFieldVar;
|
||||
import com.sun.codemodel.JMethod;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.TreeMap;
|
||||
import org.jsonschema2pojo.AbstractAnnotator;
|
||||
|
||||
/** Add {@link MaskedField} annotation to generated Java classes */
|
||||
public class MaskedAnnotator extends AbstractAnnotator {
|
||||
|
||||
/** Add {@link MaskedField} annotation property fields */
|
||||
@Override
|
||||
public void propertyField(
|
||||
JFieldVar field, JDefinedClass clazz, String propertyName, JsonNode propertyNode) {
|
||||
super.propertyField(field, clazz, propertyName, propertyNode);
|
||||
if (propertyNode.get("mask") != null && propertyNode.get("mask").asBoolean()) {
|
||||
field.annotate(MaskedField.class);
|
||||
}
|
||||
}
|
||||
|
||||
/** Add {@link MaskedField} annotation to getter methods */
|
||||
@Override
|
||||
public void propertyGetter(JMethod getter, JDefinedClass clazz, String propertyName) {
|
||||
super.propertyGetter(getter, clazz, propertyName);
|
||||
addMaskedFieldAnnotationIfApplies(getter, propertyName);
|
||||
}
|
||||
|
||||
/** Add {@link MaskedField} annotation to setter methods */
|
||||
@Override
|
||||
public void propertySetter(JMethod setter, JDefinedClass clazz, String propertyName) {
|
||||
super.propertySetter(setter, clazz, propertyName);
|
||||
addMaskedFieldAnnotationIfApplies(setter, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use reflection methods to access the {@link JDefinedClass} of the {@link JMethod} object. If
|
||||
* the {@link JMethod} is pointing to a field annotated with {@link MaskedField} then annotates
|
||||
* the {@link JMethod} object with {@link MaskedField}
|
||||
*/
|
||||
private void addMaskedFieldAnnotationIfApplies(JMethod jMethod, String propertyName) {
|
||||
try {
|
||||
Field outerClassField = JMethod.class.getDeclaredField("outer");
|
||||
outerClassField.setAccessible(true);
|
||||
JDefinedClass outerClass = (JDefinedClass) outerClassField.get(jMethod);
|
||||
|
||||
TreeMap<String, JFieldVar> insensitiveFieldsMap =
|
||||
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
insensitiveFieldsMap.putAll(outerClass.fields());
|
||||
|
||||
if (insensitiveFieldsMap.containsKey(propertyName)
|
||||
&& insensitiveFieldsMap.get(propertyName).annotations().stream()
|
||||
.anyMatch(
|
||||
annotation ->
|
||||
MaskedField.class.getName().equals(getAnnotationClassName(annotation)))) {
|
||||
jMethod.annotate(MaskedField.class);
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAnnotationClassName(JAnnotationUse annotation) {
|
||||
try {
|
||||
Field clazzField = JAnnotationUse.class.getDeclaredField("clazz");
|
||||
clazzField.setAccessible(true);
|
||||
return ((JClass) clazzField.get(annotation)).fullName();
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.openmetadata.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specifies that the field or method is masked, i.e., if the serialization must take it into
|
||||
* account or not.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
public @interface MaskedField {}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.openmetadata.annotations;
|
||||
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
|
||||
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Custom {@link JacksonAnnotationIntrospector} to serialize/deserialize only fields containing
|
||||
* {@link ExposedField} annotation.
|
||||
*/
|
||||
public class OnlyExposedFieldAnnotationIntrospector extends JacksonAnnotationIntrospector {
|
||||
|
||||
@Override
|
||||
public boolean hasIgnoreMarker(AnnotatedMember m) {
|
||||
// only ignore fields and methods
|
||||
if (m.getAnnotated().getClass().equals(Field.class)
|
||||
|| m.getAnnotated().getClass().equals(Method.class)) {
|
||||
return (super.hasIgnoreMarker(m) || _findAnnotation(m, ExposedField.class) == null);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.openmetadata.annotations;
|
||||
|
||||
import org.jsonschema2pojo.CompositeAnnotator;
|
||||
|
||||
public class OpenMetadataAnnotator extends CompositeAnnotator {
|
||||
|
||||
public OpenMetadataAnnotator() {
|
||||
// we can add multiple annotators
|
||||
super(
|
||||
new ExposedAnnotator(),
|
||||
new MaskedAnnotator(),
|
||||
new PasswordAnnotator(),
|
||||
new DeprecatedAnnotator());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.openmetadata.annotations;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.sun.codemodel.JAnnotationUse;
|
||||
import com.sun.codemodel.JClass;
|
||||
import com.sun.codemodel.JDefinedClass;
|
||||
import com.sun.codemodel.JFieldVar;
|
||||
import com.sun.codemodel.JMethod;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.TreeMap;
|
||||
import org.jsonschema2pojo.AbstractAnnotator;
|
||||
|
||||
/** Add {@link PasswordField} annotation to generated Java classes */
|
||||
public class PasswordAnnotator extends AbstractAnnotator {
|
||||
|
||||
/** Add {@link PasswordField} annotation property fields */
|
||||
@Override
|
||||
public void propertyField(
|
||||
JFieldVar field, JDefinedClass clazz, String propertyName, JsonNode propertyNode) {
|
||||
super.propertyField(field, clazz, propertyName, propertyNode);
|
||||
if (propertyNode.get("format") != null
|
||||
&& "password".equals(propertyNode.get("format").asText())) {
|
||||
field.annotate(PasswordField.class);
|
||||
}
|
||||
}
|
||||
|
||||
/** Add {@link PasswordField} annotation to getter methods */
|
||||
@Override
|
||||
public void propertyGetter(JMethod getter, JDefinedClass clazz, String propertyName) {
|
||||
super.propertyGetter(getter, clazz, propertyName);
|
||||
addMaskedFieldAnnotationIfApplies(getter, propertyName);
|
||||
}
|
||||
|
||||
/** Add {@link PasswordField} annotation to setter methods */
|
||||
@Override
|
||||
public void propertySetter(JMethod setter, JDefinedClass clazz, String propertyName) {
|
||||
super.propertySetter(setter, clazz, propertyName);
|
||||
addMaskedFieldAnnotationIfApplies(setter, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use reflection methods to access the {@link JDefinedClass} of the {@link JMethod} object. If
|
||||
* the {@link JMethod} is pointing to a field annotated with {@link PasswordField} then annotates
|
||||
* the {@link JMethod} object with {@link PasswordField}
|
||||
*/
|
||||
private void addMaskedFieldAnnotationIfApplies(JMethod jMethod, String propertyName) {
|
||||
try {
|
||||
Field outerClassField = JMethod.class.getDeclaredField("outer");
|
||||
outerClassField.setAccessible(true);
|
||||
JDefinedClass outerClass = (JDefinedClass) outerClassField.get(jMethod);
|
||||
|
||||
TreeMap<String, JFieldVar> insensitiveFieldsMap =
|
||||
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||||
insensitiveFieldsMap.putAll(outerClass.fields());
|
||||
|
||||
if (insensitiveFieldsMap.containsKey(propertyName)
|
||||
&& insensitiveFieldsMap.get(propertyName).annotations().stream()
|
||||
.anyMatch(
|
||||
annotation ->
|
||||
PasswordField.class.getName().equals(getAnnotationClassName(annotation)))) {
|
||||
jMethod.annotate(PasswordField.class);
|
||||
}
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAnnotationClassName(JAnnotationUse annotation) {
|
||||
try {
|
||||
Field clazzField = JAnnotationUse.class.getDeclaredField("clazz");
|
||||
clazzField.setAccessible(true);
|
||||
return ((JClass) clazzField.get(annotation)).fullName();
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.openmetadata.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specifies that the field or method is exposed, i.e., if the serialization will take into account
|
||||
* those fields annotated with
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
public @interface PasswordField {}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.openmetadata.annotations.utils;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.openmetadata.annotations.ExposedField;
|
||||
|
||||
public class AnnotationChecker {
|
||||
|
||||
private AnnotationChecker() {}
|
||||
|
||||
private static boolean checkIfAnyClassFieldsHasAnnotation(
|
||||
Class<?> objectClass, Class<? extends Annotation> annotationClass) {
|
||||
return checkIfAnyClassFieldsHasAnnotation(objectClass, annotationClass, new HashSet<>());
|
||||
}
|
||||
|
||||
private static boolean checkIfAnyClassFieldsHasAnnotation(
|
||||
Class<?> objectClass,
|
||||
Class<? extends Annotation> annotationClass,
|
||||
Set<Class<?>> visitedClasses) {
|
||||
for (Field field : objectClass.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(annotationClass)) {
|
||||
return true;
|
||||
}
|
||||
if (!field.getType().isPrimitive() && !visitedClasses.contains(field.getType())) {
|
||||
visitedClasses.add(field.getType());
|
||||
if (checkIfAnyClassFieldsHasAnnotation(field.getType(), annotationClass, visitedClasses)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (objectClass.getSuperclass() != null
|
||||
&& !visitedClasses.contains(objectClass.getSuperclass())) {
|
||||
visitedClasses.add(objectClass.getSuperclass());
|
||||
return checkIfAnyClassFieldsHasAnnotation(
|
||||
objectClass.getSuperclass(), annotationClass, visitedClasses);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Check if any field of a given class has the annotation {@link ExposedField}. */
|
||||
public static boolean isExposedFieldPresent(Class<?> objectClass) {
|
||||
return checkIfAnyClassFieldsHasAnnotation(objectClass, ExposedField.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.openmetadata.common.utils;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
@Slf4j
|
||||
public final class CommonUtil {
|
||||
|
||||
private static final List<String> JAR_NAME_FILTER = List.of("openmetadata", "collate");
|
||||
|
||||
private CommonUtil() {}
|
||||
|
||||
/** Get resources from jar file or directories in the class path matching pattern */
|
||||
public static List<String> getResources(Pattern pattern) throws IOException {
|
||||
ArrayList<String> resources = new ArrayList<>();
|
||||
String classPath = System.getProperty("java.class.path", ".");
|
||||
Set<String> classPathElements =
|
||||
Arrays.stream(classPath.split(File.pathSeparator))
|
||||
.filter(jarName -> JAR_NAME_FILTER.stream().anyMatch(jarName.toLowerCase()::contains))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (String element : classPathElements) {
|
||||
File file = new File(element);
|
||||
resources.addAll(
|
||||
file.isDirectory()
|
||||
? getResourcesFromDirectory(file, pattern)
|
||||
: getResourcesFromJarFile(file, pattern));
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
/** Check if any given object falls under OM, or Collate packages */
|
||||
public static Boolean isOpenMetadataObject(Object obj) {
|
||||
return obj != null
|
||||
&& JAR_NAME_FILTER.stream()
|
||||
.anyMatch(
|
||||
Arrays.stream(obj.getClass().getPackageName().split("\\.")).toList()::contains);
|
||||
}
|
||||
|
||||
private static Collection<String> getResourcesFromJarFile(File file, Pattern pattern) {
|
||||
LOG.debug("Adding from file {}", file);
|
||||
ArrayList<String> retval = new ArrayList<>();
|
||||
try (ZipFile zf = new ZipFile(file)) {
|
||||
Enumeration<? extends ZipEntry> e = zf.entries();
|
||||
while (e.hasMoreElements()) {
|
||||
String fileName = e.nextElement().getName();
|
||||
if (pattern.matcher(fileName).matches()) {
|
||||
retval.add(fileName);
|
||||
LOG.debug("Adding file from jar {}", fileName);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// Ignored exception
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static Collection<String> getResourcesFromDirectory(File file, Pattern pattern)
|
||||
throws IOException {
|
||||
final Path root = Path.of(file.getPath());
|
||||
try (Stream<Path> paths = Files.walk(Paths.get(file.getPath()))) {
|
||||
return paths
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(path -> pattern.matcher(path.toString()).matches())
|
||||
.map(
|
||||
path -> {
|
||||
String relativePath = root.relativize(path).toString();
|
||||
LOG.debug("Adding directory file {}", relativePath);
|
||||
return relativePath;
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
/** Get date after {@code days} from the given date or before i{@code days} when it is negative */
|
||||
public static LocalDate getDateByOffset(LocalDate localDate, int days) {
|
||||
return localDate.plusDays(days);
|
||||
}
|
||||
|
||||
/** Get date after {@code days} from the given date or before i{@code days} when it is negative */
|
||||
public static LocalDate getDateByOffset(DateTimeFormatter dateFormat, String strDate, int days) {
|
||||
LocalDate localDate;
|
||||
try {
|
||||
localDate = LocalDate.parse(strDate, dateFormat);
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new IllegalArgumentException("Failed to parse date " + strDate, e);
|
||||
}
|
||||
return getDateByOffset(localDate, days);
|
||||
}
|
||||
|
||||
/** Get date after {@code days} from the given date or before i{@code days} when it is negative */
|
||||
public static String getDateStringByOffset(
|
||||
DateTimeFormatter dateFormat, String strDate, int days) {
|
||||
LocalDate localDate = getDateByOffset(dateFormat, strDate, days);
|
||||
return localDate.format(dateFormat);
|
||||
}
|
||||
|
||||
/** Check if given date is with in today - pastDays and today + futureDays */
|
||||
public static boolean dateInRange(
|
||||
DateTimeFormatter dateFormat, String date, int futureDays, int pastDays) {
|
||||
LocalDate today = LocalDate.now();
|
||||
LocalDate startDate = getDateByOffset(today, -pastDays);
|
||||
LocalDate endDate = getDateByOffset(today, futureDays);
|
||||
LocalDate givenDate;
|
||||
try {
|
||||
givenDate = LocalDate.parse(date, dateFormat);
|
||||
} catch (DateTimeParseException e) {
|
||||
throw new IllegalArgumentException("Failed to parse date " + date, e);
|
||||
}
|
||||
return (givenDate.isAfter(startDate) || givenDate.equals(startDate))
|
||||
&& (givenDate.isBefore(endDate) || givenDate.equals(endDate));
|
||||
}
|
||||
|
||||
public static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
|
||||
|
||||
/** Get SHA256 Hash-based Message Authentication Code */
|
||||
public static String calculateHMAC(String secretKey, String message) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
|
||||
SecretKeySpec secretKeySpec =
|
||||
new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), HMAC_SHA256_ALGORITHM);
|
||||
mac.init(secretKeySpec);
|
||||
byte[] hmacSha256 = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(hmacSha256);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to calculate " + HMAC_SHA256_ALGORITHM, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> listOrEmpty(List<T> list) {
|
||||
return Optional.ofNullable(list).orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> collectionOrEmpty(Map<K, V> input) {
|
||||
return Optional.ofNullable(input).orElse(new HashMap<>());
|
||||
}
|
||||
|
||||
public static <T> List<T> listOrEmptyMutable(List<T> list) {
|
||||
return nullOrEmpty(list) ? new ArrayList<>() : new ArrayList<>(list);
|
||||
}
|
||||
|
||||
public static boolean nullOrEmpty(String string) {
|
||||
return string == null || string.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean nullOrEmpty(List<?> list) {
|
||||
return list == null || list.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean nullOrEmpty(Collection<?> collection) {
|
||||
return collection == null || collection.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean nullOrEmpty(Map<?, ?> m) {
|
||||
return m == null || m.isEmpty();
|
||||
}
|
||||
|
||||
public static boolean nullOrEmpty(Object object) {
|
||||
return object == null || nullOrEmpty(object.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload for Jackson {@link com.fasterxml.jackson.databind.JsonNode}: true when the node is
|
||||
* null, a JSON {@code null}, a missing tree node, an empty text value, or an empty array /
|
||||
* object. Exists because the {@code (Object)} overload would fall through to {@code toString()},
|
||||
* and {@code NullNode.toString()} returns the 4-char string {@code "null"} — which would be
|
||||
* wrongly classified as non-empty.
|
||||
*/
|
||||
public static boolean nullOrEmpty(com.fasterxml.jackson.databind.JsonNode node) {
|
||||
if (node == null || node.isNull() || node.isMissingNode()) return true;
|
||||
if (node.isTextual()) return node.asText().isEmpty();
|
||||
if (node.isArray() || node.isObject()) return node.size() == 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<String> uuidListToStrings(List<UUID> list) {
|
||||
return list.stream().map(UUID::toString).toList();
|
||||
}
|
||||
|
||||
public static <T> T nullOrDefault(T object, T defaultValue) {
|
||||
if (object == null || (nullOrEmpty(object.toString()))) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
public static String capitalizeFirst(String str) {
|
||||
if (str == null || str.isEmpty()) return str;
|
||||
return Character.toUpperCase(str.charAt(0)) + str.substring(1);
|
||||
}
|
||||
|
||||
public static <T> List<T> collectionOrDefault(List<T> c, List<T> defaultValue) {
|
||||
if (nullOrEmpty(c)) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getResourceAsStream(ClassLoader loader, String file) throws IOException {
|
||||
return IOUtils.toString(Objects.requireNonNull(loader.getResourceAsStream(file)), UTF_8);
|
||||
}
|
||||
|
||||
/** Return list of entries that are modifiable for performing sort and other operations */
|
||||
@SafeVarargs
|
||||
public static <T> List<T> listOf(T... entries) {
|
||||
if (entries == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return new ArrayList<>(Arrays.asList(entries));
|
||||
}
|
||||
|
||||
public static URI getUri(String uri) {
|
||||
try {
|
||||
return new URI(uri);
|
||||
} catch (URISyntaxException e) {
|
||||
LOG.error("Error creating URI ", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T> boolean findChildren(List<?> list, String methodName, String fqn) {
|
||||
if (list == null || list.isEmpty()) return false;
|
||||
try {
|
||||
Method getChildren = list.get(0).getClass().getMethod(methodName);
|
||||
Method getFQN = list.get(0).getClass().getMethod("getFullyQualifiedName");
|
||||
return list.stream()
|
||||
.anyMatch(
|
||||
o -> {
|
||||
try {
|
||||
return getFQN.invoke(o).equals(fqn)
|
||||
|| findChildren((List<?>) getChildren.invoke(o), methodName, fqn);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Set<String> getChildrenNames(
|
||||
List<?> list, String methodName, String parentFqn) {
|
||||
Set<String> result = new HashSet<>();
|
||||
if (list == null || list.isEmpty()) return new HashSet();
|
||||
try {
|
||||
Method getChildren = list.get(0).getClass().getMethod(methodName);
|
||||
Method getFQN = list.get(0).getClass().getMethod("getFullyQualifiedName");
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
result.add(getFQN.invoke(list.get(i)).toString().replace(parentFqn + ".", ""));
|
||||
result.addAll(
|
||||
getChildrenNames((List<?>) getChildren.invoke(list.get(i)), methodName, parentFqn));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.openmetadata.annotations.utils;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openmetadata.annotations.ExposedField;
|
||||
|
||||
public class AnnotationCheckerTest {
|
||||
|
||||
@Test
|
||||
void testWhenClassHasExposedFields() {
|
||||
assertTrue(AnnotationChecker.isExposedFieldPresent(TestHasAnnotation.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWhenClassWithAFieldWithExposedFields() {
|
||||
assertTrue(AnnotationChecker.isExposedFieldPresent(TestNestedHasAnnotation.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWhenClassInheritFieldWithExposedFields() {
|
||||
assertTrue(AnnotationChecker.isExposedFieldPresent(TestInheritHasAnnotation.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWhenClassDoesNotHasExposedFields() {
|
||||
assertFalse(AnnotationChecker.isExposedFieldPresent(TestDoesNotHasAnnotation.class));
|
||||
}
|
||||
|
||||
static class TestHasAnnotation {
|
||||
protected @ExposedField String exposed;
|
||||
}
|
||||
|
||||
static class TestNestedHasAnnotation {
|
||||
String notExposed;
|
||||
TestHasAnnotation exposed;
|
||||
}
|
||||
|
||||
static class TestInheritHasAnnotation extends TestHasAnnotation {}
|
||||
|
||||
static class TestDoesNotHasAnnotation {
|
||||
String notExposed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2026 Collate
|
||||
* 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.
|
||||
*/
|
||||
package org.openmetadata.common.utils;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.MissingNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CommonUtilTest {
|
||||
|
||||
private static final JsonNodeFactory FACTORY = JsonNodeFactory.instance;
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_trueForNullReference() {
|
||||
assertTrue(CommonUtil.nullOrEmpty((JsonNode) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_trueForJsonNull() {
|
||||
assertTrue(CommonUtil.nullOrEmpty(FACTORY.nullNode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_trueForMissingNode() {
|
||||
assertTrue(CommonUtil.nullOrEmpty(MissingNode.getInstance()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_trueForEmptyString() {
|
||||
assertTrue(CommonUtil.nullOrEmpty(FACTORY.textNode("")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_trueForEmptyArray() {
|
||||
assertTrue(CommonUtil.nullOrEmpty(FACTORY.arrayNode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_trueForEmptyObject() {
|
||||
assertTrue(CommonUtil.nullOrEmpty(FACTORY.objectNode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_falseForNonEmptyString() {
|
||||
assertFalse(CommonUtil.nullOrEmpty(FACTORY.textNode("hello")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_falseForWhitespaceString() {
|
||||
// Mirrors the (String) overload's isEmpty() semantics; callers that need
|
||||
// blank-sensitive checks should use String::isBlank explicitly.
|
||||
assertFalse(CommonUtil.nullOrEmpty(FACTORY.textNode(" ")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_falseForNumericNode() {
|
||||
assertFalse(CommonUtil.nullOrEmpty(FACTORY.numberNode(0)));
|
||||
assertFalse(CommonUtil.nullOrEmpty(FACTORY.numberNode(42)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_falseForBooleanNode() {
|
||||
assertFalse(CommonUtil.nullOrEmpty(FACTORY.booleanNode(false)));
|
||||
assertFalse(CommonUtil.nullOrEmpty(FACTORY.booleanNode(true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_falseForNonEmptyArray() {
|
||||
assertFalse(CommonUtil.nullOrEmpty(FACTORY.arrayNode().add("x")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_falseForNonEmptyObject() {
|
||||
ObjectNode obj = FACTORY.objectNode();
|
||||
obj.put("k", "v");
|
||||
assertFalse(CommonUtil.nullOrEmpty(obj));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty_jsonNode_regressionNullNodeToStringTrap() {
|
||||
// NullNode.toString() returns "null" (4 chars) — the (Object) overload would
|
||||
// fall through to nullOrEmpty(String) and classify it as non-empty. This
|
||||
// test guards against someone accidentally deleting the JsonNode overload.
|
||||
JsonNode nullNode = FACTORY.nullNode();
|
||||
assertTrue(
|
||||
CommonUtil.nullOrEmpty(nullNode),
|
||||
"NullNode must be treated as empty — do not remove the JsonNode overload");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"instance": {
|
||||
"name": "dim_location",
|
||||
"id": "25f2c29c-bc32-4a54-a014-c0a6ded20165",
|
||||
"documentation": "this is the table to hold data on dim_location",
|
||||
"owner": {
|
||||
"id": "05612b35-e1ee-4067-83c5-830dcb5ed5b2",
|
||||
"name": "user5655",
|
||||
"ownerType": "user"
|
||||
},
|
||||
"href": "http://localhost:8585/api/v1/databases/null/tables/dim_location"
|
||||
},
|
||||
"tableType": "REGULAR",
|
||||
"fullyQualifiedName": "hive.dim_location",
|
||||
"tier": "unknown",
|
||||
"columns": [
|
||||
{
|
||||
"name": "location_id",
|
||||
"columnDataType": "NUMERIC",
|
||||
"documentation": "The ID of the Shopify POS. This column is the primary key for the locations table. The location_id is null for orders not placed through Shopify POS, such as those placed through the online store.",
|
||||
"piiTags": [
|
||||
"pii"
|
||||
],
|
||||
"columnConstraint": "NULL",
|
||||
"ordinalPosition": 1
|
||||
},
|
||||
{
|
||||
"name": "shop_id",
|
||||
"columnDataType": "NUMERIC",
|
||||
"documentation": "The ID of the store. This column is a foreign key reference to the shop_id column in the shops table.",
|
||||
"piiTags": [
|
||||
"pii"
|
||||
],
|
||||
"columnConstraint": "NULL",
|
||||
"ordinalPosition": 1
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"columnDataType": "VARCHAR",
|
||||
"documentation": "Name of the POS location as provided by the merchant. For example, 150 Elgin Street or Downtown Boutique.",
|
||||
"piiTags": [
|
||||
"pii"
|
||||
],
|
||||
"columnConstraint": "NULL",
|
||||
"ordinalPosition": 1
|
||||
}
|
||||
],
|
||||
"tableConstraints": [],
|
||||
"usage": {
|
||||
"usageCount": 46420,
|
||||
"percentileRank": 8.797669437407297
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"instance": {
|
||||
"name": "dim_location",
|
||||
"id": "25f2c29c-bc32-4a54-a014-c0a6ded20165",
|
||||
"documentation": "This is the table to hold data on dim_location.",
|
||||
"owner": {
|
||||
"id": "05612b35-e1ee-4067-83c5-830dcb5ed5b2",
|
||||
"name": "user5655",
|
||||
"ownerType": "user"
|
||||
},
|
||||
"href": "http://localhost:8585/api/v1/databases/null/tables/dim_location"
|
||||
},
|
||||
"tableType": "REGULAR",
|
||||
"fullyQualifiedName": "hive.dim_location",
|
||||
"tier": "unknown",
|
||||
"columns": [
|
||||
{
|
||||
"name": "location_id",
|
||||
"columnDataType": "NUMERIC",
|
||||
"documentation": "The ID of the Shopify POS. This column is the primary key for the locations table. The location_id is null for orders not placed through Shopify POS, such as those placed through the online store.",
|
||||
"columnConstraint": "NULL",
|
||||
"ordinalPosition": 1
|
||||
},
|
||||
{
|
||||
"name": "shop_id",
|
||||
"columnDataType": "NUMERIC",
|
||||
"documentation": "The ID of the store. This column is a foreign key reference to the shop_id column in the shops table.",
|
||||
"columnConstraint": "NULL",
|
||||
"ordinalPosition": 1
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"columnDataType": "VARCHAR",
|
||||
"documentation": "Name of the POS location as provided by the merchant. For example, 150 Elgin Street or Downtown Boutique.",
|
||||
"piiTags": [
|
||||
"pii"
|
||||
],
|
||||
"columnConstraint": "NULL",
|
||||
"ordinalPosition": 1
|
||||
},
|
||||
{
|
||||
"name": "created_on",
|
||||
"columnDataType": "VARCHAR",
|
||||
"documentation": "Date when this shop was created.",
|
||||
"piiTags": [
|
||||
"pii"
|
||||
],
|
||||
"columnConstraint": "NULL",
|
||||
"ordinalPosition": 1
|
||||
}
|
||||
],
|
||||
"tableConstraints": [],
|
||||
"usage": {
|
||||
"usageCount": 56420,
|
||||
"percentileRank": 9.79
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Test entity",
|
||||
"description": "Test entity json defintion",
|
||||
"type": "object",
|
||||
|
||||
"definitions": {
|
||||
"uri": {
|
||||
"description": "Type used for UUID",
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
|
||||
"properties" : {
|
||||
"stringProperty": {
|
||||
"description": "Locally defined property in test",
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
"uriProperty": {
|
||||
"description": "Locally defined property in test",
|
||||
"$ref": "#/definitions/uri"
|
||||
},
|
||||
|
||||
"uuidProperty" : {
|
||||
"description": "Type referenced from another json file",
|
||||
"$ref" : "../type/basic.json#/definitions/uuid"
|
||||
},
|
||||
|
||||
"objectTypeProperty" : {
|
||||
"description": "Object type referenced from another json file",
|
||||
"$ref" : "../type/objectType.json"
|
||||
}
|
||||
},
|
||||
|
||||
"required": ["stringProperty"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$id": "https://streaminlinedata.ai/type/common.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Common types",
|
||||
"description": "Common reusable types",
|
||||
"definitions": {
|
||||
"uuid": {
|
||||
"description": "Type used for UUID",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"$id": "https://streaminlinedata.ai/type/objectType.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Test object type",
|
||||
"description": "Test object type",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ot1": {
|
||||
"description": "Driver class to use for the JDBC connection",
|
||||
"type": "string"
|
||||
},
|
||||
"ot2": {
|
||||
"description": "URL to connect to JDBC server",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
||||
"required": ["ot1", "ot2"]
|
||||
}
|
||||
Reference in New Issue
Block a user