I wanted to retrieve values from my context.xml, and I've found this snippet of code to do so:
// Acquire an instance of our specified bean class
MyBean bean = new MyBean();
// Customize the bean properties from our attributes
Reference ref = (Reference) obj;
Enumeration addrs = ref.getAll();
while (addrs.hasMoreElements()) {
RefAddr addr = (RefAddr) addrs.nextElement();
String name = addr.getType();
String value = (String) addr.getContent();
if (name.equals("foo")) {
bean.setFoo(value);
} else if (name.equals("bar")) {
try {
bean.setBar(Integer.parseInt(value));
} catch (NumberFormatException e) {
throw new NamingException("Invalid 'bar' value " + value);
}
}
}
// Return the customized instance
return (bean);
I wanted to know if there was a method to do the exact same thing but with less steps
a web application on Tomcat 8.0
Tomcat 8.0 has reached End of Life. Do not use it. See "Migration Guide" at tomcat.apache.org to upgrade to Tomcat 8.5 or 9.0.
See "JDNI Resources" in Tomcat documentation. E.g. factory="org.apache.naming.factory.BeanFactory" can be used to create an arbitrary bean.
If you just need a set of configurable properties, defining them with "Parameter" element in Context will be easier. A web application will get those values via javax.servlet.ServletContext.getInitParameter(name) API.
Related
I have a requirement to read information that is available in the META/MANIFEST.MF file of Spring Boot MVC web application and use this info to perform some business logic. I'm using gradle to build the application as war file and deploying it into the external tomcat.
I have tried the following:
#Configuration
public class AppConfig
{
#Bean("manifest")
public java.util.jar.Manifest getManifest() throws IOException
{
InputStream inputFile = this.getClass().getClassLoader().getResourceAsStream("META-INF/MANIFEST.MF");
Manifest manifestObj = new Manifest(inputFile);
return manifestObj;
}
}
AppService.java
#Service
public class AppService
{
#Autowired
#Qualifier("manifest")
private Manifest manifest;
#PostConstruct
public String init()
{
Attributes mainAttributes = manifest.getMainAttributes();
String buildNum = mainAttributes.getValue("Build-Number");
String customPropInfo= mainAttributes.getValue("customPropInfo");
String systemPrp1= buildNum + "_" + "SomeBusinessInfoLogic1";
String systemPrp2= customPropInfo+ "_" + "SomeBusinessInfoLogic2";
//Some Business Logic with these attributes systemPrp, systemPrp2
logger.info("System Props are updated");
}
}
I'm getting null for both buildNum and customPropInfo.
Note: I have tried creating the Manifest bean something like this which was created by me. As per the #M.Deinum suggestion I'm creating this new question here. I also tried the solutions here which didn't work for me.
#M.Deinum suggested to make use of Spring Boot's Actuator Info endpoint. But this endpoint is useful when we want to access the info outside of the application but my requirement is different as I need the data that is available in MANIFEST.MF file to perform some business operations within the application.
I get the following error when I tried this solution "/META-INF/MANIFEST.MF".
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'manifest' defined in class path resource [com/abc/AppConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.jar.Manifest]: Factory method 'getManifest' threw exception; nested exception is java.lang.NullPointerException: Cannot invoke "java.io.InputStream.read(byte[], int, int)" because "this.in" is null
Can someone please help me to read information from META/MANIFEST.MF of the Spring Boot MVC Web Application?.
UPDATE1: I get the following MainAttributes when I try to print MainAttributes. But the problem is when I try to deploy the war into external tomcat.
System.out.println("Manifest MainAttributes = " +manifestObj.getMainAttributes().keySet());
Output:
Manifest MainAttributes = [Manifest-Version, Implementation-Title, Automatic-Module-Name, Implementation-Version, Built-By, Spring-Boot-Jar-Type, Build-Jdk-Spec]
UPDATE2:
I have updated to AppService.java to print the info available in autowired Manifest object. Something like below:
#Configuration
public class AppConfig
{
#Bean("manifest")
public java.util.jar.Manifest getManifest() throws IOException
{
InputStream inputFile = new ClassPathResource("/META-INF/MANIFEST.MF").getInputStream();
Manifest manifestObj = new Manifest(inputFile);
System.out.println("Manifest Manifest-Version = " +manifestObj.getMainAttributes().getValue("Manifest-Version"));
System.out.println("Manifest KeySet = " +manifestObj.getMainAttributes().keySet());
return manifestObj;
}
}
#Service
public class AppService
{
#Autowired
#Qualifier("manifest")
private Manifest manifest;
#PostConstruct
public String init()
{
Attributes mainAttributes = manifest.getMainAttributes();
mainAttributes.forEach((k,v) -> {
System.out.println("AppService.init(): Key = "+k+", Value = "+v);
});
String buildNum = mainAttributes.getValue("Build-Number");
String customPropInfo= mainAttributes.getValue("customPropInfo");
String systemPrp1= buildNum + "_" + "SomeBusinessInfoLogic1";
String systemPrp2= customPropInfo+ "_" + "SomeBusinessInfoLogic2";
//Some Business Logic with these attributes systemPrp, systemPrp2
logger.info("System Props are updated");
}
}
I see the following output on the console:
AppService.init(): Key = Implementation-Title, Value = Apache Tomcat Bootstrap
AppService.init(): Key = Implementation-Version, Value = 9.0.12
AppService.init(): Key = Specification-Vendor, Value = Apache Software Foundation
AppService.init(): Key = Specification-Title, Value = Apache Tomcat Bootstrap
AppService.init(): Key = Class-Path, Value = commons-daemon.jar
AppService.init(): Key = Manifest-Version, Value = 1.0
AppService.init(): Key = Main-Class, Value = org.apache.catalina.startup.Bootstrap
AppService.init(): Key = Implementation-Vendor, Value = Apache Software Foundation
AppService.init(): Key = Ant-Version, Value = Apache Ant 1.9.9
AppService.init(): Key = X-Compile-Target-JDK, Value = 1.8
AppService.init(): Key = X-Compile-Source-JDK, Value = 1.8
AppService.init(): Key = Created-By, Value = some xyz
AppService.init(): Key = Specification-Version, Value = 9.0
So just by the above output, I think MANIFEST.MF is not application specific but is from commons-daemon.jar.
OK - the problem isn't that you "can't read data from META/MANIFEST.MF in Spring Boot MVC Web Application". Rather, the problem is that your code happens to be reading the WRONG MANIFEST.MF from some other, random .jar in the classpath.
One solution might be to use JarClassLoader.
Another solution, as M. Deinum suggested, might be to store the properties you wish to retrieve in application.properties (or some other "global" properties file) instead of MANIFEST.MF.
ALSO:
I assume you're probably using an IDE to develop your app (Eclipse, Netbeans, etc). If you haven't already, I would STRONGLY encourage you to familiarize yourself with your IDE's debugger: the ability to set breakpoints, display variables, single-step through method calls, etc.
I want the fetch the list of resource types available for a patient from the FHIR server. I tried to fetch the List of items from the Patient. But it doesn't contain all the resource types available for that particular patient.
I think what your asking is resources types linked to a Patient. In this case you can do a http://base/fhir/Patient/[id]/$everything
https://hl7.org/fhir/operation-patient-everything.html
When you say "the list of resource types available for a patient from the FHIR server", I think what you mean is the resource types that can be searched by patient on the FHIR server.
To build such a list, read the FHIR server's CapabilityStatement. Iterate through the CapabilityStatement's resource array, checking each item's searchParam array for an element where name = 'patient' and type = 'reference'. If you find it, add that item's type value (i.e. the resource type) to the list.
http://hapi.fhir.org/baseR4/metadata
http://wildfhir4.aegis.net/fhir4-0-1/metadata
https://vonk.fire.ly/R4/metadata
I think you're asking this.
If you examine any of the reference implementations above: you'll retrieve the CapabilityStatement.
EVERY FHIR server must supply a CapabilityStatement.
You look at the CapabilityStatement, find the Fhir-Resource ("Patient" here), and then any includes and rev-includes.
"type": "Patient",
"searchInclude": [
"Patient:general-practitioner",
"Patient:link",
"Patient:organization"
],
"searchRevInclude": [
"Account:patient",
"Account:subject",
also see:
https://www.hl7.org/fhir/search.html#include
Thank you all for posting your answers. I have achieved this by making a batch request like the below,
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
// Adding bundle entries to make the batch request
// To fetch the resource count for each resource type
for (String resource : resources) {
String url = String.format(MyConstants.URLs.FETCH_SUMMARY_COUNT,
resource, patientId);
bundle.addEntry().getRequest().setUrl(url).setMethod(Bundle.HTTPVerb.GET);
}
try {
// Create a client
IGenericClient client = fhirContext.newRestfulGenericClient(fhirServerUrl);
BearerTokenAuthInterceptor bearerTokenAuthInterceptor = new BearerTokenAuthInterceptor(accessToken);
client.registerInterceptor(bearerTokenAuthInterceptor);
bundle = client.transaction().withBundle(bundle).execute();
IFhirPath iFhirPath = fhirContext.newFhirPath();
List<IntegerType> counts = iFhirPath.evaluate(bundle, MyConstants.FHIRPATH.SUMMARY_COUNT_FROM_BUNDLE,
IntegerType.class);
ResourceTypesResponse resourceTypesResponse = ResourceParser.getResourceTypes(counts, resources);
if (resourceTypesResponse == null) {
throw new PatientNotFoundException("Resource Not Found");
}
return resourceTypesResponse;
} catch (Exception e) {
if (e.getMessage().equalsIgnoreCase("HTTP 401 Unauthorized")) {
return getResourceBundleForPatient(patientId, resources);
} else {
throw new PatientNotFoundException("Patient Id Not Found");
}
}
I am in the process of migrating existing VMSS tomcat project to AKS. In VM, we use server.xml to provide environment variables using <Environment .../> tags. For Kubernetes, I am using env: field to replace these values.
In the project, there is code Config.java:
private Object getObject(String key) {
try {
log.debug("looking up key: " + key + " in environment context.");
Object rtn = getEnvContext().lookup(key);
log.debug("Found property for " + key + ": " + rtn);
return rtn;
} catch (NamingException e) {
log.debug("Unable to find key: " + key);
return null;
}
}
private Context getEnvContext() throws NamingException {
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
return envCtx;
}
From what is java:comp/env?, it says that java:comp/env is the node in the JNDI tree where you can find properties for the current Java EE component.
Here are my questions.
When tomcat starts, does it use server.xml <Environment .../> tag to create properties in the node in the JNDI tree? (thus, when .getObject(someString) is called, it can get the value defined in server.xml)
If I want to replace server.xml with Helm chart env field, will these properties (defined in Helm) be defined in the node in the JNDI tree? or do I need to define these beans in jndi.xml as JndiObjectFactoryBean?
I am sorry if I am not making sense at all here. I am very new to tomcat and concept of JNDI and JNDI tree. Please let me know if I need to clarify myself.
W've just upgraded our application from Hazelcast 3.8.0 to 3.10.1.
We am getting an error message "key cannot be of type Data!" when accessing data in Hazelcast.
java.lang.IllegalArgumentException: key cannot be of type Data!
at com.hazelcast.util.Preconditions.checkNotInstanceOf(Preconditions.java:300)
at com.hazelcast.internal.nearcache.impl.DefaultNearCache.checkKeyFormat(DefaultNearCache.java:226)
at com.hazelcast.internal.nearcache.impl.DefaultNearCache.get(DefaultNearCache.java:114)
at com.hazelcast.map.impl.tx.TransactionalMapProxySupport.getCachedValue(TransactionalMapProxySupport.java:183)
at com.hazelcast.map.impl.tx.TransactionalMapProxySupport.getInternal(TransactionalMapProxySupport.java:132)
at com.hazelcast.map.impl.tx.TransactionalMapProxy.get(TransactionalMapProxy.java:110)
at com.hazelcast.client.impl.protocol.task.transactionalmap.TransactionalMapGetMessageTask.innerCall(TransactionalMapGetMessageTask.java:43)
at com.hazelcast.client.impl.protocol.task.AbstractTransactionalMessageTask.call(AbstractTransactionalMessageTask.java:34)
at com.hazelcast.client.impl.protocol.task.AbstractCallableMessageTask.processMessage(AbstractCallableMessageTask.java:35)
at com.hazelcast.client.impl.protocol.task.AbstractMessageTask.initializeAndProcessMessage(AbstractMessageTask.java:130)
at com.hazelcast.client.impl.protocol.task.AbstractMessageTask.run(AbstractMessageTask.java:110)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
at com.hazelcast.util.executor.HazelcastManagedThread.executeRun(HazelcastManagedThread.java:64)
at com.hazelcast.util.executor.HazelcastManagedThread.run(HazelcastManagedThread.java:80)
at ------ submitted from ------.(Unknown Source)
I am 100% sure that the "key" We're using is a String. The code snippet looks like this:
String key = getKey(blah, blah);
TransactionContext context = client.newTransactionContext();
context.beginTransaction();
TransactionalMap<String, AppPrefs> dataMap = context.getMap(MAP_NAME);
try {
prefs = dataMap.get(key);
context.commitTransaction();
} catch (Throwable t) {
LOGGER.error("Error getting AppPrefs.", t);
context.rollbackTransaction();
}
The line of code throwing the error is:
prefs = dataMap.get(key);
There is nothing between the line of code that sets the String
String key = getKey(blah, blah);
and the line that blows :(
Following the source code through, the TransactionalMapProxySupport's method "toNearCacheKeyWithStrategy" has this logic:
final Object toNearCacheKeyWithStrategy(Object key) {
if (!nearCacheEnabled) {
return key;
}
return serializeKeys ? ss.toData(key, partitionStrategy) : key;
}
The "DefaultNearCache" object then does a
private void checkKeyFormat(K key) {
if (!serializeKeys) {
checkNotInstanceOf(Data.class, key, "key cannot be of type Data!");
}
}
So it looks like the property of "TransactionalMapProxySupport" called "serializeKeyes" must have been FALSE but the same-named property in "DefaultNearCache"must have been true :(
The history of "DefaultNearCache" in git shows that the code was changed a year ago with the message: "Changed serialize-keys default for Near Cache from true to false"
Is there some configuration I should be setting?
The config I have for the map is simply:
<near-cache name="AppPrefsCache">
<!-- Cache locally for 10 mins -->
<max-idle-seconds>600</max-idle-seconds>
</near-cache>
Ah! It appears that I can add a " serialize-keys" tag of (true|false) to this xml and set the value.
It appears that different bits of the Hazelcast codebase are assuming a different default value for "serialize-keys". Either it should be a mandatory element in the config or the default should be the same everywhere, yes?
The mentioned behavior is really a bug in Hazelcast. I've created a new GitHub issue for it, so we can handle it properly - hazelcast/hazelcast#13371.
The workaround for versions affected by the bug is to set serialize-keys to true in the near-cache configuration. The false value won't help here.
Config config = new Config();
config.getMapConfig(testName)
.setNearCacheConfig(new NearCacheConfig().setSerializeKeys(true));
Thanks for the report and great investigation, Peter.
When using a directory-expression for an <int-file:outbound-gateway> endpoint, the method below is called on org.springframework.integration.file.FileWritingMessageHandler:
private File evaluateDestinationDirectoryExpression(Message<?> message) {
final File destinationDirectory;
final Object destinationDirectoryToUse = this.destinationDirectoryExpression.getValue(
this.evaluationContext, message);
if (destinationDirectoryToUse == null) {
throw new IllegalStateException(String.format("The provided " +
"destinationDirectoryExpression (%s) must not resolve to null.",
this.destinationDirectoryExpression.getExpressionString()));
}
else if (destinationDirectoryToUse instanceof String) {
final String destinationDirectoryPath = (String) destinationDirectoryToUse;
Assert.hasText(destinationDirectoryPath, String.format(
"Unable to resolve destination directory name for the provided Expression '%s'.",
this.destinationDirectoryExpression.getExpressionString()));
destinationDirectory = new File(destinationDirectoryPath);
}
else if (destinationDirectoryToUse instanceof File) {
destinationDirectory = (File) destinationDirectoryToUse;
} else {
throw new IllegalStateException(String.format("The provided " +
"destinationDirectoryExpression (%s) must be of type " +
"java.io.File or be a String.", this.destinationDirectoryExpression.getExpressionString()));
}
validateDestinationDirectory(destinationDirectory, this.autoCreateDirectory);
return destinationDirectory;
}
Based on this code I see that if the directory to use evaluates to a String, it uses that String to create a new java.io.File object.
Is there a reason that a ResourceLoader couldn't/shouldn't be used instead of directly creating a new file?
I ask because my expression was evaluating to a String of the form 'file://path/to/file/' which of course is an invalid path for the java.io.File(String) constructor. I had assumed that Spring would treat the String the same way as it treats the directory attribute on <int-file:outbound-gateway> and pass it through a ResourceLoader.
Excerpt from my configuration file:
<int-file:outbound-gateway
request-channel="inputChannel"
reply-channel="updateTable"
directory-expression="
'${baseDirectory}'
+
T(java.text.MessageFormat).format('${dynamicPathPattern}', headers['Id'])
"
filename-generator-expression="headers.filename"
delete-source-files="true"/>
Where baseDirectory is a property that changes per-environment of the form 'file://hostname/some/path/'
There's no particular reason that this is the case, it probably just wasn't considered at the time of implementation.
The request sounds reasonable to me and will benefit others (even though you have found a work-around), by providing simpler syntax. Please open an 'Improvement' JIRA issue; thanks.
While not directly answering the question, I wanted to post the workaround that I used.
In my XML configuration, I changed the directory-expression to evaluate to a file through the DefaultResourceLoader instead of a String.
So this is what my new configuration looked like:
<int-file:outbound-gateway
request-channel="inputChannel"
reply-channel="updateTable"
directory-expression=" new org.springframework.core.io.DefaultResourceLoader().getResource(
'${baseDirectory}'
+
T(java.text.MessageFormat).format('${dynamicPathPattern}', headers['Id'])).getFile()
"
filename-generator-expression="headers.filename"
delete-source-files="true"/>