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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:35:45 +08:00
commit bf2343b7e4
16049 changed files with 3531137 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
config.stopBubbling = true
lombok.log.fieldName = LOG
+158
View File
@@ -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 {}
@@ -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 {}
@@ -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"]
}