Changing path to hardcoded string - works, however this is not an option.
Test fails because it cannot find the resource, even though it's there.
Cities.java
/*
This Singleton Class loads cities.json into an ArrayList and by using a boolean method isCity can tell if passed
String is city or not.
*/
public class Cities
{
private static Cities single_instance = null;
ArrayList<City> cityArrayList;
private Cities() throws IOException
{
String path = new ClassPathResource("cities.json").getFile().getAbsolutePath();
Gson gson = new Gson();
Type cityListType = new TypeToken<ArrayList<City>>()
{
}.getType();
cityArrayList = gson.fromJson(new FileReader(path), cityListType);
}
public static Cities getInstance() throws IOException
{
if (single_instance == null)
single_instance = new Cities();
return single_instance;
}
public boolean isCity(String cityToLookFor)
{
for (City city : cityArrayList)
{
if (city.getName().strip().equalsIgnoreCase(cityToLookFor))
{
return true;
}
}
return false;
}
}
CitiesTest.java
class CitiesTest
{
Cities cities = Cities.getInstance();
CitiesTest() throws IOException {}
#Test
void isCityTest2()
{
assertFalse(cities.isCity("USA"), "City not found in localDbase");
}
#Test
void isCityTest3()
{
assertEquals(true, cities.isCity("Paris"), "City not found in localDbase");
}
}
pom.xml
https://pastebin.com/fMQNknM1
java.io.FileNotFoundException: class path resource [cities.json] cannot be resolved to URL because it does not exist
at org.springframework.core.io.ClassPathResource.getURL(ClassPathResource.java:195)
at org.springframework.core.io.AbstractFileResolvingResource.getFile(AbstractFileResolvingResource.java:150)
at gr.serresparc.palantir.repository.Cities.<init>(Cities.java:24)
at gr.serresparc.palantir.repository.Cities.getInstance(Cities.java:35)
at gr.serresparc.palantir.repository.CitiesTest.<init>(CitiesTest.java:12)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at
Remove the <targetPath>..</targetPath> from the resources section of the pom.xml. You might as well remove the whole resources section.
The executed test code is not looking for the file in within src/main/resources but within target/classes. The classpath resource cities.json needs to appear there.
Please be also aware that if the application is shipped as a spring boot jar file then it is not possible to read a classpath resource as file.
Try to change the code as below,
String path = new ClassPathResource("classpath:cities.json").getFile().getAbsolutePath();
Please, note I have added classpath: in front of cities.json.
EDIT
You can also get the InputStreamReader like below, First, inject ResourceLoader.
#Autowired
ResourceLoader resourceLoader;
Then load json file just like below:
cityArrayList = gson.fromJson(new JsonReader(resourceLoader.getResource("classpath:cities.json").getInputStream()), cityListType);
Related
I want to format some files in SpringBoot with one request for each file. With each request, I have to call the getOutputFolder(dirName) method to get an output path in order to save the file in the expected path but my solution comes with high at overhead cost. I want to define one constant and then when I have to call the function I instead call this. But I feel it seems to be wrong or at least like a sneaky way to do. Is there any better way to solve this problem?
private static final String OUTPUT_FOLDER_PATH = getOutputFolderPath();
private String getOutputFolder(String dirName) {
String pathStr = getOutputFolderPath() + dirName + File.separator + "submit" + File.separator;
Path outputDirPath = Paths.get(pathStr);
Path path = null;
boolean dirExists = Files.exists(outputDirPath);
if (!dirExists) {
try {
path = Files.createDirectories(outputDirPath);
} catch (IOException io) {
logger.error("Error occur when create the folder at: {}", pathStr);
}
}
return dirExists ? pathStr : Objects.requireNonNull(path).toString();
}
You might want to look at jcache.
To do this, you need to install it to your Spring Boot project
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'javax.cache:cache-api:1.1.0'
// or the maven equivalent if you are using maven
Then create a org.springframework.cache.CacheManager bean to configure the cache.
#Bean
public CacheManager cacheManager() {
CachingProvider cachingProvider = Caching.getCachingProvider();
CacheManager cacheManager = cachingProvider.getCacheManager();
// The class arguments is <String, String> because the method to cache accepts a String and returns a String
// just explore this object for the config you need.
MutableConfiguration<String, String> configuration = new MutableConfiguration<>();
String cacheName = "OUTPUT_FOLDER_CACHE";
cacheManager.createCache(cacheName, configuration);
return cacheManager;
}
When this is setup, you can now annotated the method to be cached.
#Cacheable(
cacheNames = { "OUTPUT_FOLDER_CACHE" }, // The same string in config
unless = "#result == null" // Dont' cache null result; or do, if you need it.
)
String getOutputFolder(String dirName) {
// method contents...
}
When properly configured: the method will return the cache value if it exists, or run the actual method, cache the result and return the result if the cached value does not exist.
You can solve that issue by using ThreadLocal.
A threadlocal can store value and you can make useful for yourself. Suppose if your getOutputFolderPath() is different for different request then you can
store the getOutputFolderPath() value while a new request dispatched on server and you can do your all operation upto your request live.
See Threadlocal Docs
#Service
public class FileSaveService {
private static final ThreadLocal<String> path=new ThreadLocal<>();
#PostConstruct
public void init() {
setPath(getOutputFolderPath());
}
public void setPath(String pathString) {
path.set(pathString);
}
public String getPath() {
if(path.get() == null) return getOutputFolderPath();
return path.get();
}
#PreDestroy
public void destroy() {
path.remove();
}
}
When I start the application, I need to load properties from different sources: war, file system, database, and JVM. I need to load properties once and use them within running my application. I do not need to refresh it. I don't have DI - it is a simple java application with singletons. I decide to create AppProperties singleton and load properties when starting the application. It is the best solution by the current time for me(I hope somebody makes the best solution). It is my Singleton:
import java.io.InputStream;
import java.util.Properties;
public class AppProperties {
private static AppProperties instance;
private Properties propertiesFromWar;
private Properties propertiesFromFile;
private Properties propertiesFromDB;
private AppProperties() {
propertiesFromWar = new Properties();
try {
propertiesFromWar.load(getPropertiesAsInputStreamFromWar());
propertiesFromFile.load(getPropertiesAsInputStreamFromFile());
propertiesFromDB.load(getPropertiesAsInputStreamFromDB());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private InputStream getPropertiesAsInputStreamFromDB() {
//some implementation
return null;
}
private InputStream getPropertiesAsInputStreamFromFile() {
//some implementation
return null;
}
private InputStream getPropertiesAsInputStreamFromWar() {
return getClass().getResourceAsStream("META-INF/application.properties");
}
public static AppProperties getInstance() {
if (instance == null) {
instance = new AppProperties();
}
return instance;
}
public String getProperty(String key) {
String value;
value = System.getProperty(key);
if (value == null) {
value = propertiesFromDB.getProperty(key);
if (value == null) {
value = propertiesFromFile.getProperty(key);
if (value == null) {
value = propertiesFromWar.getProperty(key);
}
}
}
return value;
}
}
But I do not understand, How can I use it in tests. Because I hardcode paths for aplication.properties files. And when I will create this instance in the tests, I will create AppProperties with real properties.
I tried to add a public method like load(filePath). But with this method, it will be not a singleton. If somebody will call this method in another place of application - my singleton will be reloaded with new data. Now I have 2 problems.
If I add load() method - it will be dangerous for reloading data. But I can use it in tests.
If I do not add this method - I can not test it.
P.S I read this article The Little Singleton
But I do not understand some moments. If I have singleton with private constructor, I can not extend it like in article.
In your test resources directory, create META-INF directory. Here create a file application.properties and add some properties for testing purposes in it.
Make sure the above directory is in the classpath when you will run the tests. This way, when getPropertiesAsInputStreamFromWar() is called, it will look for META-INF/application.properties in the classpath.
Being tests allow specifying JVM launch arguments, this can be "solved" pretty easily.
This also adds some flexibility.
java -DpropertiesPath="..." -jar yourJar.jar
And, adapting your code
private InputStream getPropertiesAsInputStreamFromWar() {
final String propertiesPath = Objects.requireNonNull(System.getProperty("propertiesPath"));
return getClass().getResourceAsStream(propertiesPath);
}
Instead of requireNonNull, you could use a default value, e.g.
META-INF/application.properties.
I have a maven project with the file src/main/resources/barcoding.properties. I keep getting a null pointer with the code below when trying to get the barcoding.properties file with the code below:
public class BarcodingProperties{
private static Properties props = null;
private static void getProperties() {
System.out.println("we in getProperties");
props = new Properties();
InputStream in = BarcodingProperties.class.getClass().getClassLoader().getResourceAsStream("barcoding.properties"); <-- This line returns null pointer exception.
System.out.println("in. = "+in.toString());
try {
props.load(in);
} catch (IOException e) {
System.out.println("unable to load properties");
e.printStackTrace();
}
}
}
When i build the project and look into the jar file, barcoding.properties exists right in the project root.
Why does this return null and how can i fix it?
You are trying to use the classloader of Class for loading the resource, instead of BarcodingProperties like this:
BarcodingProperties.class.getClassLoader().getResourceAsStream("barcoding.properties");
The Class's classloader is null, indicating that it was loaded by the bootstrap class loader, see Stock JDK classes and the "null" ClassLoader? for more information.
This seems strange:
BarcodingProperties.class.getClass()
BarcodingProperties.class is the BarcodingProperties class object instance. if you do a getClass() on that, it'll return the Class class object...
Ideone fiddle though with String.class
What you want is probably:
InputStream in = BarcodingProperties.class.getClassLoader().getResourceAsStream("barcoding.properties");
I'm pretty new to Java so bear with me. I can't for the life of me figure out why I'm getting
a cannot find symbol error on resourceResolver.resolve. When on the line above it I'm defining the variable.
Maybe this is something simple I'm missing but I can't figure this out and I feel like I've
stared at this way to long.
private static final String ROOTCHILD = "rootChild";
public void setResource(Resource resource) {
this.resource = resource;
}
public void setProperties(ValueMap properties) {
this.properties = properties;
}
public Page getRootPage() {
ResourceResolver resourceResolver = getResource().getResourceResolver();
return (this.properties != null)
? resourceResolver.resolve(
properties.get( ROOTCHILD,currentPage.getPath())).adaptTo(Page.class)
: null;
}
My guess here (never worked with sling and haven't used Java for a while):
I think the problem is you initialized the ValueMap properties so that it doesn't contain Strings or HttpServletRequests, but something else. The .resolve() method only accepts either a String or an HttpServletRequest. (Or two parameters, but you're only passing one, so that one can't be the case.) There is no .resolve() method found accepting the parameters you try to give it, so that symbol is not found!
To see the true error, rewrite your code and compile it:
public Page getRootPage() {
if( properties == null ) {
return null;
}
YYYYYY resource = getResource();
ResourceResolver resourceResolver = resource.getResourceResolver();
String path = currentPage.getPath();
String rootChild = properties.get( ROOTCHILD, path );
XXXXXX rc = resourceResolver.resolve( rootChild );
return rc.adaptTo( Page.class );
}
With Jasper, I use resources to load the report. So, to load the main report, I use something like :
InputStream is = getClass().getResourceAsStream("/resources/report1.jrxml");
design = JRXmlLoader.load(is);
But, if there is a subreport in report1.jrxml, how to say it is in /resources/sub.jrxml ?
I did it this way:
jasperDesign = JRXmlLoader.load(rootpath + "/WEB-INF/templates/Report.jrxml");
jasperDesignSR = JRXmlLoader.load(rootpath + "/WEB-INF/templates/SubReport.jrxml");
JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);
JasperReport jasperReportSR = JasperCompileManager.compileReport(jasperDesignSR);
parameters.put("SubReportParam", jasperReportSR);
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource);
"SubReportParam" would be a parameter of the type "JasperReport" as a SubreportExpression within your Report.
In the .jrxml:
<parameter name="SubReportParam" class="net.sf.jasperreports.engine.JasperReport" isForPrompting="false"/>
I don't know if You use IReport for your Design of Reports.
With a right click on your subreport you should find the SubreportExpression.
parameters is a map which I pass to "fillReport"
Good luck.
I was not quite happy with lkdg's answer because I wanted to separate the concern of loading the correct file from the design as in my opinion I should not be forced to organize from where the reports are loaded at design time of the JRXML files.
Unfortunately the code of the Jasper Library is full of static references that make it hard to find the correct spot for the injection of a custom subreport loader and also some of the documentation sucks (e.g. the interface RepositoryService is completely lacking a contract documentation so I needed to guess the contract by reading calling code), but it is possible:
private static void fillReport() throws IOException, JRException {
// The master report can be loaded just like that, because the
// subreports will not be loaded at this point, but later when
// report is filled.
final JasperReport report = loadReport("masterReport.jasper");
// The SimpleJasperReportsContext allows us to easily specify some
// own extensions that can be injected into the fill manager. This
// class will also delegate to the DefaultJasperReportsContext and
// combine results. Thus all the default extensions will still be available
SimpleJasperReportsContext jasperReportsContext = new SimpleJasperReportsContext();
jasperReportsContext.setExtensions(
RepositoryService.class, singletonList(new SubReportFindingRepository())
);
final byte[] pdf = JasperExportManager.exportReportToPdf(
JasperFillManager
.getInstance(jasperReportsContext)
// carefully select the correct `fill` method here and don't
// accidentally select one of the static ones!:
.fill(report, YOUR_PARAMS, YOUR_CONNECTION)
);
}
private static JasperReport loadReport(final String fileName) throws IOException, JRException {
try(InputStream in = loadReportAsStream(fileName)) {
return (JasperReport) JRLoader.loadObject(in);
}
}
private static InputStream loadReportAsStream(final String fileName) {
final String resourceName = "/package/path/to/reports/" + fileName;
final InputStream report = CurrentClass.class.getResourceAsStream(resourceName);
if (report == null) {
throw new RuntimeException("Report not found: " + resourceName);
}
return report;
}
private static class SubReportFindingRepository implements RepositoryService {
#Override
public Resource getResource(final String uri) {
return null; // Means "not found". The next RepositoryService will be tried
}
#Override
public void saveResource(final String uri, final Resource resource) {
throw new UnsupportedOperationException();
}
#Override
public <K extends Resource> K getResource(final String uri, final Class<K> resourceType) {
if (!isKnownSubReport(uri)) {
return null; // Means "not found". The next RepositoryService will be tried
}
final ReportResource reportResource = new ReportResource();
try {
reportResource.setReport(loadReport(uri));
} catch (IOException | JRException e) {
throw new Error(e);
}
return resourceType.cast(reportResource);
}
private static boolean isKnownSubReport(final String uri) {
return "subReport1.jasper".equals(uri) || "subReport2.jasper".equals(uri);
}
}
As an alternative to the local injection you can also write a global extension. As far as I got it (I did not try) this requires the creation of a jasperreports_extension.properties file with class names that should be loaded which can include a custom repository to load the reports from. However in this case you completely loose the ability to work with conflicting configurations needed in different use cases.