Internal references in propertyfiles for Spring MessageSource - spring

I have several webpages with similar forms on them.
One field that exists in several of the pages are email-address.
I want to be able to use a page specific message code, but I would like to be able to reference another message code in order to have a single declaration. In this way, I can change the look of the email-adress label one place and have it changed in all the webpages, but at the same time, I'm able to change the text for a single page with only propertyfile updates.
I'm looking for functionality like this:
message.properties:
label.email=Email address
webpage1.label.email=${label.email}
webpage2.label.email=${label.email}
However,
when using the following jsp-code:
<spring:message code="webpage1.label.email"/>
I get the literal ${label.email} instead of "Email address" in my webpages.
Any hints?

You can replace the DefaultPropertiesPersister with this one:
This will allow you to reference other entries, e.g.:
user=User
user.add=Add ${user}
user.delete=Delete ${user}
Simply specify this persister with your MessageSource, e.g. messageSource.setPropertiesPersister(new RecursivePropertiesPersister());
Source:
public class RecursivePropertiesPersister extends DefaultPropertiesPersister {
private final static Pattern PROP_PATTERN = Pattern.compile("\\$'?\\{'?([^}']*)'?\\}'?");
private final static String CURRENT = "?LOOP?";
#Override
public void load(Properties props, Reader reader) throws IOException {
Properties propsToLoad = new Properties();
super.load(propsToLoad, reader);
replace(propsToLoad, props);
}
#Override
public void load(Properties props, InputStream is) throws IOException {
Properties propsToLoad = new Properties();
super.load(propsToLoad, is);
replace(propsToLoad,props);
}
protected void replace ( Properties src, Properties dest) {
for (Map.Entry entry: src.entrySet()) {
String key = (String) entry.getKey();
String value = (String)entry.getValue();
replace(src, dest, key, value);
}
}
protected String replace(Properties src, Properties dest, String key, String value) {
String replaced = (String) dest.get(key);
if (replaced != null) {
// already replaced (or loop), just return the string
return replaced;
}
dest.put(key,CURRENT); // prevent loops
final Matcher matcher = PROP_PATTERN.matcher(value);
final StringBuffer sb = new StringBuffer();
while (matcher.find()) {
final String subkey = matcher.group(1);
final String replacement = (String)src.get(subkey);
matcher.appendReplacement(sb,replace(src,dest,subkey,replacement));
}
matcher.appendTail(sb);
final String resolved = sb.toString();
dest.put(key, resolved);
return resolved;
}
}

Related

Serving list of images in spring boot

I have created assets folder inside resources. I want to display list of image names in the assets folder. By clicking image name, it should open the particular image. I can access the images separately but how to display all the images as file explorer.
You might use ResourcePatternResolver:
#Controller
#RequestMapping("/assets")
public class AssetController {
#Autowired
private ResourcePatternResolver resolver;
#GetMapping("")
#ResponseBody
public String resources() throws IOException {
final String root = resolver.getResource("classpath:/static/assets").getURI().toString();
final Resource[] resources = resolver
.getResources("classpath:/static/assets/**/*.png");
final List<String> fileNames = Stream.of(resources)
.filter(Resource::isFile)
.map(r -> {
try {
return r.getURI().toString().replace(root, "");
} catch (final IOException e) {
throw new IOError(e);
}
})
.collect(Collectors.toList());
final StringBuilder html = new StringBuilder();
html.append("<html>");
html.append("<ul>");
for (final String fileName : fileNames) {
html.append("<li>");
html.append("" + fileName + "");
html.append("</li>");
}
html.append("</ul>");
html.append("</html>");
return html.toString();
}
}

FlatFileItemWriterBuilder-headerCallback() get number of rows written

Is it possible to get the total number of rows written from FlatFileItemWriter.headerCallback()?
I am a spring-batch nubee and I looked at putting count of lines into header of flat file and Spring Batch - Counting Processed Rows.
However I can't seem to implement the logic using the advice given there. It makes sense the writer count will only be available after the file is processed. However I am trying to get the row-count just before the file is officially written.
I tried to look for a hook like #AfterStep and grab the total rows, but I keep going in circles.
#Bean
#StepScope
public FlatFileItemWriter<MyFile> generateMyFileWriter(Long jobId,Date eventDate) {
String filePath = "C:\MYFILE\COMPLETED";
Resource file = new FileSystemResource(filePath);
DelimitedLineAggregator<MyFile> myFileLineAggregator = new DelimitedLineAggregator<>();
myFileLineAggregator.setDelimiter(",");
myFileLineAggregator.setFieldExtractor(getMyFileFieldExtractor());
return new FlatFileItemWriterBuilder<MyFile>()
.name("my-file-writer")
.resource(file)
.headerCallback(new MyFileHeaderWriter(file.getFilename()))
.lineAggregator(myFileLineAggregator)
.build();
}
private FieldExtractor<MyFile> getMyFileFieldExtractor() {
final String[] fieldNames = new String[]{
"typeRecord",
"idSystem"
};
return item -> {
BeanWrapperFieldExtractor<MyFile> extractor = new BeanWrapperFieldExtractor<>();
extractor.setNames(fieldNames);
return extractor.extract(item);
};
}
Notice I am using the MyFileHeaderWriter.java class(below) in the headerCallback(new MyFileHeaderWriter(file.getFilename())) (above). I am trying to initialize the value of qtyRecordsCreated below.
class MyFileHeaderWriter implements FlatFileHeaderCallback {
private final String header;
private String dtxCreated;
private String tmxCreated;
private String fileName;//15 byte file name private String qtyRecordsCreated;//number of rows in file including the header row
MyFileHeaderWriter(String sbfFileName) {
SimpleDateFormat dateCreated = new SimpleDateFormat("YYDDD");
SimpleDateFormat timeCreated = new SimpleDateFormat("HHMM");
Date now = new Date();
this.dtxCreated = dateCreated.format(now);
this.tmxCreated = timeCreated.format(now);
this.fileName = sbfFileName; this.qtyRecordsCreated="";
String[] headerValues = {dtxCreated,tmxCreated,fileName,qtyRecordsCreated};
this.header = String.join(",", headerValues);
}
#Override
public void writeHeader(Writer writer) throws IOException {
writer.write(header);
}
}
How can I get the number of rows in the header row?
Can the FlatFileFooterCallback be used to fetch the number of rows and then update the header with number of rows in the file afterwards?
You can achieve this in ItemProcessor, try this it work for me
public class EmployeeProcessor implements ItemProcessor<Employee, Employee> {
#Override
public Employee process(Employee employee) throws Exception {
return employee;
}
#AfterStep
public void afterStep(StepExecution stepExecution) {
ExecutionContext stepContext = stepExecution.getExecutionContext();
stepContext.put("count", stepExecution.getReadCount());
System.out.println("COUNT" + stepExecution.getReadCount());
}
}
And in you writer to get value
int count = stepContext.getInt("count");
Hope work for you

How to remove r-00000 extention from reducer output in mapreduce

I am able to rename my reducer output file correctly but r-00000 is still persisting .
I have used MultipleOutputs in my reducer class .
Here is details of the that .Not sure what am i missing or what extra i have to do?
public class MyReducer extends Reducer<NullWritable, Text, NullWritable, Text> {
private Logger logger = Logger.getLogger(MyReducer.class);
private MultipleOutputs<NullWritable, Text> multipleOutputs;
String strName = "";
public void setup(Context context) {
logger.info("Inside Reducer.");
multipleOutputs = new MultipleOutputs<NullWritable, Text>(context);
}
#Override
public void reduce(NullWritable Key, Iterable<Text> values, Context context)
throws IOException, InterruptedException {
for (Text value : values) {
final String valueStr = value.toString();
StringBuilder sb = new StringBuilder();
sb.append(strArrvalueStr[0] + "|!|");
multipleOutputs.write(NullWritable.get(), new Text(sb.toString()),strName);
}
}
public void cleanup(Context context) throws IOException,
InterruptedException {
multipleOutputs.close();
}
}
I was able to do it explicitly after my job finishes and thats ok for me.No delay in the job
if (b){
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HHmm");
Calendar cal = Calendar.getInstance();
String strDate=dateFormat.format(cal.getTime());
FileSystem hdfs = FileSystem.get(getConf());
FileStatus fs[] = hdfs.listStatus(new Path(args[1]));
if (fs != null){
for (FileStatus aFile : fs) {
if (!aFile.isDir()) {
hdfs.rename(aFile.getPath(), new Path(aFile.getPath().toString()+".txt"));
}
}
}
}
A more suitable approach to the problem would be changing the OutputFormat.
For eg :- If you are using TextOutputFormatClass, just get the source code of the TextOutputFormat class and modify the below method to get the proper filename (without r-00000). We need to then set the modified output format in the driver.
public synchronized static String getUniqueFile(TaskAttemptContext context, String name, String extension) {
/*TaskID taskId = context.getTaskAttemptID().getTaskID();
int partition = taskId.getId();*/
StringBuilder result = new StringBuilder();
result.append(name);
/*
* result.append('-');
* result.append(TaskID.getRepresentingCharacter(taskId.getTaskType()));
* result.append('-'); result.append(NUMBER_FORMAT.format(partition));
* result.append(extension);
*/
return result.toString();
}
So whatever name is passed through the multiple outputs, filename will be created according to it.

Distributed Cache Hadoop not retrieving the file content

I am getting some garbage like value instead of the data from the file I want to use as distributed cache.
The Job Configuration is as follows:
Configuration config5 = new Configuration();
JobConf conf5 = new JobConf(config5, Job5.class);
conf5.setJobName("Job5");
conf5.setOutputKeyClass(Text.class);
conf5.setOutputValueClass(Text.class);
conf5.setMapperClass(MapThree4c.class);
conf5.setReducerClass(ReduceThree5.class);
conf5.setInputFormat(TextInputFormat.class);
conf5.setOutputFormat(TextOutputFormat.class);
DistributedCache.addCacheFile(new URI("/home/users/mlakshm/ap1228"), conf5);
FileInputFormat.setInputPaths(conf5, new Path(other_args.get(5)));
FileOutputFormat.setOutputPath(conf5, new Path(other_args.get(6)));
JobClient.runJob(conf5);
In the Mapper, I have the following code:
public class MapThree4c extends MapReduceBase implements Mapper<LongWritable, Text,
Text, Text >{
private Set<String> prefixCandidates = new HashSet<String>();
Text a = new Text();
public void configure(JobConf conf5) {
Path[] dates = new Path[0];
try {
dates = DistributedCache.getLocalCacheFiles(conf5);
System.out.println("candidates: "+candidates);
String astr = dates.toString();
a = new Text(astr);
} catch (IOException ioe) {
System.err.println("Caught exception while getting cached files: " +
StringUtils.stringifyException(ioe));
}
}
public void map(LongWritable key, Text value, OutputCollector<Text, Text> output,
Reporter reporter) throws IOException {
String line = value.toString();
StringTokenizer st = new StringTokenizer(line);
st.nextToken();
String t = st.nextToken();
String uidi = st.nextToken();
String uidj = st.nextToken();
String check = null;
output.collect(new Text(line), a);
}
}
The output value, I am getting from this mapper is:[Lorg.apache.hadoop.fs.Path;#786c1a82
instead of the value from the distributed cache file.
That looks like what you get when you call toString() on an array and if you look at the javadocs for DistributedCache.getLocalCacheFiles(), that is what it returns. If you need to actually read the contents of the files in the cache, you can open/read them with the standard java APIs.
From your code:
Path[] dates = DistributedCache.getLocalCacheFiles(conf5);
Implies that:
String astr = dates.toString(); // is a pointer to the above array (ie.dates) which is what you see in the output as [Lorg.apache.hadoop.fs.Path;#786c1a82.
You need to do the following to see the actual paths:
for(Path cacheFile: dates){
output.collect(new Text(line), new Text(cacheFile.getName()));
}

Wicket serving images from File System

I am pretty new to Wicket and i have some difficulties with using resource references. I am using wicket 1.5.4 and have following problem: I store images on the file system. I have class ImageElement which holds part of the file path relative to configured rootFilePath (i.e dir1/dir2/img1.png). On the page I add Image as follows:
new Image("id",ImagesResourceReference.get(), pageParameters)
where page parameters includes image path parameter (path="/dir1/dir2/img1.png"). My questions are:
Is it the simplest way of serving images from the file system?
Is it ok to use ResourceReference with static method? or I should construct each time new ResourceReference? I saw that in previous version it was possible to use new ResourceReference(globalId), but it seems not to be the case anymore. If so what is the global resource reference for? So far as I understand resource reference is supposed to be factory for resources so it would be rather strange to create new factory for each resource request.
The last question is, how can i pass the path to the image in a better way so that i do not have to concatenate indexed parameters to build the path once respond method is invoked on ImageResource.
What would be the best scenario to get it working in efficient and simple way, i saw the example in 'Wicket in action', but this is meant for dynamic image generation from db and am not sure if it suites for my case
My implementation of ResourceReference which I mounted in Application under "/images" path, looks as follows:
public class ImagesResourceReference extends ResourceReference {
private static String rootFileDirectory;
private static ImagesResourceReference instance;
private ImagesResourceReference() {
super(ImagesResourceReference.class, "imagesResourcesReference");
}
public static ImagesResourceReference get() {
if(instance == null) {
if(StringUtils.isNotBlank(rootFileDirectory)) {
instance = new ImagesResourceReference();
} else {
throw new IllegalStateException("Parameter configuring root directory " +
"where images are saved is not set");
}
}
return instance;
}
public static void setRootFileDirectory(String rootFileDirectory) {
ImagesResourceReference.rootFileDirectory = rootFileDirectory;
}
private static final long serialVersionUID = 1L;
#Override
public IResource getResource() {
return new ImageResource(rootFileDirectory);
}
private static class ImageResource implements IResource {
private static final long serialVersionUID = 1L;
private final String rootFileDirectory;
public ImageResource(String rootFileDirectory) {
this.rootFileDirectory = rootFileDirectory;
}
#Override
public void respond(Attributes attributes) {
PageParameters parameters = attributes.getParameters();
List<String> indexedParams = getAllIndexedParameters(parameters);
if(!indexedParams.isEmpty() && isValidImagePath(indexedParams)) {
String pathToRequestedImage = getImagePath(indexedParams);
FileResourceStream fileResourceStream = new FileResourceStream(new File(pathToRequestedImage));
ResourceStreamResource resource = new ResourceStreamResource(fileResourceStream);
resource.respond(attributes);
}
}
private boolean isValidImagePath(List<String> indexedParams) {
String fileName = indexedParams.get(indexedParams.size() -1);
return !FilenameUtils.getExtension(fileName).isEmpty();
}
private List<String> getAllIndexedParameters(PageParameters parameters) {
int indexedparamCount = parameters.getIndexedCount();
List<String> indexedParameters = new ArrayList<String>();
for(int i=0; i<indexedparamCount ;i++) {
indexedParameters.add(parameters.get(i).toString());
}
return indexedParameters;
}
private String getImagePath(List<String> indexedParams) {
return rootFileDirectory + File.separator + StringUtils.join(indexedParams, File.separator);
}
}
Any help and advices appreciated! Thanks in advance.
You could use it as a shared resource:
public class WicketApplication extends WebApplication {
#Override
public Class<HomePage> getHomePage() {
return HomePage.class;
}
#Override
public void init() {
super.init();
getSharedResources().add("downloads", new FolderContentResource(new File("C:\\Users\\ronald.tetsuo\\Downloads")));
mountResource("downloads", new SharedResourceReference("downloads"));
}
static class FolderContentResource implements IResource {
private final File rootFolder;
public FolderContentResource(File rootFolder) {
this.rootFolder = rootFolder;
}
public void respond(Attributes attributes) {
PageParameters parameters = attributes.getParameters();
String fileName = parameters.get(0).toString();
File file = new File(rootFolder, fileName);
FileResourceStream fileResourceStream = new FileResourceStream(file);
ResourceStreamResource resource = new ResourceStreamResource(fileResourceStream);
resource.respond(attributes);
}
}
}
You can still use ResourceReferences with global IDs. You just have to use a SharedResourceReference. This is probably better, too.
add(new Image("image", new SharedResourceReference("mySharedResourceRef", parameters));
I would try to avoid building paths from URL parameters. This can easily end up in security leaks.

Resources