Skip to content

Commit

Permalink
A first stab at issue apache#3454
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Casters committed Jan 7, 2025
1 parent 1cbb1af commit 2d59f4e
Show file tree
Hide file tree
Showing 27 changed files with 1,094 additions and 50 deletions.
7 changes: 7 additions & 0 deletions assemblies/plugins/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,13 @@
<version>${project.version}</version>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.apache.hop</groupId>
<artifactId>hop-resolvers-pipeline</artifactId>
<version>2.12.0-SNAPSHOT</version>
<type>zip</type>
</dependency>

<dependency>
<groupId>org.apache.hop</groupId>
<artifactId>hop-tech-avro</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.apache.hop.core.plugins.PluginRegistry;
import org.apache.hop.core.row.value.ValueMetaPluginType;
import org.apache.hop.core.util.EnvUtil;
import org.apache.hop.core.variables.resolver.VariableResolverPluginType;
import org.apache.hop.core.vfs.HopVfs;
import org.apache.hop.core.vfs.plugin.VfsPluginType;

Expand Down Expand Up @@ -80,6 +81,7 @@ public static synchronized void init() throws HopException {
DatabasePluginType.getInstance(),
ExtensionPointPluginType.getInstance(),
TwoWayPasswordEncoderPluginType.getInstance(),
VariableResolverPluginType.getInstance(),
VfsPluginType.getInstance()));
}

Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/org/apache/hop/core/util/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public class StringUtil {

public static final String FIELD_CLOSE = "}";

public static final String RESOLVER_OPEN = "#{";

public static final String RESOLVER_CLOSE = "}";

public static final String CRLF = "\r\n";

public static final String INDENTCHARS = " ";
Expand Down
82 changes: 80 additions & 2 deletions core/src/main/java/org/apache/hop/core/variables/Variables.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@
import java.util.Set;
import org.apache.hop.core.Const;
import org.apache.hop.core.config.HopConfig;
import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.exception.HopValueException;
import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.row.value.ValueMetaBase;
import org.apache.hop.core.util.StringUtil;
import org.apache.hop.core.util.Utils;
import org.apache.hop.core.variables.resolver.VariableResolver;
import org.apache.hop.metadata.api.IHopMetadataSerializer;
import org.apache.hop.metadata.serializer.multi.MultiMetadataProvider;
import org.apache.hop.metadata.util.HopMetadataInstance;

/** This class is an implementation of IVariables */
public class Variables implements IVariables {
Expand Down Expand Up @@ -139,11 +144,84 @@ public synchronized void setVariable(String variableName, String variableValue)

@Override
public synchronized String resolve(String aString) {
if (aString == null || aString.length() == 0) {
if (aString == null || aString.isEmpty()) {
return aString;
}

return StringUtil.environmentSubstitute(aString, properties);
String resolved = StringUtil.environmentSubstitute(aString, properties);

resolved = substituteVariableResolvers(resolved);

return resolved;
}

private String substituteVariableResolvers(String input) {
String resolved = input;
int startIndex = 0;
while (startIndex < resolved.length()) {
int resolverIndex = resolved.indexOf(StringUtil.RESOLVER_OPEN);
if (resolverIndex < 0) {
// There's nothing more to do here.
//
return resolved;
}

// Is there a close token?
//
int closeIndex = resolved.indexOf(StringUtil.RESOLVER_CLOSE, resolverIndex);
if (closeIndex < 0) {
// False positive on the open token
//
return resolved;
}

// The variable resolver String [resolverIndex, closeIndex$]
// is in the format: #{name:argument}
//
int colonIndex = resolved.indexOf(':', resolverIndex);
String name;
String argument;
if (colonIndex >= 0) {
name = resolved.substring(resolverIndex + StringUtil.RESOLVER_OPEN.length(), colonIndex);
argument = resolved.substring(colonIndex + 1, closeIndex);
} else {
name = resolved.substring(resolverIndex + StringUtil.RESOLVER_OPEN.length(), closeIndex);
argument = "";
}

try {
MultiMetadataProvider provider = HopMetadataInstance.getMetadataProvider();
IHopMetadataSerializer<VariableResolver> serializer =
provider.getSerializer(VariableResolver.class);

// This next loadA() method is relatively slow since it needs to go to disk.
//
VariableResolver resolver = serializer.load(name);
if (resolver == null) {
// return resolved;
//
throw new HopException(
"Variable Resolver '" + name + "' could not be found in the metadata");
}
String resolvedArgument = resolver.getIResolver().resolve(argument, this);

// We take the first part of the original resolved string, the resolved argument, and the
// last part
// to continue resolving all expressions in the string.
//
String before = resolved.substring(0, resolverIndex);
String after = resolved.substring(closeIndex + 1);
resolved = before + resolvedArgument + after;

// Where do we continue?
startIndex = before.length() + resolvedArgument.length();

} catch (HopException e) {
throw new RuntimeException(
"Error resolving variable '" + input + "' with variable resolver metadata", e);
}
}
return resolved;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.hop.core.variables.resolver;

import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.metadata.api.HopMetadataObject;

/** Indicates that the class can resolve variables in a specific manner. */
@HopMetadataObject(objectFactory = VariableResolverObjectFactory.class)
public interface IVariableResolver {
void init();

/**
* Resolve the variables in the given String.
*
* @param string The String in which we want to replace variables.
* @param variables The variables and values to use as a reference
* @return The input string with expressions resolved.
* @throws HopException In case something goes wrong.
*/
String resolve(String string, IVariables variables) throws HopException;

void setPluginId();

String getPluginId();

void setPluginName(String pluginName);

String getPluginName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.hop.core.variables.resolver;

import lombok.Getter;
import lombok.Setter;
import org.apache.hop.metadata.api.HopMetadata;
import org.apache.hop.metadata.api.HopMetadataBase;
import org.apache.hop.metadata.api.HopMetadataProperty;
import org.apache.hop.metadata.api.HopMetadataPropertyType;
import org.apache.hop.metadata.api.IHopMetadata;

@HopMetadata(
key = "variable-resolver",
name = "i18n::VariableResolver.name",
description = "i18n::VariableResolver.Description",
image = "ui/images/variable.svg",
documentationUrl = "/metadata-types/variable-resolver.html",
hopMetadataPropertyType = HopMetadataPropertyType.RDBMS_CONNECTION)
@Getter
@Setter
public class VariableResolver extends HopMetadataBase implements IHopMetadata {
public static final String GUI_PLUGIN_ELEMENT_PARENT_ID =
"VariableResolver-PluginSpecific-Options";

@HopMetadataProperty private String description;

@HopMetadataProperty(key = "variable-resolver")
private IVariableResolver iResolver;

public String getPluginName() {
if (iResolver == null) {
return null;
}
return iResolver.getPluginName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.hop.core.variables.resolver;

import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.plugins.IPlugin;
import org.apache.hop.core.plugins.PluginRegistry;
import org.apache.hop.metadata.api.IHopMetadataObjectFactory;

public class VariableResolverObjectFactory implements IHopMetadataObjectFactory {

@Override
public Object createObject(String id, Object parentObject) throws HopException {
PluginRegistry registry = PluginRegistry.getInstance();
IPlugin plugin = registry.findPluginWithId(VariableResolverPluginType.class, id);
return registry.loadClass(plugin);
}

@Override
public String getObjectId(Object object) throws HopException {
if (!(object instanceof IVariableResolver)) {
throw new HopException(
"Object is not of class IVariableResolver but of " + object.getClass().getName() + "'");
}
return ((IVariableResolver) object).getPluginId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.hop.core.variables.resolver;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** This annotation indicates that the given plugin is a variable resolver plugin. */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface VariableResolverPlugin {
/**
* @return The ID of the password encoder plugin. You can specify more than one ID in a comma
* separated format: id1,id2,id3 for deprecation purposes.
*/
String id();

String name();

String description() default "";

/**
* @return True if a separate class loader is needed every time this class is instantiated
*/
boolean isSeparateClassLoaderNeeded() default false;

String documentationUrl() default "";

String casesUrl() default "";

String forumUrl() default "";

String classLoaderGroup() default "";
}
Loading

0 comments on commit 2d59f4e

Please sign in to comment.