I am building a Spring Batch Job that has 2 steps for downloading 2 files. The file names are job parameters.
Please find below the steps configuration class (FileDownloader and FileDownloadTasklet are custom classes for the download logic):
#Autowired
private FileDownloader fileDownloader;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Bean
public Step downloadFirstFileStep() {
return stepBuilderFactory.get("downloadFirstFileStep")
.tasklet(firstFileDownloadTasklet(null)).build();
}
#Bean
#StepScope
public FileDownloadTasklet firstFileDownloadTasklet(
#Value("#{jobParameters['firstFile']}") String fileName) {
return new FileDownloadTasklet(fileDownloader, fileName);
}
#Bean
public Step downloadSecondFileStep() {
return stepBuilderFactory.get("downloadSecondFileStep")
.tasklet(secondFileDownloadTasklet(null)).build();
}
#Bean
#StepScope
public FileDownloadTasklet secondFileDownloadTasklet(
#Value("#{jobParameters['secondFile']}") String fileName) {
return new FileDownloadTasklet(fileDownloader, fileName);
}
I'm feeling I'm not doing the right thing by duplicating the Step beans since the only difference is the actual name of the file to be downloaded.
Can you please give me a hint on how can I do it without duplicating the beans?
Yes we can do this in better way using a partitioner. Currently you are downloading 2 files and future you might want to download more number of files.
#Bean(name = "partitionerJob")
public Job partitionerJob() {
return jobs.get("partitioningJob")
.start(partitionStep())
.build();
}
#Bean
#StepScope
public Step partitionStep() {
return steps.get("partitionStep")
.partitioner("slaveStep", partitioner())
.step(downloadFirstFileStep())
.taskExecutor(taskExecutor())
.build();
}
#Bean
public Step downloadFirstFileStep() {
return stepBuilderFactory.get("downloadFirstFileStep")
.tasklet(firstFileDownloadTasklet(null)).build();
}
#Bean
#StepScope
public FileDownloadTasklet firstFileDownloadTasklet(
#Value("#{stepExecutionContext['fileName']}") String fileName) {
return new FileDownloadTasklet(fileDownloader, fileName);
}
#Bean
public YourPartitioner partitioner() {
return new YourPartitioner();
}
public class YourPartitioner implements Partitioner {
#Value("#{jobParameters['fileNames']}") //Pass Comma separated file names as argument
protected String fileNames;
#Override
public Map<String, ExecutionContext> partition(int gridSize) {
Map<String, ExecutionContext> map = new HashMap<>(gridSize);
int i = 0, k = 1;
for (String resource : fileNames.split(",") {
ExecutionContext context = new ExecutionContext();
context.putString("fileName", resource); //This will be fetched as argument to the step job from JobExecutionContext
map.put("PARTITION_KEY " + i, context);
i++;
}
return map;
}
}
Related
#SpringBootApplication
public class SpringDataSolarApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataSolarApplication.class, args);
}
#Bean
SolrTemplate solrTemplate() {
return new SolrTemplate(solrClientFactory());
}
#Bean
SolrClientFactory solrClientFactory() {
Credentials credentials = new UsernamePasswordCredentials("solr", "SolrRocks");
return new HttpSolrClientFactory(solrClient(), credentials , "BASIC");
}
#Bean
SolrClient solrClient() {
return new HttpSolrClient.Builder("http://localhost:8983/solr").build();
}
}
public interface EmployeeRepository extends SolrCrudRepository{
Employee findByName(String name);
}
#RestController
public class EmployeeController {
#Autowired
private EmployeeRepository repository;
#PostConstruct
public void addEmployees() {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("373", "Basant", new String[] { "Bangalore", "BTM" }));
employees.add(new Employee("908", "Santosh", new String[] { "Hyderbad", "XYZ" }));
employees.add(new Employee("321", "Sagar", new String[] { "Pune", "PQR" }));
repository.saveAll(employees);
}
#GetMapping("/getALL")
public Iterable<Employee> getEmployees() {
return repository.findAll();
}
#GetMapping("/getEmployee/{name}")
public Employee getEmployeeByName(#PathVariable String name) {
return repository.findByName(name);
}
}
the getALL operation is working fine but the save operation failed with this error. Please help
Caused by: org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity.
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:225) ~[httpclient-4.5.7.jar:4.5.7]
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185) ~[httpclient-4.5.7.jar:4.5.7]
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89) ~[httpclient-4.5.7.jar:4.5.7]
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) ~[httpclient-4.5.7.jar:4.5.7]
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) ~[httpclient-4.5.7.jar:4.5.7]
... 63 common frames omitted
Came across same issue and solved with extending HttpSolrClient and applying same backend approach with recommended way mentioned on Solr docs but getting credentials from constructor not setting on each request.
class CustomSolrClient extends HttpSolrClient {
#Nullable
private final String username;
#Nullable
private final String password;
CustomSolrClient(Builder builder, String username, String password) {
super(builder);
this.username = username;
this.password = password;
}
#Override
public NamedList<Object> request(SolrRequest request, ResponseParser processor, String collection) throws SolrServerException, IOException {
HttpRequestBase method = createMethod(request, collection);
if (username != null && password != null) {
String userPass = username + ":" + password;
String encoded = Base64.byteArrayToBase64(userPass.getBytes(UTF_8));
method.setHeader(new BasicHeader("Authorization", "Basic " + encoded));
}
return executeMethod(method, processor, request instanceof V2Request || request.getPath().contains("/____v2"));
}
}
And create bean using that:
#Bean
public SolrClient solrClient() {
return new CustomSolrClient(new HttpSolrClient.Builder(properties.getHost()), properties.getUsername(), properties.getPassword());
}
This may seem as an ugly approach but if you check HttpSolrClientFactory sources it's even more uglier which actually accesses private field of HttpClient belongs to Solr client.
Using Spring boot 1.5.6.RELEASE.
I have the following mongo document base class:
#Document(collection="validation_commercial")
public abstract class Tier {
#Id
private String id;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Date created;
#Field("tran")
private Tran tran;
public Tier() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public Tran getTran() {
return tran;
}
public void setTran(Tran tran) {
this.tran = tran;
}
}
which is then extended:
public class Tier1 extends Tier {
#Field("tier1")
private Tier1Programs tier1;
public Tier1() {
this.tier1 = new Tier1Programs();
}
public Tier1Programs getTier1() {
return tier1;
}
public void setTier1(Tier1Programs tier1) {
this.tier1 = tier1;
}
}
which in turn is extended:
public class Tier2 extends Tier1 {
#Field("tier2")
private Tier2Programs tier2;
public Tier2() {
this.tier2 = new Tier2Programs();
}
public Tier2Programs getTier2() {
return tier2;
}
public void setTier2(Tier2Programs tier2) {
this.tier2 = tier2;
}
}
There is a Tier1 Supervisor (Spring Boot Application) that uses the Tier1 class within the MongoRepository interface:
public interface Tier1Repository extends MongoRepository<Tier1,String>{}
for retrieving and saving - no issue.
I then have a Tier2 Supervisor (Spring Boot Application) that uses a Tier1 Repository (for retrieving the Tier1 document and a Tier2 Repository for saving the Tier2 document:
#Repository("tier1Repository")
public interface Tier1Repository extends MongoRepository<Tier1,String>{}
#Repository("tier2Repository")
public interface Tier2Repository extends MongoRepository<Tier2,String>{}
My service is:
#Service
public class TierService {
#Qualifier("tier1Repository")
#Autowired
private final Tier1Repository tier1Repository;
#Qualifier("tier2Repository")
#Autowired
private final Tier2Repository tier2Repository;
public TierService(#Qualifier("tier1Repository") Tier1Repository tier1Repository, #Qualifier("tier2Repository") Tier2Repository tier2Repository) {
this.tier1Repository = tier1Repository;
this.tier2Repository = tier2Repository;
}
public Tier1 findOne(String id) {
return tier1Repository.findOne(id);
}
public void SaveTier(Tier2 tier) {
tier2Repository.save(tier);
}
public Tier1Repository getTier1Repository() {
return tier1Repository;
}
public Tier2Repository getTier2Repository() {
return tier2Repository;
}
}
and finally the app:
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, JdbcTemplateAutoConfiguration.class})
#Configuration
#ComponentScan(basePackages = {"com.k12commercial.tier2supervisor"})
#ImportResource("classpath:application-context.xml")
public class Application implements CommandLineRunner {
#Autowired
private IReceiver raBidNetPriceReceiver;
#Autowired
private UdyDataSourceFactory udyDSRegistry;
public static void main(String[] args) throws InterruptedException {
try {
SpringApplication.run(Application.class, args);
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void run(String... args) throws Exception {
raBidNetPriceReceiver.processTierMessages();
exit(0);
}
}
When I run the Tier2 Supervisor from the command line I get the following error:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'tierService' defined in URL
[jar:file:/opt/java-commandline/tier2supervisor-1.0.jar!/BOOT-INF/classes!/com/k12commercial/tier2supervisor/service/TierService.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tier2Repository': Invocation of init method failed; nested exception is org.springframework.data.mapping.model.MappingException: Ambiguous field mapping detected! Both private final java.lang.reflect.Type org.springframework.data.util.TypeDiscoverer.type and private final java.lang.Class org.springframework.data.util.ClassTypeInformation.type map to the same field name type! Disambiguate using #Field annotation!
I am not sure if the issue is Tier2 extending Tier1 (did try putting #Document tag above Tier1 and Tier2 with no change). I think I have marked the relevant fields so don't understand the need to disambiguate. I thought the issue was having 2 repositories (Spring Boot not knowing which one to DI) so removed the Tier1Repository - didn't work. Tried better qualifying the repositories but still got the same error. I made Tier1 and Tier2 #Transient and that got rid of the message but also removed the tier1 section in the mongo document - so wrong correction.
Thinking it is an annotation fix but not seeing it...
Please advise - thank you.
Sorry for the delay (I got pulled away to work on something else) and thank you to those who responded.
The issue was I had a MongoTemplate in my Tier level programs e.g.Tier2Programs (sub library) which Spring Boot was trying to autowire.
By moving the Mongo (CRUD) requirements to the supervisor level (I also replaced the Repositories with one MongoTemplate to simplify) I removed the ambiguity. (I also removed the Service class).
The code is contained with the RaBidNetReciever class
#Component
public class RaBidNetPriceReceiver extends BaseReceiver implements IReceiver, ApplicationEventPublisherAware {
private static final Logger LOGGER = LoggerFactory.getLogger(RaBidNetPriceReceiver.class);
private final RabbitTemplate raBidNetPriceRabbitTemplate;
public RaBidNetPriceReceiver(MongoTemplate mongoTemplate, RabbitTemplate raBidNetPriceRabbitTemplate) {
super(mongoTemplate);
this.raBidNetPriceRabbitTemplate = raBidNetPriceRabbitTemplate;
}
#Transactional
public void processTierMessages() {
try {
while (true) {
gson = getGsonBuilder().create();
byte[] body = (byte[]) raBidNetPriceRabbitTemplate.receiveAndConvert();
if (body == null) {
setFinished(true);
break;
}
tier1Message = gson.fromJson(new String(body), Tier1Message.class);
// document a 'Tier1' type so retrieve Tier1 first...
Tier1 tier1 = mongoTemplate.findById(tier1Message.getId(), Tier1.class);
Tier2Message tier2Message = new Tier2Message(tier1Message.getTran(), tier1Message.getId());
Tier2Process tierProcess = getTierProcess(tier2Message.getTran().getK12ArchitectureId());
Tier2 tier2 = new Tier2();
tier2.setId(tier1.getId());
tier2.setTier1Programs(tier1.getTier1Programs());
tier2.setCreated(tier1.getCreated());
tier2.setTran(tier1.getTran());
tierProcess.setTier(tier2);
tier2 = tier2.getTier2Programs().getRaBidNetPriceProgram().process(tierProcess);
mongoTemplate.save(tier2);
if (tier2.getTier2Programs().getRaBidNetPriceProgram().isFinished()) {
// publish event
publisher.publishEvent(new ProgramEvent(this, "FINISHED", tier2Message));
}
}
} catch (Exception e) {
LOGGER.error("id: " + tier1Message.getId() + " " + e.getMessage());
}
}
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}
Thank you,
I had been developing and testing on the Codename One simulator and everything worked fine.
However, when I tested it on a real Android device, I get a 405 Method Not Allowed error. This happened on both a POST and GET request.
I suspect it is the #Consume and #Produces which are causing the problem. How do I fix this?
Here are my server side code:
#GET
#Path("/all/{language}")
#Produces("application/json")
public final Response getAllCelebrities(#PathParam("language") String language) {
String celebritiesJSONString = CelebrityActions.getAllCelebritiesNamesJSONString(language);
return Response.ok(celebritiesJSONString).build();
}
#POST
#Path("/login")
#Consumes("application/x-www-form-urlencoded")
#Produces("text/plain")
public final Response login(
#FormParam("loginid") String loginid,
#FormParam("password") String password
) {
System.out.println("login 0 started");
Long fanID;
try {
fanID = AccountsActions.login(loginid, password);
} catch (Exception e) {
return Response.serverError().entity(e.getMessage()).build();
}
if (fanID == null) {
return responseFanIDNotFoundError();
}
System.out.println("This is printed out!!!");
System.out.println("login 100 ended");
return Response.ok().build();
}
And here's my log upon execution of the login() method:
login 0 started
This is printed out!!!
login 100 ended
which means the server side method was ready to return a 200 response.
What is causing the Android client to show a 405 Method Not Allow error?
EDIT: I'm adding my cient-side code here:
(note that this one handles a cookie from a server)
public class Login extends PostConnection {
private final String LoginEndpoint = "account/login";
private String loginIDString;
private String loginPasswordString;
// Tested and works on simulator!
public Login(String loginIDString, String loginPasswordString) {
super();
endpoint = LoginEndpoint;
this.loginIDString = loginIDString;
this.loginPasswordString = loginPasswordString;
}
#Override
protected void prepareParametersMap() {
parametersMap = new HashMap<>();
parametersMap.put("loginid", loginIDString);
parametersMap.put("password", loginPasswordString);
}
}
public abstract class PostConnection extends PostPutConnection {
public PostConnection() {
super();
}
public boolean connect() throws IOException {
connectionRequest.setHttpMethod("POST");
return super.connect();
}
}
public abstract class PostPutConnection extends Connection {
protected HashMap<String, String> parametersMap;
public PostPutConnection() {
super();
}
protected static final void setPostParameters(ConnectionRequest connectionRequest, HashMap<String, String> parametersMap) {
Set<String> paramateterKeys = parametersMap.keySet();
Iterator<String> parameterKeysIterator = paramateterKeys.iterator();
while (parameterKeysIterator.hasNext()) {
String key = parameterKeysIterator.next();
String value = parametersMap.get(key);
connectionRequest.addArgument(key, value);
}
}
protected abstract void prepareParametersMap();
public boolean connect() throws IOException {
prepareParametersMap();
setPost();
setPostParameters();
return super.connect();
}
private void setPostParameters() {
setPostParameters(connectionRequest, parametersMap);
}
private final void setPost() {
connectionRequest.setPost(true);
}
}
public abstract class Connection {
private final static String protocol = "http";
private final static String domain = "192.168.0.109:20000";
protected ConnectionRequest connectionRequest;
protected String endpoint;
public Connection() {
super();
init();
}
protected void init() {
connectionRequest = new ConnectionRequest();
connectionRequest.setCookiesEnabled(true);
ConnectionRequest.setUseNativeCookieStore(true);
}
public boolean connect() throws IOException {
connectionRequest.setUrl(protocol + "://" + domain + "/" + endpoint);
NetworkManager.getInstance().addToQueueAndWait(connectionRequest);
int responseCode = getResponseCode();
return responseCode == 200 ? true : false;
}
private int getResponseCode() {
int responseCode = connectionRequest.getResponseCode();
return responseCode;
}
}
And another method below:
(note that this one does not handle cookies)
public class GetAllCelebrities extends GetConnection {
private final String GetCelebritiesEndpoint = "celebrity/all";
public GetAllCelebrities(String language) {
super();
endpoint = GetCelebritiesEndpoint + "/" + language;
}
}
public abstract class GetConnection extends Connection {
private Map<String, Object> responseData;
public GetConnection() {
super();
}
public boolean connect() throws IOException {
connectionRequest.setHttpMethod("GET");
boolean connectResult = super.connect();
if (!connectResult) {
return false;
}
responseData = getResponseResult();
return true;
}
private Map<String, Object> getResponseResult() throws IOException {
byte[] responseData = connectionRequest.getResponseData();
ByteArrayInputStream responseDataBAIS = new ByteArrayInputStream(responseData);
InputStreamReader responseDataISR = new InputStreamReader(responseDataBAIS, "UTF-8");
JSONParser responseDateJSONParser = new JSONParser();
Map<String, Object> responseResult = responseDateJSONParser.parseJSON(responseDataISR);
return responseResult;
}
public Map<String, Object> getResponseData() {
return responseData;
}
}
And it is called like:
private Map<String, Object> fetchCelebrities() throws IOException {
GetAllCelebrities getAllCelebrities = new GetAllCelebrities("en");
getAllCelebrities.connect();
return getAllCelebrities.getResponseData();
}
private boolean performLogin() throws IOException {
String loginIDString = loginID.getText();
String loginPasswordString = loginPassword.getText();
Login login = new Login(loginIDString, loginPasswordString);
boolean loginResult = login.connect();
return loginResult;
}
It's a bit hard to read all of this code but I'll venture a guess based on the server message. You've set the method to "PUT" along the way in the post put class and that isn't supported by the server yet.
The best way to debug these things is with the network monitor in the Simulator. Its shows the traffic and would have made these things mostly clear
I have created a custom appender (will be used for Linux). For creating of this appender I used this article How write custom log4j appender
public class SolrAppender extends AppenderSkeleton {
private String path = null;
public void setPath(String path) { this.path = path; }
public String getPath() { return this.path; }
#Override
public boolean requiresLayout() {
return true;
}
#Override
public void close() {
}
#Override
public void activateOptions() {
super.activateOptions();
}
#Override
public synchronized void append(LoggingEvent event) {
SolrServer server = new HttpSolrServer(path);
SolrInputDocument document = new SolrInputDocument();
//some logic
UpdateResponse response = server.add(document);
server.commit();
}
Configuration of this appender is
# Solr appender
log4j.appender.SOLR = ricardo.solr.appender.QueryParser.SolrAppender
log4j.appender.SOLR.layout = org.apache.log4j.SimpleLayout
log4j.appender.SOLR.path = http://XX.XXX.XX.XX:8985/application/core
Appender works correct if path is hardcoded. Why path is not set via configuration?
From what I've seen so far, the name of a property of an appender, in the configuration, should start with an upper case character, so 'Path' instead of 'path', so you should use:
log4j.appender.SOLR.Path = http://XX.XXX.XX.XX:8985/application/core
Not sure why it's not the case for 'layout', though.
I'm having an issue with NoClassDefFoundError when running Java from the Windows Command Prompt. I put the main TestLogger.java file in C:\mywork and four other java files in C:\mywork\logging that were organized as a package.
There was no difficulty compiling these files (javac TestLogger.java), or even creating a jar file, but I failed when trying to run the compiled TestLogger, and got the "Exception in thread 'main' java.lang.NoClassDefFoundError".
I also did some searching on the web, and found that most folks who were getting this error were only trying to run a single simple HelloWorld program. So I tried my own HelloWorld.java in C:\mywork and weird enough, there was no problem compiling and running it. And none of the various solutions I found on the web such as checking the environment variable, and adding a period after -classpath parameter, could work a miracle in my case.
I'm using JDK 1.7 on Windows XP. I'm sure there's nothing wrong with the environment variables. Below are the full contents of my source files (an example from Jeff Friesen's 2nd Edition of Learn Java for Android Development, p. 175). Hopefully someone can shed some light on this. Thanks in advance!
In C:\mywork I have one single file as the main entrance.
TestLogger.java:
package logging;
import logging.Logger;
import logging.LoggerFactory;
public class TestLogger {
public static void main(String[] args) {
Logger logger = LoggerFactory.newLogger(LoggerFactory.CONSOLE);
if (logger.connect()) {
logger.log("test message #1");
logger.disconnect();
}
else
System.out.println("cannot connect to console-based logger");
logger = LoggerFactory.newLogger(LoggerFactory.FILE, "x.txt");
if (logger.connect()) {
logger.log("test message #2");
logger.disconnect();
}
else
System.out.println("cannot connect to file-based logger");
logger = LoggerFactory.newLogger(LoggerFactory.FILE);
if (logger.connect()) {
logger.log("test message #3");
logger.disconnect();
}
else
System.out.println("cannot connect to file-based logger");
}
}
In the C:\mywork\logging folder I have four files:
1) Logger.java:
package logging;
public interface Logger {
boolean connect();
boolean disconnect();
boolean log(String msg);
}
2) Console.java:
package logging;
public class Console implements Logger {
private String dstName;
Console(String dstName) {
this.dstName = dstName;
}
#Override
public boolean connect() {
return true;
}
#Override
public boolean disconnect() {
return true;
}
#Override
public boolean log(String msg) {
System.out.println(msg);
return true;
}
}
3) File.java:
package logging;
public class File implements Logger {
private String dstName;
File(String dstName) {
this.dstName = dstName;
}
#Override
public boolean connect() {
if (dstName == null)
return false;
System.out.println("opening file " + dstName);
return true;
}
#Override
public boolean disconnect() {
if (dstName == null)
return false;
System.out.println("closing file " + dstName);
return true;
}
#Override
public boolean log(String msg) {
if (dstName == null)
return false;
System.out.println("writing "+msg+" to file " + dstName);
return true;
}
}
4) LoggingFactory.java:
package logging;
public abstract class LoggerFactory {
public final static int CONSOLE = 0;
public final static int FILE = 1;
public static Logger newLogger(int dstType, String... dstName) {
switch (dstType) {
case CONSOLE:
return new Console(dstName.length == 0 ? null : dstName[0]);
case FILE:
return new File(dstName.length == 0 ? null : dstName[0]);
default:
return null;
}
}
}