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.
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();
}
}
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);
I'm working on the reporting module of our web-application. There are six reports available to the client, each of them has a code. The problem is that now the module is not closed for modification with respect to potential addition of new reports, thus violating OCP.
To elucidate, I have the following set of classes:
A generic report class, which all other reports inherit:
public abstract class Report
{
private final String code;
Report(String code)
{
this.code = code;
}
public String getCode() { return code; }
public abstract byte[] generate();
}
A servlet which manages POST requests for report generation:
public class ReportServlet extends HttpServlet
{
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
Report requested = ReportRegistry.lookup(req.getParameter("report_code"));
byte[] bytes = requested.generate();
// attach bytes to response
}
}
Report registry, which stores all existing reports for later access:
public class ReportRegistry
{
private static final Map<String, Report> registry = new HashMap<>();
static
{
// Violates OCP!
registerReport( GlobalReport.getInstance() );
registerReport( AvailablePackagesReport.getInstance() );
registerReport( BadgeReport.getInstance() );
registerReport( PlacementReport.getInstance() );
registerReport( TerminalReport.getInstance() );
registerReport( VerActReport.getInstance() );
}
private ReportRegistry() { }
static void registerReport(final Report report)
{
registry.put(report.getCode(), report);
}
public static Report lookup(final String reportCode)
{
return registry.get(reportCode);
}
}
However, ReportRegistry violates OCP, since we need to add an entry to its static block every time a new report is created.
My question is: how can I make any new subclass of Report to be registered automatically, without any explicit mentioning?
I would think OCP would be more applicable to Report itself, and that having ReportRegistry sitting outside of the class hierarchy would be a valid design.
That said, if you want to avoid modifying ReportRegistry each time you create a Report subclass, you could use some reflection tricks to seek out all such subclasses, or create an annotation that ReportRegistry could search for to register all classes with instances.
You should look at https://github.com/ronmamo/reflections. I have never tried this library but it looks like it does what you want (retrieving all subclasses of a known class).
You could then register them in your ReportRegistry static block.
Consider the following servlet code:
public class AddDevice extends JsonServlet {
private static final long serialVersionUID = 1L;
#Override
protected void doGet(final JsonServletRequest request,
final JsonServletResponse response) throws ServletException,
IOException {
try {
final DeviceEntity device = new DeviceEntity();
device.type =
FleetManagerDatabaseHelper
.deviceTypesAccessor()
.queryForId(Integer.valueOf(
request.getParameter(DeviceTypeEntity._ID)));
device.sn = request.getParameter(DeviceEntity._SN);
device.status = Long.valueOf(0);
FleetManagerDatabaseHelper.devicesAccessor().create(device);
}
catch (final SQLException e) {
throw new ServletException("device already exists");
}
}
}
This code depends on the DeviceEntity and on the FleetManagerDatabaseHelper classes.
Now, I would like to write a test for it checking that the created entity is filled with the correct type, sn and status values.
For this purpose I could create a FleetManagerDatabaseHelperMockup class.
How would you apply Google Guice (or something else) here with minimal changes?
Your first step is to design for dependency injection--avoid constructors and static methods, and instead take in instances that you require. It looks like those types are Provider<DeviceEntity>, DevicesAccessor, and DeviceTypesAccessor.
Provider is a very simple Guice interface that provides instances of whatever class is in its type argument via a single no-arg method get(). If you have bound Foo, Guice automatically knows how to bind Provider<Foo>. It is extremely useful if your instances are expensive, or if you need more than one over the lifetime of your servlet (as you do).
After refactoring for dependency injection, your class will look like this:
public class AddDevice extends JsonServlet {
private static final long serialVersionUID = 1L;
private final Provider<DeviceEntity> deviceEntityProvider;
private final DevicesAccessor devicesAccessor;
private final DeviceTypesAccessor deviceTypesAccessor;
#Inject
public AddDevice(Provider<DeviceEntity> deviceEntityProvider,
DevicesAccessor devicesAccessor,
DeviceTypesAccessor deviceTypesAccessor>) {
this.deviceEntityProvider = deviceEntityProvider;
this.devicesAccessor = devicesAccessor;
this.deviceTypesAccessor = deviceTypesAccessor;
}
#Override
protected void doGet(final JsonServletRequest request,
final JsonServletResponse response) throws ServletException,
IOException {
try {
final DeviceEntity device = deviceEntityProvider.get();
device.type = deviceTypesAccessor.queryForId(
Integer.valueOf(request.getParameter(DeviceTypeEntity._ID)));
device.sn = request.getParameter(DeviceEntity._SN)
device.status = Long.valueOf(0);
devicesAccessor.create(device);
} catch (final SQLException e) {
throw new ServletException("device already exists");
}
}
}
At this point, it's extremely easy to write a test by passing in a Provider that keeps track of the instance it returns, along with a mock DevicesAccessor and a mock DeviceTypesAccessor. (I recommend Mockito.) If you write your own Provider interface and remove the #Inject, you don't even need to use Guice; in your tests, you could continue to use that constructor, but you would want to satisfy Java EE with a constructor like:
public AddDevice() {
this(new NewDeviceEntityProvider(),
FleetManagerDatabaseHelper.deviceTypesAccessor(),
FleetManagerDatabaseHelper.devicesAccessor());
}
private class NewDeviceEntityProvider implements Provider<DeviceEntity> {
#Override public DeviceEntity get() {
return new DeviceEntity();
}
}
But if you do want to use Guice to remove that boilerplate, just write a Guice Module. Your module would need to bind DeviceTypesAccessor and DevicesAccessor to the instances that FleetManagerDatabaseHelper would return; Guice would see that DeviceEntity has a no-arg constructor and would be able to inject DeviceEntity and Provider<DeviceEntity> automatically. (Comment if you want me to expand on what the Module would look like.)
Hope this helps!
Is it possible to force Properties not to add the date comment in front? I mean something like the first line here:
#Thu May 26 09:43:52 CEST 2011
main=pkg.ClientMain
args=myargs
I would like to get rid of it altogether. I need my config files to be diff-identical unless there is a meaningful change.
Guess not. This timestamp is printed in private method on Properties and there is no property to control that behaviour.
Only idea that comes to my mind: subclass Properties, overwrite store and copy/paste the content of the store0 method so that the date comment will not be printed.
Or - provide a custom BufferedWriter that prints all but the first line (which will fail if you add real comments, because custom comments are printed before the timestamp...)
Given the source code or Properties, no, it's not possible. BTW, since Properties is in fact a hash table and since its keys are thus not sorted, you can't rely on the properties to be always in the same order anyway.
I would use a custom algorithm to store the properties if I had this requirement. Use the source code of Properties as a starter.
Based on https://stackoverflow.com/a/6184414/242042 here is the implementation I have written that strips out the first line and sorts the keys.
public class CleanProperties extends Properties {
private static class StripFirstLineStream extends FilterOutputStream {
private boolean firstlineseen = false;
public StripFirstLineStream(final OutputStream out) {
super(out);
}
#Override
public void write(final int b) throws IOException {
if (firstlineseen) {
super.write(b);
} else if (b == '\n') {
firstlineseen = true;
}
}
}
private static final long serialVersionUID = 7567765340218227372L;
#Override
public synchronized Enumeration<Object> keys() {
return Collections.enumeration(new TreeSet<>(super.keySet()));
}
#Override
public void store(final OutputStream out, final String comments) throws IOException {
super.store(new StripFirstLineStream(out), null);
}
}
Cleaning looks like this
final Properties props = new CleanProperties();
try (final Reader inStream = Files.newBufferedReader(file, Charset.forName("ISO-8859-1"))) {
props.load(inStream);
} catch (final MalformedInputException mie) {
throw new IOException("Malformed on " + file, mie);
}
if (props.isEmpty()) {
Files.delete(file);
return;
}
try (final OutputStream os = Files.newOutputStream(file)) {
props.store(os, "");
}
if you try to modify in the give xxx.conf file it will be useful.
The write method used to skip the First line (#Thu May 26 09:43:52 CEST 2011) in the store method. The write method run till the end of the first line. after it will run normally.
public class CleanProperties extends Properties {
private static class StripFirstLineStream extends FilterOutputStream {
private boolean firstlineseen = false;
public StripFirstLineStream(final OutputStream out) {
super(out);
}
#Override
public void write(final int b) throws IOException {
if (firstlineseen) {
super.write(b);
} else if (b == '\n') {
// Used to go to next line if did use this line
// you will get the continues output from the give file
super.write('\n');
firstlineseen = true;
}
}
}
private static final long serialVersionUID = 7567765340218227372L;
#Override
public synchronized Enumeration<java.lang.Object> keys() {
return Collections.enumeration(new TreeSet<>(super.keySet()));
}
#Override
public void store(final OutputStream out, final String comments)
throws IOException {
super.store(new StripFirstLineStream(out), null);
}
}
Can you not just flag up in your application somewhere when a meaningful configuration change takes place and only write the file if that is set?
You might want to look into Commons Configuration which has a bit more flexibility when it comes to writing and reading things like properties files. In particular, it has methods which attempt to write the exact same properties file (including spacing, comments etc) as the existing properties file.
You can handle this question by following this Stack Overflow post to retain order:
Write in a standard order:
How can I write Java properties in a defined order?
Then write the properties to a string and remove the comments as needed. Finally write to a file.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
properties.store(baos,null);
String propertiesData = baos.toString(StandardCharsets.UTF_8.name());
propertiesData = propertiesData.replaceAll("^#.*(\r|\n)+",""); // remove all comments
FileUtils.writeStringToFile(fileTarget,propertiesData,StandardCharsets.UTF_8);
// you may want to validate the file is readable by reloading and doing tests to validate the expected number of keys matches
InputStream is = new FileInputStream(fileTarget);
Properties testResult = new Properties();
testResult.load(is);