Logback Filter - limitative error log message - gradle

Still newbie on springboot, gradle and logback, I need help! I am trying to create my own logback filter.
Main goal is to allow my logger to only send a single log message if some logs with same error message are send by application.
To do it, I just create a basic gradle project to test, with 2 classes.
build.gradle
logback.xml
project_explorer_eclipse
I - My main class which logs some errors
package com.example.CDOP221logback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
#SpringBootApplication
public class Cdop221LogbackApplication {
private final static Logger log = LoggerFactory.getLogger("com.example.CDOP221log4j");
public static void main(String[] args) {
SpringApplication.run(Cdop221LogbackApplication.class, args);
LoggerContext context = (LoggerContext)LoggerFactory.getILoggerFactory();
context.reset();
JoranConfigurator config = new JoranConfigurator();
config.setContext(context);
try {
config.doConfigure("/home/mehdi/eclipse-workspace/CDOP-221-logback/logback.xml");
} catch (JoranException e) {
e.printStackTrace();
}
test();
}
private static void test() {
log.debug("Application Cdop221 with LOGBACK logger launch succesful");
log.error("ERROR_1");
log.error("ERROR_1");
log.error("ERROR_1");
log.error("ERROR_1");
log.error("ERROR_1");
log.error("ERROR_1");
log.error("ERROR_1");
log.error("ERROR_1");
int i = 0;
while(i < 100) {
log.error("ERROR_2");
i++;
}
}
}
II - My own appender which has to limit number of log if some entries are the same
package com.logback;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* Improved {#link ch.qos.logback.classic.turbo.DuplicateMessageFilter} with a timeout feature added and time window error stacking #buzzwords
* Indeed if there's some error logs that are the same (same hashcode) they are stacked and sent after {#link DuplicateErrorLogFilter#cacheTimeoutInSec}
*/
public class DuplicateErrorLogFilter extends Filter<ILoggingEvent> {
/**
* Repetition number MDC property
*/
private static final String REP_NB = "repNb";
/**
* The default cache size.
*/
private static final int DEFAULT_CACHE_SIZE = 100;
/**
* The default cache timeout in seconds
*/
private static final int DEFAULT_CACHE_TIMEOUT_IN_SEC = 300;
private String smtpAppenderName;
private int cacheSize = DEFAULT_CACHE_SIZE;
private int cacheTimeoutInSec = DEFAULT_CACHE_TIMEOUT_IN_SEC;
private Map<Integer, FoldingTask> tasks = new ConcurrentHashMap<>(cacheSize);
/**
* Timer that will expire folding tasks
*/
private Timer foldingTimer = new Timer("folding-timer", false);
private final class FoldingTask extends TimerTask {
private Integer key;
private ILoggingEvent lastEvent;
private int foldingCount;
#Override
public void run() {
// Remove current task
tasks.remove(key);
// And send the event to SMTP appender
sendEvent(lastEvent, foldingCount);
}
}
/**
* Append an event that has been folded
*
* #param event the last seen event of this kind
* #param foldingCount how many events were folded
*/
protected void sendEvent(ILoggingEvent event, int foldingCount) {
if (event != null) {
if (foldingCount > 1) {
// Do that to prevent UnsupportedOp from EmptyMap
if (event.getMDCPropertyMap().isEmpty() && event instanceof LoggingEvent) {
((LoggingEvent) event).setMDCPropertyMap(new HashMap<>());
}
event.getMDCPropertyMap().put(REP_NB, "[" + foldingCount + "x]");
}
((Logger) (LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME))).getAppender(smtpAppenderName).doAppend(event);
}
}
public void setSmtpAppenderName(String smtpAppenderName) {
this.smtpAppenderName = smtpAppenderName;
}
public void setCacheSize(int cacheSize) {
this.cacheSize = cacheSize;
}
public void setCacheTimeoutInSec(int cacheTimeoutInSec) {
this.cacheTimeoutInSec = cacheTimeoutInSec;
}
#Override
public void start() {
super.start();
}
#Override
public void stop() {
tasks.clear();
tasks = null;
super.stop();
}
#Override
public FilterReply decide(ILoggingEvent event) {
if (!event.getLevel().isGreaterOrEqual(Level.ERROR)) {
return FilterReply.NEUTRAL;
}
Integer key = eventHashCode(event);
FoldingTask task = tasks.get(key);
if (task == null) {
// First time we encounter this event
task = new FoldingTask();
task.key = key;
// lastEvent will be set at the first folded event
tasks.put(key, task);
// Arm timer for this task
foldingTimer.schedule(task, TimeUnit.SECONDS.toMillis(cacheTimeoutInSec));
// And log this event
return FilterReply.NEUTRAL;
} else {
// Fold this event
task.lastEvent = event;
task.foldingCount++;
return FilterReply.DENY;
}
}
/**
* Compute a signature for an event
*/
private int eventHashCode(ILoggingEvent event) {
IThrowableProxy thrInfo = event.getThrowableProxy();
if (thrInfo == null || ArrayUtils.isEmpty(thrInfo.getStackTraceElementProxyArray())) {
// No stacktrace
String message = event.getFormattedMessage();
return message.hashCode();
}
StackTraceElementProxy[] stack = thrInfo.getStackTraceElementProxyArray();
int hashCode = 0;
for (StackTraceElementProxy str : stack) {
hashCode = 31 * hashCode + str.hashCode();
}
return hashCode;
}
}
So, when I run my code, it doesn't work actually... But I am not really able to identify if it's because of a bad configuration (I am beginner with logback library) or if my code sucks?...
Thank you in advance for your help
result code (doesn't work correctly)

A part is missing from the logback config file (logback.xml) where you connect your filter (DuplicateErrorLogFilter) and logback:
<filter class="com.logback.DuplicateErrorLogFilter"/>
for additional info on how to use filter: https://logback.qos.ch/manual/filters.html

Related

Issues running/debugging mapbox android code examples

I am trying to get started with mapbox android and can't get any of the example projects to work.
My problem is with the imports
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import com.mapbox.mapboxandroiddemo.R;
I get I "cannot resolve symbol annotation", "cannot resolve symbol v7" and "cannot resolve symbol mapboxandroiddemo".
I feel like this is some android problem that I am just not understanding correctly so if anyone has some insight that would be amazing. I have tried taking out some code and using the recommended bug fixes but all that has done is break my project.
Here is the entire MainActivity.java file
package com.example.mapboxtut;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatActivity;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.FeatureCollection;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxandroiddemo.R;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import com.mapbox.mapboxsdk.utils.BitmapUtils;
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textAllowOverlap;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textField;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textIgnorePlacement;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textOffset;
/**
* Use the {#link MapView#addOnStyleImageMissingListener(MapView.OnStyleImageMissingListener)}
* to handle the situation where a SymbolLayer tries using a missing image as an icon. If an icon-image
* cannot be found in a map style, a custom image can be provided to the map via
* the listener.
*/
public class MissingIconActivity extends AppCompatActivity {
private static final String ICON_SOURCE_ID = "ICON_SOURCE_ID";
private static final String ICON_LAYER_ID = "ICON_LAYER_ID";
private static final String PROFILE_NAME = "PROFILE_NAME";
private static final String CARLOS = "Carlos";
private static final String ANTONY = "Antony";
private static final String MARIA = "Maria";
private static final String LUCIANA = "Luciana";
private MapView mapView;
private MapboxMap mapboxMap;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Mapbox access token is configured here. This needs to be called either in your application
// object or in the same activity which contains the mapview.
Mapbox.getInstance(this, getString(R.string.access_token));
// This contains the MapView in XML and needs to be called after the access token is configured.
setContentView(R.layout.activity_styles_missing_icon);
mapView = findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(#NonNull final MapboxMap mapboxMap) {
// Add Features which represent the location of each profile photo SymbolLayer icon
Feature carlosFeature = Feature.fromGeometry(Point.fromLngLat(-7.9760742,
41.2778064));
carlosFeature.addStringProperty(PROFILE_NAME, CARLOS);
Feature antonyFeature = Feature.fromGeometry(Point.fromLngLat(-8.0639648,
37.5445773));
antonyFeature.addStringProperty(PROFILE_NAME, ANTONY);
Feature mariaFeature = Feature.fromGeometry(Point.fromLngLat(-9.1845703,
38.9764924));
mariaFeature.addStringProperty(PROFILE_NAME, MARIA);
Feature lucianaFeature = Feature.fromGeometry(Point.fromLngLat(-7.5146484,
40.2459915));
lucianaFeature.addStringProperty(PROFILE_NAME, LUCIANA);
// Use a URL to build and add a Style object to the map. Then add a source to the Style.
mapboxMap.setStyle(
new Style.Builder().fromUrl(Style.LIGHT)
.withSource(new GeoJsonSource(ICON_SOURCE_ID,
FeatureCollection.fromFeatures(new Feature[] {
carlosFeature,
antonyFeature,
mariaFeature,
lucianaFeature}))),
new Style.OnStyleLoaded() {
#Override
public void onStyleLoaded(#NonNull Style style) {
MissingIconActivity.this.mapboxMap = mapboxMap;
// Add a SymbolLayer to the style. iconImage is set to a value that will
// be used later in the addOnStyleImageMissingListener below
style.addLayer(new SymbolLayer(ICON_LAYER_ID, ICON_SOURCE_ID).withProperties(
iconImage(get(PROFILE_NAME)),
iconIgnorePlacement(true),
iconAllowOverlap(true),
textField(get(PROFILE_NAME)),
textIgnorePlacement(true),
textAllowOverlap(true),
textOffset(new Float[] {0f, 2f})
));
}
});
}
});
// Use the listener to match the id with the appropriate person. The correct profile photo is
// given to the map during "runtime".
mapView.addOnStyleImageMissingListener(new MapView.OnStyleImageMissingListener() {
#Override
public void onStyleImageMissing(#NonNull String id) {
switch (id) {
case CARLOS:
addImage(id, R.drawable.carlos);
break;
case ANTONY:
addImage(id, R.drawable.antony);
break;
case MARIA:
addImage(id, R.drawable.maria);
break;
case LUCIANA:
addImage(id, R.drawable.luciana);
break;
default:
addImage(id, R.drawable.carlos);
break;
}
}
});
}
private void addImage(String id, int drawableImage) {
Style style = mapboxMap.getStyle();
if (style != null) {
style.addImageAsync(id, BitmapUtils.getBitmapFromDrawable(
getResources().getDrawable(drawableImage)));
}
}
// Add the mapView lifecycle to the activity's lifecycle methods
#Override
public void onResume() {
super.onResume();
mapView.onResume();
}
#Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
#Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
#Override
public void onPause() {
super.onPause();
mapView.onPause();
}
#Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
#Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
}

Deleting Data From Database Through JavaFX GUI

What I want to be able to do is load my database through a table view select an item and have it deleted into the database. I do not have users entering the id of a particular song so it makes it harder for me to accomplish this. I have the GUI set up and all the code that I have so far.
GUI CODE:
SongContent Code:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package playmymusic;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* #author man
*/
public class SongContent
{
private final StringProperty artist;
private final StringProperty title;
private final StringProperty genre;
private final IntegerProperty id;
public SongContent(int id, String artist, String title, String genre)
{
this.artist = new SimpleStringProperty(artist);
this.title = new SimpleStringProperty(title);
this.genre = new SimpleStringProperty(genre);
this.id = new SimpleIntegerProperty(id);
}
public Integer getId()
{
return id.get();
}
public void setID(int paramId)
{
id.set(paramId);
}
public String getArtist()
{
return artist.get();
}
public void setArtist(String paramArtist)
{
artist.set(paramArtist);
}
public String getTitle()
{
return title.get();
}
public void setTitle(String paramTitle)
{
title.set(paramTitle);
}
public String getGenre()
{
return genre.get();
}
public void setGenre(String paramGenre)
{
genre.set(paramGenre);
}
public StringProperty artistProperty(){return artist;}
public StringProperty titleProperty(){return title;}
public StringProperty genreProperty(){return genre;}
public IntegerProperty idProperty() { return id;}
}
Controller Code:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package playmymusic;
import java.io.IOException;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ResourceBundle;
import javafx.beans.property.IntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javax.swing.JOptionPane;
import org.apache.derby.jdbc.ClientDriver;
/**
*
* #author man
*/
public class FXMLDocumentController implements Initializable {
public LoginModel loginModel = new LoginModel();
#FXML
private TextField txtUsername;
#FXML
private TextField txtPassword;
#FXML
private TextField txtArtist;
#FXML
private TextField fxTitle;
#FXML
private TextField fxGenre;
#FXML
private TableView<SongContent> tableView;
#FXML
private TableColumn<SongContent, Integer> id;
#FXML
private TableColumn<SongContent, String> artist;
#FXML
private TableColumn<SongContent, String> title;
#FXML
private TableColumn<SongContent, String> genre;
private ObservableList<SongContent> data;
#FXML
private void Login(ActionEvent event) throws SQLException {
try {
if(loginModel.isLogin(txtUsername.getText(), txtPassword.getText()))
{
Stage primaryStage = new Stage();
FXMLLoader loader = new FXMLLoader();
Pane root = loader.load(getClass().getResource("PopUpWindow.fxml").openStream());
Scene scene = new Scene(root, 785, 809);
primaryStage.setScene(scene);
primaryStage.show();
PlayMyMusic.primaryStage.close();
}else
{
System.out.println("WOOPS");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#FXML
private void songs(ActionEvent e) throws SQLException, ClassNotFoundException
{
loginModel.insertSongs(txtArtist.getText(), fxTitle.getText(), fxGenre.getText());
try
{
int i = 1;
Connection conn = DriverManager.getConnection("jdbc:derby://localhost:1527/PlayMyMusicDB;user=test;password=test");
data = FXCollections.observableArrayList();
ResultSet rs = conn.createStatement().executeQuery("select * from Song");
while(rs.next())
{
data.add(new SongContent(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4)));
i++;
}
}catch(SQLException ex) {
System.err.println("Error" + ex);
}
id.setCellValueFactory(new PropertyValueFactory<>("id"));
artist.setCellValueFactory(new PropertyValueFactory<>("artist"));
title.setCellValueFactory(new PropertyValueFactory<>("title"));
genre.setCellValueFactory(new PropertyValueFactory<>("genre"));
tableView.setItems(null);
tableView.setItems(data);
txtArtist.clear();
fxTitle.clear();
fxGenre.clear();
}
#FXML
public void deleteItems(ActionEvent e) throws SQLException, ClassNotFoundException
{
Connection c = DriverManager.getConnection("jdbc:derby://localhost:1527/PlayMyMusicDB;user=test;password=test");
int action = JOptionPane.showConfirmDialog(null, "Are you sure you want to delete this item?");
if(action == 0)
{
try
{
IntegerProperty i = SongContent.idProperty();
ResultSet rs = c.createStatement().executeQuery("DELETE FROM Song where i = " + i);
}catch(Exception e1)
{
e1.printStackTrace();
}
}
}
#Override
public void initialize(URL url, ResourceBundle rb)
{
}
}
`
Any explination of why this could not be deleting my data? I would also love it if someone explained to me a strategy of resetting the SongNumberID every time the GUI opened and closed. But, the main goal for me is to figure out how to delete songs.
Thanks so much
-Aaron
The result of calling toString on a SimpleIntegerProperty is something like IntegerProperty [value: 10]. You should use the value, not the IntegerProperty. Furthermore it's better to use a PreparedStatement to create the query. Also you should get the selected item from the table instead of trying to reference a instance method as if it was static:
SongContent song = tableView.getSelectionModel().getSelectedItem();
if (song != null) {
// there is a selection -> delete
...
PreparedStatement statement = c.prepareStatement("DELETE FROM Song WHERE i = ?");
statement.setInt(1, song.getId());
statement.executeUpdate();
...
}
Furthermore you should make sure i is actually the column name of the id column (and not id).

Schedule a task with Cron which allows dynamic update

I use sprint boot 1.3, spring 4.2
In this class
#Service
public class PaymentServiceImpl implements PaymentService {
....
#Transactional
#Override
public void processPayment() {
List<Payment> payments = paymentRepository.findDuePayment();
processCreditCardPayment(payments);
}
}
I would like to call processPayment every x moment.
This x moment is set in a database.
The user can modify it.
So i think i can't use anotation.
I started to this this
#EntityScan(basePackageClasses = {MyApp.class, Jsr310JpaConverters.class})
#SpringBootApplication
#EnableCaching
#EnableScheduling
public class MyApp {
#Autowired
private DefaultConfigService defaultConfigService;
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
#Bean
public TaskScheduler poolScheduler() {
SimpleAsyncTaskExecutor taskScheduler = new SimpleAsyncTaskExecutor();
DefaultConfigDto defaultConfigDto = defaultConfigService.getByFieldName("payment-cron-task");
String cronTabExpression = "0 0 4 * * ?";
if (defaultConfigDto != null && !defaultConfigDto.getFieldValue().isEmpty()) {
cronTabExpression = "0 0 4 * * ?";
}
appContext.getBean("scheduler");
taskScheduler.schedule(task, new CronTrigger(cronTabExpression));
return scheduler;
}
Maybe it's not the good way.
Any suggestion?
Don't know if to get my context if i need to create a property like
#Autowired
ConfigurableApplicationContext context;
and after in the main
public static void main(String[] args) {
context = SpringApplication.run(MyApp.class, args);
}
Looking at the question seems like you want to update the scheduler, without restart.
The code you have shared only ensures the config is picked from DB, but it will not refresh without application restart.
The following code will use the default scheduler available in the spring context and dynamically compute the next execution time based on the available cron setting in the DB:
Here is the sample code:
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
#SpringBootApplication
#EnableScheduling
public class Perses implements SchedulingConfigurer {
private static final Logger log = LoggerFactory.getLogger(Perses.class);
#Autowired
private DefaultConfigService defaultConfigService;
#Autowired
private PaymentService paymentService;
public static void main(String[] args) {
SpringApplication.run(Perses.class, args);
}
private String cronConfig() {
String cronTabExpression = "*/5 * * * * *";
if (defaultConfigDto != null && !defaultConfigDto.getFieldValue().isEmpty()) {
cronTabExpression = "0 0 4 * * ?";
}
return cronTabExpression;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(new Runnable() {
#Override
public void run() {
paymentService.processPayment();
}
}, new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
String cron = cronConfig();
log.info(cron);
CronTrigger trigger = new CronTrigger(cron);
Date nextExec = trigger.nextExecutionTime(triggerContext);
return nextExec;
}
});
}
}
Just if someone still having this issue a better solution getting value from database whenever you want without many changes would be run cron every minute and get mod between current minute versus a configurated value delta from database, if this mod is equals to 0 means it has to run like if it is a mathematical multiple, so if you want it to run every 5 minutes for example delta should be 5.
A sample:
#Scheduled(cron = "0 */1 * * * *") //fire every minute
public void perform() {
//running
Integer delta = 5;//get this value from databse
Integer minutes = getField(Calendar.MINUTE)//calendar for java 7;
Boolean toRun = true;//you can also get this one from database to make it active or disabled
toRun = toRun && (minutes % delta == 0);
if (toRun && (!isRunning)) {
isRunning = true;
try {
//do your logic here
} catch (Exception e) { }
isRunning = false;
}
}
public Integer getField(int field) {
Calendar now = Calendar.getInstance();
if(field == Calendar.MONTH) {
return now.get(field)+ 1; // Note: zero based!
}else {
return now.get(field);
}
}
Hope this help :D

Spring Batch to read multiple files with same extension

I have custom reader to read data from CSV File.
package org.kp.oppr.remediation.batch.csv;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.remediation.batch.csv.FlatFileItemReaderNewLine;
import org.remediation.batch.model.RawItem;
import org.remediation.batch.model.RawItemLineMapper;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.annotation.BeforeStep;
import org.springframework.batch.item.file.LineCallbackHandler;
import org.springframework.batch.item.file.LineMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.batch.item.file.transform.LineTokenizer;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
public class RawItemCsvReader extends MultiResourceItemReader<RawItem>
implements StepExecutionListener, LineCallbackHandler,
FieldSetMapper<RawItem> {
static final Logger LOGGER = LogManager.getLogger(RawItemCsvReader.class);
final private String COLUMN_NAMES_KEY = "COLUMNS_NAMES_KEY";
private StepExecution stepExecution;
private DefaultLineMapper<RawItem> lineMapper;
private String[] columnNames;
private Resource[] resources;
// = DelimitedLineTokenizer.DELIMITER_COMMA;
private char quoteCharacter = DelimitedLineTokenizer.DEFAULT_QUOTE_CHARACTER;
private String delimiter;
public RawItemCsvReader() {
setLinesToSkip(0);
setSkippedLinesCallback(this);
}
#Override
public void afterPropertiesSet() {
// not in constructor to ensure we invoke the override
final DefaultLineMapper<RawItem> lineMapper = new RawItemLineMapper();
setLineMapper(lineMapper);
}
/**
* Satisfies {#link LineCallbackHandler} contract and and Acts as the
* {#code skippedLinesCallback}.
*
* #param line
*/
#Override
public void handleLine(String line) {
getLineMapper().setLineTokenizer(getTokenizer());
getLineMapper().setFieldSetMapper(this);
}
private LineTokenizer getTokenizer() {
// this.columnNames = line.split(delimiter);
DelimitedLineTokenizer lineTokenizer = new DelimitedLineTokenizer();
lineTokenizer.setQuoteCharacter(quoteCharacter);
lineTokenizer.setDelimiter(delimiter);
lineTokenizer.setStrict(true);
lineTokenizer.setNames(columnNames);
addColumnNames();
return lineTokenizer;
}
private void addColumnNames() {
stepExecution.getExecutionContext().put(COLUMN_NAMES_KEY, columnNames);
}
#Override
public void setResources(Resource[] resources) {
this.resources = resources;
super.setResources(resources);
}
/**
* Provides acces to an otherwise hidden field in parent class. We need this
* because we have to reconfigure the {#link LineMapper} based on file
* contents.
*
* #param lineMapper
*/
#Override
public void setLineMapper(LineMapper<RawItem> lineMapper) {
if (!(lineMapper instanceof DefaultLineMapper)) {
throw new IllegalArgumentException(
"Must specify a DefaultLineMapper");
}
this.lineMapper = (DefaultLineMapper) lineMapper;
super.setLineMapper(lineMapper);
}
private DefaultLineMapper getLineMapper() {
return this.lineMapper;
}
/**
* Satisfies {#link FieldSetMapper} contract.
*
* #param fs
* #return
* #throws BindException
*/
#Override
public RawItem mapFieldSet(FieldSet fs) throws BindException {
if (fs == null) {
return null;
}
Map<String, String> record = new LinkedHashMap<String, String>();
for (String columnName : this.columnNames) {
record.put(columnName,
StringUtils.trimToNull(fs.readString(columnName)));
}
RawItem item = new RawItem();
item.setResource(resources);
item.setRecord(record);
return item;
}
#BeforeStep
public void saveStepExecution(StepExecution stepExecution) {
this.stepExecution = stepExecution;
}
#Override
public void beforeStep(StepExecution stepExecution) {
//LOGGER.info("Start Raw Read Step for " + itemResource.getFilename());
}
#Override
public ExitStatus afterStep(StepExecution stepExecution) {
LOGGER.info("End Raw Read Step for lines read: " + stepExecution.getReadCount()
+ " lines skipped: " + stepExecution.getReadSkipCount());
/*
LOGGER.info("End Raw Read Step for " + itemResource.getFilename()
+ " lines read: " + stepExecution.getReadCount()
+ " lines skipped: " + stepExecution.getReadSkipCount());
*/
return ExitStatus.COMPLETED;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
public void setQuoteCharacter(char quoteCharacter) {
this.quoteCharacter = quoteCharacter;
}
public String[] getColumnNames() {
return columnNames;
}
public void setColumnNames(String[] columnNames) {
this.columnNames = columnNames;
}
public String getDelimiter() {
return delimiter;
}
}
I want to use MultiResourceItemReader along with this class to read multiple files with the same extension. I am using the Spring MultiResourceItemReader to do the job. I need to know how to configure private ResourceAwareItemReaderItemStream delegate; instance for this class
package org.kp.oppr.remediation.batch.csv;
import java.util.Arrays;
import java.util.Comparator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.item.file.LineCallbackHandler;
import org.springframework.batch.item.file.LineMapper;
import org.springframework.batch.item.file.ResourceAwareItemReaderItemStream;
import org.springframework.batch.item.util.ExecutionContextUserSupport;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
public class MultiResourceItemReader <T> implements ItemReader<T>, ItemStream, InitializingBean,ResourceAwareItemReaderItemStream<T> {
static final Logger LOGGER = LogManager
.getLogger(MultipleFlatFileItemReaderNewLine.class);
private final ExecutionContextUserSupport executionContextUserSupport = new ExecutionContextUserSupport();
private ResourceAwareItemReaderItemStream<? extends T> delegate;
private Resource[] resources;
private MultiResourceIndex index = new MultiResourceIndex();
private boolean saveState = true;
// signals there are no resources to read -> just return null on first read
private boolean noInput;
private LineMapper<T> lineMapper;
private int linesToSkip = 0;
private LineCallbackHandler skippedLinesCallback;
private Comparator<Resource> comparator = new Comparator<Resource>() {
/**
* Compares resource filenames.
*/
public int compare(Resource r1, Resource r2) {
return r1.getFilename().compareTo(r2.getFilename());
}
};
public MultiResourceItemReader() {
executionContextUserSupport.setName(ClassUtils.getShortName(MultiResourceItemReader.class));
}
/**
* #param skippedLinesCallback
* will be called for each one of the initial skipped lines
* before any items are read.
*/
public void setSkippedLinesCallback(LineCallbackHandler skippedLinesCallback) {
this.skippedLinesCallback = skippedLinesCallback;
}
/**
* Public setter for the number of lines to skip at the start of a file. Can
* be used if the file contains a header without useful (column name)
* information, and without a comment delimiter at the beginning of the
* lines.
*
* #param linesToSkip
* the number of lines to skip
*/
public void setLinesToSkip(int linesToSkip) {
this.linesToSkip = linesToSkip;
}
/**
* Setter for line mapper. This property is required to be set.
*
* #param lineMapper
* maps line to item
*/
public void setLineMapper(LineMapper<T> lineMapper) {
this.lineMapper = lineMapper;
}
/**
* Reads the next item, jumping to next resource if necessary.
*/
public T read() throws Exception, UnexpectedInputException, ParseException {
if (noInput) {
return null;
}
T item;
item = readNextItem();
index.incrementItemCount();
return item;
}
/**
* Use the delegate to read the next item, jump to next resource if current
* one is exhausted. Items are appended to the buffer.
* #return next item from input
*/
private T readNextItem() throws Exception {
T item = delegate.read();
while (item == null) {
index.incrementResourceCount();
if (index.currentResource >= resources.length) {
return null;
}
delegate.close();
delegate.setResource(resources[index.currentResource]);
delegate.open(new ExecutionContext());
item = delegate.read();
}
return item;
}
/**
* Close the {#link #setDelegate(ResourceAwareItemReaderItemStream)} reader
* and reset instance variable values.
*/
public void close() throws ItemStreamException {
index = new MultiResourceIndex();
delegate.close();
noInput = false;
}
/**
* Figure out which resource to start with in case of restart, open the
* delegate and restore delegate's position in the resource.
*/
public void open(ExecutionContext executionContext) throws ItemStreamException {
Assert.notNull(resources, "Resources must be set");
noInput = false;
if (resources.length == 0) {
LOGGER.warn("No resources to read");
noInput = true;
return;
}
Arrays.sort(resources, comparator);
for(int i =0; i < resources.length; i++)
{
LOGGER.info("Resources after Sorting" + resources[i]);
}
index.open(executionContext);
delegate.setResource(resources[index.currentResource]);
delegate.open(new ExecutionContext());
try {
for (int i = 0; i < index.currentItem; i++) {
delegate.read();
}
}
catch (Exception e) {
throw new ItemStreamException("Could not restore position on restart", e);
}
}
/**
* Store the current resource index and position in the resource.
*/
public void update(ExecutionContext executionContext) throws ItemStreamException {
if (saveState) {
index.update(executionContext);
}
}
/**
* #param delegate reads items from single {#link Resource}.
*/
public void setDelegate(ResourceAwareItemReaderItemStream<? extends T> delegate) {
this.delegate = delegate;
}
/**
* Set the boolean indicating whether or not state should be saved in the
* provided {#link ExecutionContext} during the {#link ItemStream} call to
* update.
*
* #param saveState
*/
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
/**
* #param comparator used to order the injected resources, by default
* compares {#link Resource#getFilename()} values.
*/
public void setComparator(Comparator<Resource> comparator) {
this.comparator = comparator;
}
/**
* #param resources input resources
*/
public void setResources(Resource[] resources) {
this.resources = resources;
}
/**
* Facilitates keeping track of the position within multi-resource input.
*/
private class MultiResourceIndex {
private static final String RESOURCE_KEY = "resourceIndex";
private static final String ITEM_KEY = "itemIndex";
private int currentResource = 0;
private int markedResource = 0;
private int currentItem = 0;
private int markedItem = 0;
public void incrementItemCount() {
currentItem++;
}
public void incrementResourceCount() {
currentResource++;
currentItem = 0;
}
public void mark() {
markedResource = currentResource;
markedItem = currentItem;
}
public void reset() {
currentResource = markedResource;
currentItem = markedItem;
}
public void open(ExecutionContext ctx) {
if (ctx.containsKey(executionContextUserSupport.getKey(RESOURCE_KEY))) {
currentResource = ctx.getInt(executionContextUserSupport.getKey(RESOURCE_KEY));
}
if (ctx.containsKey(executionContextUserSupport.getKey(ITEM_KEY))) {
currentItem = ctx.getInt(executionContextUserSupport.getKey(ITEM_KEY));
}
}
public void update(ExecutionContext ctx) {
ctx.putInt(executionContextUserSupport.getKey(RESOURCE_KEY), index.currentResource);
ctx.putInt(executionContextUserSupport.getKey(ITEM_KEY), index.currentItem);
}
}
#Override
public void afterPropertiesSet() throws Exception {
// TODO Auto-generated method stub
}
#Override
public void setResource(Resource resource) {
// TODO Auto-generated method stub
}
}
Configuration Files for Spring is :
<batch:step id="readFromCSVFileAndUploadToDB" next="stepMovePdwFile">
<batch:tasklet transaction-manager="transactionManager">
<batch:chunk reader="multiResourceReader" writer="rawItemDatabaseWriter"
commit-interval="500" skip-policy="pdwUploadSkipPolicy" />
</batch:tasklet>
</batch:step>
<bean id="multiResourceReader"
class="org.springframework.batch.item.file.MultiResourceItemReader" scope="step">
<property name="resource" value="file:#{jobParameters[filePath]}/*.dat" />
<property name="delegate" ref="rawItemCsvReader"></property>
</bean>
<bean id="rawItemCsvReader" class="org.kp.oppr.remediation.batch.csv.RawItemCsvReader"
scope="step">
<property name="resources" value="file:#{jobParameters[filePath]}/*.dat" />
<property name="columnNames" value="${columnNames}" />
<property name="delimiter" value="${delimiter}" />
</bean>
Use a standard FlatFileItemReader (properly configured via XML) instead of your RawItemCsvReader as delegate.
This solution will answer your question because FlatFileItemReader implements AbstractItemStreamItemReader.
Remember: SB is heavly based on delegation; write a class like your reader is rarely requested.

Trying to send data from Android Tablet to Android Wear device and losing

I could really use some help. I'm having trouble getting my Android Wear device to recognize data changes made by my activity that runs on my Android tablet. I'm relatively new to Android and very new to the Wear APIs so I might be missing something completely obvious or trivial without realizing. I've pieced together a project from a couple of examples from the Android Wear doc pages and from the TwoToasters example on github. I just want to get data communication set up between the devices so that I can then edit the code in order to display images from my tablet on my Wear device. Ultimately, I want to be able to start/stop a slideshow on my Wear device from my tablet but I should be able to get there on my own once I get the data communication protocol set up and working so I'm really just looking for help with that. Here is my code so far:
Mobile:
package com.example.administrator.moto360displaycontrol;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import android.widget.ToggleButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class MainActivity extends Activity {
private static final long CONNECTION_TIME_OUT_MS = 100;
private static final String ON_MESSAGE = "On!";
private static final String OFF_MESSAGE = "Off!";
private String message = null;
int count = 0;
private GoogleApiClient client;
private String nodeId;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initApi();
setupWidgets();
}
/**
* Initializes the GoogleApiClient and gets the Node ID of the connected device.
*/
private void initApi() {
client = getGoogleApiClient(this);
retrieveDeviceNode();
}
/**
* Returns a GoogleApiClient that can access the Wear API.
* #param context
* #return A GoogleApiClient that can make calls to the Wear API
*/
private GoogleApiClient getGoogleApiClient(Context context) {
return new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.build();
}
/**
* Connects to the GoogleApiClient and retrieves the connected device's Node ID. If there are
* multiple connected devices, the first Node ID is returned.
*/
private void retrieveDeviceNode() {
new Thread(new Runnable() {
#Override
public void run() {
client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
NodeApi.GetConnectedNodesResult result =
Wearable.NodeApi.getConnectedNodes(client).await();
List<Node> nodes = result.getNodes();
if (nodes.size() > 0) {
nodeId = nodes.get(0).getId();
}
client.disconnect();
}
}).start();
}
/**
* Sets up the button for handling click events.`
*/
private void setupWidgets() {
findViewById(R.id.toggleButton).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
Asset asset = createAssetFromBitmap(bitmap);
PutDataRequest request = PutDataRequest.create("/image");
request.putAsset("profileImage", asset);
Wearable.DataApi.putDataItem(client, request);
showToast("SentData");
}
});
}
private static Asset createAssetFromBitmap(Bitmap bitmap) {
final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
return Asset.createFromBytes(byteStream.toByteArray());
}
public void showToast(String string) {
Toast.makeText(this, string, Toast.LENGTH_LONG).show();
}
}
Wear:
package com.example.administrator.moto360displaycontrol;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import android.widget.Toast;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class MainActivity extends Activity implements
DataApi.DataListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener{
private TextView mTextView;
private static final long CONNECTION_TIME_OUT_MS = 100;
private static final String ON_MESSAGE = "On!";
private static final String OFF_MESSAGE = "Off!";
private static final String TAG = "Moto360DisplayControl";
private GoogleApiClient client;
private String nodeId;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initApi();
}
private void initApi() {
client = getGoogleApiClient(this);
retrieveDeviceNode();
}
private GoogleApiClient getGoogleApiClient(Context context) {
return new GoogleApiClient.Builder(context)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
private void retrieveDeviceNode() {
new Thread(new Runnable() {
#Override
public void run() {
client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
NodeApi.GetConnectedNodesResult result =
Wearable.NodeApi.getConnectedNodes(client).await();
List<Node> nodes = result.getNodes();
if (nodes.size() > 0) {
nodeId = nodes.get(0).getId();
}
client.disconnect();
}
}).start();
}
#Override
protected void onStart() {
super.onStart();
client.connect();
}
#Override
public void onConnected(Bundle connectionHint) {
Wearable.DataApi.addListener(client, this);
Toast.makeText(this, "AddedListener!", Toast.LENGTH_LONG).show();
}
#Override
public void onConnectionSuspended(int num) {
Toast.makeText(this, "ConnectionSuspended", Toast.LENGTH_LONG).show();
}
#Override
public void onConnectionFailed(ConnectionResult res) {
Toast.makeText(this, "ConnectionFailed", Toast.LENGTH_LONG).show();
}
#Override
protected void onStop() {
Wearable.DataApi.removeListener(client, this);
client.disconnect();
super.onStop();
}
#Override
public void onDataChanged(DataEventBuffer dataEvents) {
Toast.makeText(this, "DataChanged!", Toast.LENGTH_LONG).show();
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED && event.getDataItem().getUri().getPath().equals("/image")) {
DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
Asset profileAsset = dataMapItem.getDataMap().getAsset("profileImage");
Bitmap bitmap = loadBitmapFromAsset(profileAsset);
// Do something with bitmap
Toast.makeText(this, "DataChanged!", Toast.LENGTH_LONG).show();
}
}
}
public Bitmap loadBitmapFromAsset(Asset asset) {
if (asset == null) {
throw new IllegalArgumentException("Asset must be non-null");
}
ConnectionResult result = client.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
if (!result.isSuccess()) {
return null;
}
// Convert asset into a file descriptor and block until it's ready
InputStream assetInputStream = Wearable.DataApi.getFdForAsset(client, asset).await().getInputStream();
client.disconnect();
if (assetInputStream == null) {
Log.w(TAG, "Requested an unknown Asset.");
return null;
}
// Decode the stream into a bitmap
return BitmapFactory.decodeStream(assetInputStream);
}
}
Everything "seems" to succeed as far as I can tell but the Toast in the Wear's "onDataChanged" method never shows up on the Wear's display which makes me think it isn't seeing the data change for some reason. Any help would be greatly appreciated!
Try adding a timestamp to your request on the handheld, it did the trick for me back then, and I think Google updated their documentations adding this bit of info

Resources