What are the strategies to embed a unique version number in a Spring application?
I've got an app using Spring Boot and Spring Web.
Its matured enough that I want to version it and see it displayed on screen at run time.
I believe what you are looking for is generating this version number during build time (Usually by build tools like Ant, Maven or Gradle) as part of their build task chain.
I believe a quite common approach is to either put the version number into the Manifest.mf of the produced JAR and then read it, or create a file that is part of the produced JAR that can be read by your application.
Another solution would be just using Spring Boot's banner customization options described here: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html#boot-features-banner
However, this will only allow you to change spring-boot banner.
I also believe that Spring Boot exposes product version that is set in Manifest.MF of your application. To achieve this you will need to make sure Implementation-Version attribute of the manifest is set.
Custom solution for access anywhere in the code
Lets assume you would like to have a version.properties file in your src/main/resources that contains your version information. It will contain placeholders instead of actual values so that these placeholders can be expanded during build time.
version=${prodVersion}
build=${prodBuild}
timestamp=${buildTimestamp}
Now that you have a file like this you need to fill it with actual data. I use Gradle so there I would make sure that processResources task which is automatically running for builds is expanding resources. Something like this should do the trick in the build.gradle file for Git-based code:
import org.codehaus.groovy.runtime.*
import org.eclipse.jgit.api.*
def getGitBranchCommit() {
try {
def git = Git.open(project.file(project.getRootProject().getProjectDir()));
def repo = git.getRepository();
def id = repo.resolve(repo.getFullBranch());
return id.abbreviate(7).name()
} catch (IOException ex) {
return "UNKNOWN"
}
}
processResources {
filesMatching("**/version.properties") {
expand (
"prodVersion": version,
"prodBuild": getGitBranchCommit(),
"buildTimestamp": DateGroovyMethods.format(new Date(), 'yyyy-MM-dd HH:mm')
)
}
}
processResources.outputs.upToDateWhen{ false }
In the code about the following is happening:
We defined a function that can take a build number out of the VCS
(in this case Git). The commit hash is limited to 7 characters.
We configure the processResources task to process
version.properties file and fill it with our variables.
prodVersion is taken from Gradle project version. It's usually set
as version in gradle.properties file (part of the general build
setup).
As a last step we ensure that it's always updated (Gradle
has some mechanics to detect if files ened to be processed
Considering you are on SVN, you will need to have a getSvnBranchCommit() method instead. You could for instance use SVNKit or similar for this.
The last thing that is missing now is reading of the file for use in your application.
This could be achieved by simply reading a classpath resource and parsing it into java.util.Properties. You could take it one step further and for instance create accessor methods specifically for each field, e.g getVersion(), getBuild(), etc.
Hope this helps a bit (even though may not be 100% applicable straight off)
Maven can be used to track the version number, e.g.:
<!-- pom.xml -->
<version>2.0.3</version>
Spring Boot can refer to the version, and expose it via REST using Actuator:
# application.properties
endpoints.info.enabled=true
info.app.version=#project.version#
Then use Ajax to render the version in the browser, for example using Polymer iron-ajax:
<!-- about-page.html -->
<iron-ajax auto url="/info" last-response="{{info}}"></iron-ajax>
Application version is: [[info.app.version]]
This will then show in the browser as:
Application version is: 2.0.3
I'm sure you've probably figured something out since this is an older question, but here's what I just did and it looks good. (Getting it into the banner requires you to duplicate a lot).
I'd recommend switching to git (it's a great SVN client too), and then using this in your build.gradle:
// https://github.com/n0mer/gradle-git-properties
plugins {
id "com.gorylenko.gradle-git-properties" version "1.4.17"
}
// http://docs.spring.io/spring-boot/docs/current/reference/html/deployment-install.html
springBoot {
buildInfo() // create META-INF/build-info.properties
}
bootRun.dependsOn = [assemble]
And this in your SpringBoot application:
#Resource
GitProperties props;
#Resource
BuildProperties props2;
Or this way to expose those properties into the standard spring environment:
#SpringBootApplication
#PropertySources({
#PropertySource("classpath:git.properties"),
#PropertySource("classpath:META-INF/build-info.properties")
})
public class MySpringBootApplication {
and then referencing the individual properties as needed.
#Value("${git.branch}")
String gitBranch;
#Value("${build.time}")
String buildTime;
Related
I would like to be able to create a custom override to add a VcpkgConfiguration Property based on our current configuration.
We have a C++ project that uses Premake and vcpkg. We have found vcpkg to conflict with other projects that include their own versions of similar libraries, so we cannot use the global integration that it provides. Instead we have added it as a sub-module to our project and linked it through premake with a custom override:
p.override(p.vstudio.vc2010, "importExtensionTargets", function(base, prj)
p.push('<ImportGroup Label="ExtensionTargets">')
p.callArray(p.vstudio.vc2010.elements.importExtensionTargets, prj)
p.pop('</ImportGroup>')
p.push('<ImportGroup Label="ExtensionTargets">')
p.w('<Import Project="$(SolutionDir)External/vcpkg/scripts/buildsystems/msbuild/vcpkg.targets"/>')
p.pop('</ImportGroup>')
end)
Unfortunately we do not use the regular "Debug" or "Release" configurations in our project, so vcpkg by default does not link correctly. To get past that problem, we modified the vcpkg.targets file to recognize our configuration in a local branch. This is not ideal, as it forces us to rebase our branch off vcpkg in order to update it, and could potentially conflict if that file is ever modified in their repo.
The targets file allows you to set the VcpkgConfiguration property before including the target, which is what we would like to do.
Basically what we would like is to be able to call a command through the filters like this:
filter {"configurations:<SomeConfiguration>"}
VcpkgConfig "Debug"
Which would add this inside the propertygroup
<VcpkgConfiguration>Debug</VcpkgConfiguration>
How can we accomplish this?
The problem seems to be that importExtensionTargets is per project but you want this per configuration.
You can try to register your key word
api.register {
name= "VcpkgConfig",
scope = "config",
kind = "string",
}
then in your custom function
-- loop over all configurations
for _, cfgName in ipairs(prj.configurations) do
-- find config
local cfg = project.findClosestMatch(prj, cfgName)
if cfg.VcpkgConfig then
p.push('<ImportGroup Label="ExtensionTargets">')
p.push('<VcpkgConfiguration>'.. cfg.VcpkgConfig .. '</VcpkgConfiguration>')
p.w('<Import Project="$(SolutionDir)External/vcpkg/scripts/buildsystems/msbuild/vcpkg.targets"/>')
p.pop('</ImportGroup>')
end
Not tested.
Would this work ?
You know, I spend more time just trying to get things set up to work with Angular than I do actually developing with Angular. There must be an easier way... :(
Currently, I am using the aspnetcore-spa template, creating a project with the command "dotnet new angular" - this is version 1.0.3, which adds Angular 4.1.2 to the npm dependencies. This works great to get a project running quickly. But now I want to add PrimeNG to take advantage of their form controls. I have been struggling with this all day, and would love it if anyone could provide some assistance.
Here is what I have done in my current effort (the latest of many, starting fresh each time):
1) Added to the package.json file: "primeng": "4.1.0-rc.2"
2) Added 'primeng/primeng' to the webpack.config.vendor.js file's vendor collection.
3) Added the following to my test module (which is in turn referenced in app.module.shared.ts so I can route to it via my RouterModule):
import { FileUploadModule } from 'primeng/components/fileupload/fileupload';
And in the html for the module, in an attempt to use the file uploader control, I have (from their site - https://www.primefaces.org/primeng/#/fileupload):
<p-fileUpload name="myfile[]" url="./upload.php"></p-fileUpload>
4) ran "webpack --config webpack.config.vendor.js" from a command prompt at the root of the project folder, which completed with no errors.
Then I hit F5 to run the project, and I got this error:
Exception: Call to Node module failed with error: Error: Template parse errors:
'p-fileUpload' is not a known element:
1. If 'p-fileUpload' is an Angular component, then verify that it is part of this module.
2. If 'p-fileUpload' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '#NgModule.schemas' of this component to suppress this message. (" type="button" (click)="onclick()" class="ui-button-info" label="Click Me">Click Me</button>-->
So, in an effort to comply, I added a reference to the ngprime module to the app.module.shared.ts file, like this (I don't really know how I should reference the module...):
import { FileUploadModule } from 'primeng/primeng';
But got the same exact error.
What am I missing???
Any help would be most appreciated.
I finally have this working, using the asp-prerender-module to get server-side rendering, and not having to rely on the asp-ng2-prerender-module (see my last comment). The trick, I found, was to reference the FileUploaderModule in the app.module.shared.ts file like this:
import { FileUploadModule } from 'primeng/components/fileupload/fileupload';
rather than like this:
import { FileUploadModule } from 'primeng/primeng';
The reason this matters is that the latter method of referencing will load all other components as well (see explanation here: https://www.primefaces.org/primeng/#/setup), and SOME of the PrimeNG components can not be rendered on the server due to DOM-related references (things like "window", which do not exist on the server). See the discussion here for more on this: https://github.com/primefaces/primeng/issues/1341
This change, combined with the other steps listed in my answer and, of course, actually referencing the directive in app.module (thank you #pankaj !) made everything work correctly at last. Only took me about 7 hours to figure it out. :(
If you upgrade from version 1 to version 2 of BundleTransformer you may get this message:
Could not find a factory, that creates an instance of the JavaScript
engine with name MsieJsEngine.
Like me, you may not even have realized you've upgraded more than just a point release.
How to fix?
Version 2 DOES NOT USE WEB.CONFIG for configuration anymore
So start by removing it and read the rest of this link
https://github.com/Taritsyn/JavaScriptEngineSwitcher/wiki/How-to-upgrade-applications-to-version-2.X
Basically you will be doing the following:
Removing existing web.config nodes for javscript engine
Adding to someplace like global.asax some initialization code
Install Nuget packages for the engines you want to use
Make sure to add a using statement to be able to use extension methods (if you choose that way)
I ended up with something like this:
using JavaScriptEngineSwitcher.Core;
using JavaScriptEngineSwitcher.Msie;
using JavaScriptEngineSwitcher.V8;
....
public class JsEngineSwitcherConfig
{
public static void Configure(JsEngineSwitcher engineSwitcher)
{
engineSwitcher.EngineFactories
.AddMsie(new MsieSettings
{
UseEcmaScript5Polyfill = true,
UseJson2Library = true
})
.AddV8();
engineSwitcher.DefaultEngineName = MsieJsEngine.EngineName;
}
}
I'm following the instructions, but my code is now breaking on BundleConfig
var cssTransformer = new StyleTransformer();
In the name attribute of /configuration/bundleTransformer/less/jsEngine configuration element not specified a name of JavaScript engine.
If you have not installed JavaScript engine, then for correct working
of this module is recommended to install one of the following NuGet
packages: * JavaScriptEngineSwitcher.Msie *
JavaScriptEngineSwitcher.V8 * JavaScriptEngineSwitcher.ChakraCore
After package is installed, need set a name of JavaScript engine (for
example, MsieJsEngine) to the name attribute of
/configuration/bundleTransformer/less/jsEngine configuration
element.
I want to load dynamically XML meta-data files that are not in the classpath (the XML meta-data files will be generated after launching my app).
I thought I could call the method
MetaDataManager.registerFile(java.lang.String fileURLString, FileMetaData filemd, ClassLoaderResolver clr)
Then, I tried the API enhancement, so I added the following lines:
JDOEnhancer enhancer = JDOHelper.getEnhancer();
enhancer.setVerbose(true);
enhancer.addClasses(ClassToPersist.class.getName()).enhance();
getClass().getClassLoader().loadClass(ClassToPersist.class.getName());
The following jars are in the classpath: datanucleus-api-jdo.jar, datanucleus-connectionpool.jar, datanucleus-core.jar datanucleus-rdbms.jar, jdo-api.jar, asm.jar.
But when I launch my app, I get this exception:
Caused by: mypackage.MyException:
org.datanucleus.api.jdo.exceptions.ClassNotPersistenceCapableException: The class "mypackage.ClassToPersist" is not persistable. This means that it either hasnt been enhanced, or that the enhanced version of the file is not in the CLASSPATH (or is hidden by an unenhanced version), or the Meta-Data/annotations for the class are not found.
Do you please have any idea how to fix this ?
PS: I also noticed that the method enhance returns 0 which indicates that the class has not been enhanced (we could exclude the other options)
Thanks
I think I found an easy way to fix this.
At runtime, I created a jar that contains the updated metadata file as META-INF/package.jdo. Then I added this jar to the classpath as described here
Using this hacks, I didn't need to re-enhace my class since it is enhanced after compilation.
(But for information, I had to call JDOEnhancer.addFiles() to enhance my class.)
So your call to loadClass has loaded the unenhanced class (since it would have been loaded in order to perform the enhancement presumably), and you didn't follow the tutorial that is provided at http://www.datanucleus.org/documentation/development/dynamic_class_metadata_enhance_runtime.html
I have a few gradle war tasks in my build file, and I would like to change the webAppDirName per war task. I tried this:
task myWarTask(type: War) {
ext.webAppDirName = 'src/anotherfolder/webapp' // also tried just webAppDirName
version ""
destinationDir = file("$buildDir/libs")
baseName = 'myWarName'
classpath = configurations.myWarConfiguration
}
But this is still pulling in the contents of src/main/webapp instead of src/anotherfolder/webapp
Can I configure the webAppDirName on a per war file basis like this?
There is just one webAppDirName property per project, and the War plugin automatically adds a corresponding from to each War task. So the main problem is how to undo that from. I think the following should work:
apply plugin: "war"
webAppDirName = "non/existing/dir"
task myWarTask(type: War) {
from "src/anotherfolder/webapp"
...
}
An alternative is to only use the War task type, but not the War plugin. You'll have to configure a few more task properties then, and will lose a few features, mostly related to provided configurations and publishing of the War. Of course you can make up for this with explicit configuration (if necessary). If you are interested in the details, have a look at the source code for the War plugin.
PS: webAppDirName is not an extra property (ext.), but a convention property added by the War plugin. Extra properties are only meant for ad-hoc use in build scripts. You'd use ext. when writing an extra property, but omit it when reading the property.
task myWarTask(type: War) {
from 'src/anotherfolder/webapp'
version ""
destinationDir = file("$buildDir/libs")
baseName = 'myWarName'
classpath = configurations.myWarConfiguration
}