Activate profile while submitting Apache storm topology of a maven project (Without recompiling) - maven

I have to activate the profile while submitting the jar to storm like we do for a spring/boot project. like below
java -jar project-xyz.jar --spring.profiles.active=dev.
It is a maven project with multiple sub modules. we have the resources structured
Anything that is common to all the profiles will go in the root directory of resources, and anything that is specific to a profile (like DB connections) will go in that particular profile directory.
The root directory file will have the place holders for profile specific properties and will be replaced by the actual properties defined in profile directory.
Ex: ${mysql.host} will be resolved to localhost when local profile is active.
This final file will be placed in the classpath when we build the jar using
mvn clean install -P{profile}
And then the topology is submitted to storm like the following.
storm jar project-xyz.jar path.to.class.containing.main.method
The final properties file generated by maven will be read by a property reader, stored and served whenever requested.
private static final Map<String, Properties> properties = new HashMap<>();
public static String getProperty( String file, String key )
{
try {
if ( !properties.containsKey( file ) ) {
synchronized ( properties ) {
if ( !properties.containsKey( file ) ) {
loadProps( file );
}
}
}
return properties.get( file ).getProperty( key );
} catch ( IOException e ) {
throw new PropertyException( "There was a problem getting " + key + " from " + file + ".properties", e );
}
}
private static void loadProps( String file ) throws IOException
{
InputStream inputStream = PropertyFileHandler.class.getClassLoader().getResourceAsStream( file + ".properties" );
if ( inputStream == null ) {
throw new FileNotFoundException( "Property file " + file + ".properties not found" );
}
Properties prop = new Properties();
prop.load( inputStream );
properties.put( file, prop );
}
I've already gone through this question and its different in a way that I use maven and I have to activate the profile instead of providing specific property.
So, is there a way I can activate the profile while submitting the jar to storm somewhat like the following?
storm jar project-xyz.jar --profiles.active=dev
If not, what can I do to achieve this without re-compiling the source? Does converting it to a spring boot project help?

You won't be able to do this as long as you're using Maven to generate a jar containing a single properties file. That jar can only be used in the environment you generated it for.
Instead, you could include all the possible properties files in the jar, and decide which one to read based on a system or environment property.
A better option is probably that you generate the property files separately to the jar, and then put the jar plus property file into Storm. You can pass the property file path to your Storm submission code as a command line argument to your main method. You would then read the properties into memory, and pass them to your bolts/spouts. You need to do this loading eagerly (i.e. read the entire properties at topology submission time, rather than when you need a specific property), so your lazy loading configuration code should be discarded.
You might want something like Flux for convenience:
To enable property filtering, use the --filter command line option and
specify a .properties file. For example, if you invoked flux like so:
storm jar myTopology-0.1.0-SNAPSHOT.jar org.apache.storm.flux.Flux
--local my_config.yaml --filter dev.properties
With the following dev.properties file:
kafka.zookeeper.hosts: localhost:2181
You would then be able to reference those properties by key in your
.yaml file using ${} syntax:
- id: "zkHosts"
className: "org.apache.storm.kafka.ZkHosts"
constructorArgs:
- "${kafka.zookeeper.hosts}"
See the documentation at https://storm.apache.org/releases/2.1.0/flux.html.

Related

With the spring boot Gradle plugin, is there a task for writing the manifest file without packaging it

I am writing a plug-in using legacy code (on which I don't have any writing rights) for a spring-boot project, and I need to have access to the manifest file in his final form, as produced by the bootJar task. Sadly, this file seems too be directly written to the jar file, without any intermediate file.
Do you know a way to generate the Manifest file in its final form without unzipping the final jar ?
Not sure it helps, and couldn't write it as a comment. I had the same challenge with git, to log the commit id.
build.gradle:
plugins {id 'com.gorylenko.gradle-git-properties'}
gitProperties
project structure:
build
-classes
-generated
-libs
--x.jar
---com
---META-INF
----MANIFEST.MF
---git.properties
src
...
java:
Properties props = new Properties();
InputStream is = getClass().getClassLoader().getResourceAsStream("git.properties");
props.load(is);
log.info("git props: " + props);
so maybe you can try ..getResourceAsStream("META-INF/MANIFEST.MF");
Now I understood you need the access not from the app, but from the plug-in, but still..
Please let us know if you solved the challenge.

in Quarkus, can I merge files that have the same name in many dependencies, ie typesafe config files?

I have a file called reference.conf that exists in many dependencies and derives from typesafe config (https://github.com/lightbend/config)
I use Quarkus for my application.
When Quarkus builds the uber-jar it keeps only one of these files (the one from the last dependency that it parses).
How can I merge all this files to a single one?
Thanks to this commit in Quarkus https://github.com/quarkusio/quarkus/commit/b3d3788ae92542d5fb39d89488890e16d64cec90 "Introduce UberJarMergedResourceBuildItem and UberJarIgnoredResourceBuildItem",
that works from release https://github.com/quarkusio/quarkus/releases/tag/1.13.4.Final we can create an extension in Quarkus and use it to merge any resource we want to.
Many thanks to George Gastaldi for this.
To create the extension is quite easy, as I did also for first time for this feature.
c:\projects> mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create-extension -N -DgroupId=myproject.quarkus -DextensionId=quarkus-files-extension -DwithoutTests
Done, the extension has been created with all the needed maven projects, configuration etc. Now, edit the file of the extension
c:\projects\quarkus-files-extension\deployment\src\main\java\myproject\quarkus\files\extension\deployment\QuarkusFilesExtensionProcessor.java
add the BuildStep: UberJarMergedResourceBuildItem
class QuarkusFilesExtensionProcessor {
private static final String path = "reference.conf";
#BuildStep
UberJarMergedResourceBuildItem feature() {
return new UberJarMergedResourceBuildItem(path);
}
}
Build the Quarkus extension maven project, ie
c:\projects\quarkus-files-extension> mvn clean install
Now add the extension to your project that needs the merging by either executing
c:\project\mybigproject> mvn quarkus:add-extension -Dextensions="myproject.quarkus:quarkus-files-extension"
It simply adds the dependency of the extension in your pom.xml
That's it, next time that you build the uber-jar in the project, it merges all the reference.conf files to a single one.
The only thing missing is a verbose message, which is not possible with the current version,
You can notice that it works, by either of course checking the reference.conf in the uber-jar
or the following message is now missing from the build log
[WARNING] [io.quarkus.deployment.pkg.steps.JarResultBuildStep] Dependencies with duplicate files detected. The dependencies [groupId1:artifact1::jar:1.0(compile), groupId2:artifact2::jar:1.0(compile)] contain duplicate files, e.g. reference.conf
They also offer a feature to ignore files with a certain name, with the BuildItem UberJarIgnoredResourceBuildItem. You create an extension exactly the same way.
The solution proposed by Andreas is fine for files that can be appended sequentially, however that's not true for XML or JSON files, for example.
For these cases, it's preferable to perform the merge in a #BuildStep method in an extension and produce a GeneratedResourceBuildItem instead.
Here is an example using XmlCombiner:
#BuildStep
void uberJarMergedResourceBuildItem(BuildProducer<GeneratedResourceBuildItem> generatedResourcesProducer,
PackageConfig packageConfig) {
if (packageConfig.isUberJar()) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
XmlCombiner combiner = new XmlCombiner();
List<URL> resources = Collections
.list(getClass().getClassLoader().getResources("META-INF/wsdl.plugin.xml"));
for (URL resource : resources) {
try (InputStream is = resource.openStream()) {
combiner.combine(is);
}
}
combiner.buildDocument(baos);
} catch (ParserConfigurationException | SAXException | TransformerException | IOException e) {
e.printStackTrace();
}
generatedResourcesProducer.produce(new GeneratedResourceBuildItem("META-INF/wsdl.plugin.xml", baos.toByteArray()));
}
}
See a test in https://github.com/quarkusio/quarkus/pull/17199

Using JavaCompiler API in a Spring Boot app - unable to properly set the compilation classpath

We have an app that's been migrated from a traditional WAR Spring web application to what we had hoped would be a modern Spring Boot executable Jar.
One of the app modules uses the JavaCompiler API to generate Java code and compile it in runtime.
The generated code requires dependencies that reside in the web app classpath, and so we had roughly the following code:
StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(null, null, null);
List<String> optionList = new ArrayList<String>();
// set compiler's classpath to be same as the runtime's
String classpathString = System.getProperty("java.class.path");
optionList.addAll(Arrays.asList("-nowarn",
"-classpath",
classpathString,
"-g",
"-source",
javaVersion,
"-target",
javaVersion));
Collection classpathFiles = getClasspathJars(classpathString);
addPreviousCompiledClassesToClasspathFiles(rootClassPath, classpathFiles);
try {
File file = new File(getTargetRoot(tempClassPath));
file.mkdirs();
standardFileManager.setLocation(StandardLocation.CLASS_OUTPUT, Lists.newArrayList(file));
standardFileManager.setLocation(StandardLocation.CLASS_PATH, classpathFiles);
// adding current dir to the source path, to find the compiled DV
if (generateSeparateJarsForEachDecision.equals(NO_JAR)) {
standardFileManager.setLocation(StandardLocation.SOURCE_PATH, Lists.newArrayList(file));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
List<CharSequenceJavaFileObject> compilationUnits = Lists
.newArrayList(new CharSequenceJavaFileObject( StringUtils.capitalize(filename),
toString(javaCode)));
JavaCompiler.CompilationTask task = compiler
.getTask(writer, standardFileManager, listener, optionList, null, compilationUnits);
status = task.call();
```
However, that doesn't work with Boot.
The classpath only contains my-app.jar, and I can't add any of the nested jars to the -cp of the task - it just won't those nested jars.
I've also attempted to manually add the to the -cp parameter them like so: {absolute-path}/my-app.jar!/BOOT-INF/lib/my-dep.jar
{absolute-path}/my-app.jar!/BOOT-INF/lib/*.jar
None of those worked.
Thought about using the <requiresUnpack> tag on build too, but that didn't seem to help because I couldn't get a hold of the expanded dir in order to add it to the classpath.
Faced similar issue.
Looks like a limitation with spring boot.
So created a standalone application with Jersey and Tomcat embedded.
Now jar contains all libraries and able to set it as class path to Java Compiler

Setting gradle system property in .gradle file

I have a "general.gradle" file that sets the common properties for all of my projects.
This file is committed to git repository and shared among many users.
I would like to add a system property to is so it will be common to all the users
such options like systemProp.http.nonProxyHosts
is there a way?
You could make another file, like general.properties, add your system properties there prefixed by systemProp and then in general.gradle load the properties from that file, like so:
FileInputStream fileInputStream = new FileInputStream(new File('{YOUR_PATH}/general.properties'))
Properties properties = new Properties()
properties.load(fileInputStream)
fileInputStream.close()
properties.stringPropertyNames().forEach({key -> ext.set(key, properties.getProperty(key))})
and then load it to your root build.gradle file in projects, like so:
apply from: '{YOUR_PATH}/general.gradle'
You can retrieve it from the ext property. Following this example, if you put general.properties in your project and add there,for example: spring=dev. Then you put the property loading code in general.gradle and apply it in your build.gradle, if you add a task like this in your build.gradle:
task testProp << {
String profile = getProperty('spring')
System.setProperty('Spring.profiles.active', profile)
String prop = System.getProperty('Spring.profiles.active');
println prop
}
then the task execution should print out dev.

Loading a file as a string content in a Spring service contained in a JAR built with Maven

I am using Spring with Maven and I would like to load a file contained in the maven folder src/main/resources inside a spring service annotated with #Service contained in a JAR file (used by another WAR file as a Maven dependency). This does not seem to work for many different reasons (read: for many different exceptions according to many different attempts).
I know that Maven, when building, puts the stuff in src/main/resources at the root of the JAR file. I've opened the JAR file and I've verified that.
I would prefer a File object (something I can use with utility libraries like Apache Commons IO), but also "something" else that's working is fine.
I would prefere going for stuff within the Java class annotated with #Service (i.e. no application context XML file, with stuff inside that I recall within the service etc.)
The file contains stuff that I eventually use as a List<String> where every item is a line in the original file. I need to keep this file as it is, I can not use a database or another solution different than a file.
This is not a .properties file and is not intended to be.
The following are the not working attempts (throwing NullPointerException, FileNotFoundException, IllegalArgumentException: URI is not hierarchical and so on...).
Among the attempts I have tried also adding/removing the prefix src/main/resources while trying to figure out what was wrong, but then I figured out that Maven puts all the stuff at the root of the JAR file as stated before.
#Value("classpath:src/main/resources/myFile.txt")
private Resource myFileResource;
private File myFile =
new File(this.getClass().getResource("/myFile.txt").toURI());
private File myFile =
new FileSystemResource("src/main/resources/myFile.txt").getFile();
private BufferedReader bufferedReaderMyFile =
new BufferedReader(
new InputStreamReader(getClass().getResourceAsStream("myFile.txt")));
What's wrong with what I am doing?
File inside jar file is no longer a File, so treat it as Resource and read it via Stream
InputStream inputStream = this.getClass().getResourceAsStream("/myFile.txt");
assumming you have actually packed that myFile.txt inside jar file and it is available at the root of JAR file

Resources