Why is my Scheduled Task not running - java

I have a scheduled task which takes the hostname and other Calendar information from a JSP page. For the "HOUR_of_Day" input, I always add 12 and pass the total to Calender since it uses a 24 - hour format. What I did worked fine yesterday evening but it is not working this morning. It does not run anymore at the scheduled time. I certainly have done something wrong and need some direction. Below is my code:
import java.util.Calendar;
import java.util.Timer;
import java.util.TimerTask;
public class ScheduledTask extends TimerTask {
String hostname = null;
//The default no - arg constructor
//=============================
public ScheduledTask(){
}
/**
* Another overloaded constructor
* that accepts an object
* #param scanHostObj
public ScheduledTask(ScanHost scanHostObj){
hostname = scanHostObj.getHostname();
}
/**
* Another overloaded constructor
* that accepts a string parameter
* #param nodename
*/
public ScheduledTask(String nodename){
hostname = nodename;
}
/**
* The run method that executes
* the scheduled task
*/
public void run() {
new ScanUtility().performHostScan(hostname);
}
public void executeScheduledScanJob(ScanHost hostObj, Scheduler schedulerObj){
/**
* Get the various schedule
* data, convert to appropriate data type
* and feed to calender class
*/
String nodename = hostObj.getHostname();
String dayOfScan = schedulerObj.getDayOfScan();
String hourOfScan = schedulerObj.getHourOfScan();
String minuteOfScan = schedulerObj.getMinuteofScan();
//Convert String values to integers
//===================================
final int THE_DAY = Integer.parseInt(dayOfScan);
final int THE_HOUR = Integer.parseInt(hourOfScan);
final int REAL_HOUR = THE_HOUR + 12;
final int THE_MINUTE = Integer.parseInt(minuteOfScan);
/**
* Feed these time values to the Calendar class.
* Since Calendar takes a 24 - hour format for hours
* it is better to add 12 to any integer value for
* hours to bring the real hourly format to the 24
* format required by the Calendar class
*/
Calendar scheduleCalendar = Calendar.getInstance();
scheduleCalendar.set(Calendar.DAY_OF_WEEK, THE_DAY);
scheduleCalendar.set(Calendar.HOUR_OF_DAY, REAL_HOUR);
scheduleCalendar.set(Calendar.MINUTE, THE_MINUTE);
/**
* Then Initialize the timer Object
* and pass in parameters to it method
* cancel out all tasks/jobs after running them
*/
Timer scheduledTimeObj = new Timer();
ScheduledTask scheduledTaskObj = new ScheduledTask(nodename);
scheduledTimeObj.schedule(scheduledTaskObj, scheduleCalendar.getTime());
scheduledTimeObj.cancel();
scheduledTaskObj.cancel();
}
}

You could try to replace
scheduleCalendar.set(Calendar.HOUR_OF_DAY, REAL_HOUR);
With this:
scheduleCalendar.set(Calendar.HOUR, THE_HOUR);
This one will take 12 hour format.
Additionally, you can set AM_PM to AM or PM. If you try to parse a PM date, use PM here.
scheduleCalendar.set(Calendar.AM_PM, Calendar.PM);

Related

How to use spring #Scheduled with cron for execute every X weeks? [duplicate]

I need to create Job that will :
starts one 12/20/2012
endDate = 12/31/2017
will occur every 2 weeks on Sunday and monday
fires at 5 pm.
is this cron expression valid?
Date start = 12/20/2012;
Date endDate = 12/31/2017;
SimpleTrigger trigger = newTrigger()
.withIdentity("trigger3", "group1")
.startAt(startDate)
.withSchedule(cronSchedule("* * 17 0 0/2 *,SUN,MON").build())
.endAt(endDate)
.build;
Please advise.
I suggest, that you make a unit test based on your cron expression. With kudos to Van de Voorde Toni, you can base it on this code, and use it to verify that the "nextValidTimeAfter" matches your expectation:
import java.text.ParseException;
import java.util.Date;
import org.quartz.CronExpression;
public class CronTester {
public static void main(String[] args) throws ParseException {
final String expression = "* * 17 0 0/2 *,SUN,MON";
final CronExpression cronExpression = new CronExpression(expression);
final Date nextValidDate1 = cronExpression.getNextValidTimeAfter(new Date());
final Date nextValidDate2 = cronExpression.getNextValidTimeAfter(nextValidDate1);
System.out.println(nextValidDate1);
System.out.println(nextValidDate2);
}
}

GCP Dataflow 2.0 PubSub to GCS

I'm having a difficult time understanding the concepts of .withFileNamePolicy of TextIO.write(). The requirements for supplying a FileNamePolicy seem incredibly complex for doing something as simple as specifying a GCS bucket to write streamed filed.
At a high level, I have JSON messages being streamed to a PubSub topic, and I'd like to write those raw messages to files in GCS for permanent storage (I'll also be doing other processing on the messages). I initially started with this Pipeline, thinking it would be pretty simple:
public static void main(String[] args) {
PipelineOptions options = PipelineOptionsFactory.fromArgs(args).withValidation().create();
Pipeline p = Pipeline.create(options);
p.apply("Read From PubSub", PubsubIO.readStrings().fromTopic(topic))
.apply("Write to GCS", TextIO.write().to(gcs_bucket);
p.run();
}
I got the error about needing WindowedWrites, which I applied, and then needing a FileNamePolicy. This is where things get hairy.
I went to the Beam docs and checked out FilenamePolicy. It looks like I would need to extend this class which then also require extending other abstract classes to make this work. Unfortunately the documentation on Apache is a bit scant and I can't find any examples for Dataflow 2.0 doing this, except for The Wordcount Example, which even then uses implements these details in a helper class.
So I could probably make this work just by copying much of the WordCount example, but I'm trying to better understand the details of this. A few questions I have:
1) Is there any roadmap item to abstract a lot of this complexity? It seems like I should be able to do supply a GCS bucket like I would in a nonWindowedWrite, and then just supply a few basic options like the timing and file naming rule. I know writing streaming windowed data to files is more complex than just opening a file pointer (or object storage equivalent).
2) It looks like to make this work, I need to create a WindowedContext object which requires supplying a BoundedWindow abstract class, and PaneInfo Object Class, and then some shard info. The information available for these is pretty bare and I'm having a hard time knowing what is actually needed for all of these, especially given my simple use case. Are there any good examples available that implement these? In addition, it also looks like I need the set the # of shards as part of TextIO.write, but then also supply # shards as part of the fileNamePolicy?
Thanks for anything in helping me understand the details behind this, hoping to learn a few things!
Edit 7/20/17
So I finally got this pipeline to run with extending the FilenamePolicy. My challenge was needing to define the window of the streaming data from PubSub. Here is a pretty close representation of the code:
public class ReadData {
public static void main(String[] args) {
PipelineOptions options = PipelineOptionsFactory.fromArgs(args).withValidation().create();
Pipeline p = Pipeline.create(options);
p.apply("Read From PubSub", PubsubIO.readStrings().fromTopic(topic))
.apply(Window.into(FixedWindows.of(Duration.standardMinutes(1))))
.apply("Write to GCS", TextIO.write().to("gcs_bucket")
.withWindowedWrites()
.withFilenamePolicy(new TestPolicy())
.withNumShards(10));
p.run();
}
}
class TestPolicy extends FileBasedSink.FilenamePolicy {
#Override
public ResourceId windowedFilename(
ResourceId outputDirectory, WindowedContext context, String extension) {
IntervalWindow window = (IntervalWindow) context.getWindow();
String filename = String.format(
"%s-%s-%s-%s-of-%s.json",
"test",
window.start().toString(),
window.end().toString(),
context.getShardNumber(),
context.getShardNumber()
);
return outputDirectory.resolve(filename, ResolveOptions.StandardResolveOptions.RESOLVE_FILE);
}
#Override
public ResourceId unwindowedFilename(
ResourceId outputDirectory, Context context, String extension) {
throw new UnsupportedOperationException("Unsupported.");
}
}
In Beam 2.0, the below is an example of writing the raw messages from PubSub out into windowed files on GCS. The pipeline is fairly configurable, allowing you to specify the window duration via a parameter and a sub directory policy if you want logical subsections of your data for ease of reprocessing / archiving. Note that this has an additional dependency on Apache Commons Lang 3.
PubSubToGcs
/**
* This pipeline ingests incoming data from a Cloud Pub/Sub topic and
* outputs the raw data into windowed files at the specified output
* directory.
*/
public class PubsubToGcs {
/**
* Options supported by the pipeline.
*
* <p>Inherits standard configuration options.</p>
*/
public static interface Options extends DataflowPipelineOptions, StreamingOptions {
#Description("The Cloud Pub/Sub topic to read from.")
#Required
ValueProvider<String> getTopic();
void setTopic(ValueProvider<String> value);
#Description("The directory to output files to. Must end with a slash.")
#Required
ValueProvider<String> getOutputDirectory();
void setOutputDirectory(ValueProvider<String> value);
#Description("The filename prefix of the files to write to.")
#Default.String("output")
#Required
ValueProvider<String> getOutputFilenamePrefix();
void setOutputFilenamePrefix(ValueProvider<String> value);
#Description("The shard template of the output file. Specified as repeating sequences "
+ "of the letters 'S' or 'N' (example: SSS-NNN). These are replaced with the "
+ "shard number, or number of shards respectively")
#Default.String("")
ValueProvider<String> getShardTemplate();
void setShardTemplate(ValueProvider<String> value);
#Description("The suffix of the files to write.")
#Default.String("")
ValueProvider<String> getOutputFilenameSuffix();
void setOutputFilenameSuffix(ValueProvider<String> value);
#Description("The sub-directory policy which files will use when output per window.")
#Default.Enum("NONE")
SubDirectoryPolicy getSubDirectoryPolicy();
void setSubDirectoryPolicy(SubDirectoryPolicy value);
#Description("The window duration in which data will be written. Defaults to 5m. "
+ "Allowed formats are: "
+ "Ns (for seconds, example: 5s), "
+ "Nm (for minutes, example: 12m), "
+ "Nh (for hours, example: 2h).")
#Default.String("5m")
String getWindowDuration();
void setWindowDuration(String value);
#Description("The maximum number of output shards produced when writing.")
#Default.Integer(10)
Integer getNumShards();
void setNumShards(Integer value);
}
/**
* Main entry point for executing the pipeline.
* #param args The command-line arguments to the pipeline.
*/
public static void main(String[] args) {
Options options = PipelineOptionsFactory
.fromArgs(args)
.withValidation()
.as(Options.class);
run(options);
}
/**
* Runs the pipeline with the supplied options.
*
* #param options The execution parameters to the pipeline.
* #return The result of the pipeline execution.
*/
public static PipelineResult run(Options options) {
// Create the pipeline
Pipeline pipeline = Pipeline.create(options);
/**
* Steps:
* 1) Read string messages from PubSub
* 2) Window the messages into minute intervals specified by the executor.
* 3) Output the windowed files to GCS
*/
pipeline
.apply("Read PubSub Events",
PubsubIO
.readStrings()
.fromTopic(options.getTopic()))
.apply(options.getWindowDuration() + " Window",
Window
.into(FixedWindows.of(parseDuration(options.getWindowDuration()))))
.apply("Write File(s)",
TextIO
.write()
.withWindowedWrites()
.withNumShards(options.getNumShards())
.to(options.getOutputDirectory())
.withFilenamePolicy(
new WindowedFilenamePolicy(
options.getOutputFilenamePrefix(),
options.getShardTemplate(),
options.getOutputFilenameSuffix())
.withSubDirectoryPolicy(options.getSubDirectoryPolicy())));
// Execute the pipeline and return the result.
PipelineResult result = pipeline.run();
return result;
}
/**
* Parses a duration from a period formatted string. Values
* are accepted in the following formats:
* <p>
* Ns - Seconds. Example: 5s<br>
* Nm - Minutes. Example: 13m<br>
* Nh - Hours. Example: 2h
*
* <pre>
* parseDuration(null) = NullPointerException()
* parseDuration("") = Duration.standardSeconds(0)
* parseDuration("2s") = Duration.standardSeconds(2)
* parseDuration("5m") = Duration.standardMinutes(5)
* parseDuration("3h") = Duration.standardHours(3)
* </pre>
*
* #param value The period value to parse.
* #return The {#link Duration} parsed from the supplied period string.
*/
private static Duration parseDuration(String value) {
Preconditions.checkNotNull(value, "The specified duration must be a non-null value!");
PeriodParser parser = new PeriodFormatterBuilder()
.appendSeconds().appendSuffix("s")
.appendMinutes().appendSuffix("m")
.appendHours().appendSuffix("h")
.toParser();
MutablePeriod period = new MutablePeriod();
parser.parseInto(period, value, 0, Locale.getDefault());
Duration duration = period.toDurationFrom(new DateTime(0));
return duration;
}
}
WindowedFilenamePolicy
/**
* The {#link WindowedFilenamePolicy} class will output files
* to the specified location with a format of output-yyyyMMdd'T'HHmmssZ-001-of-100.txt.
*/
#SuppressWarnings("serial")
public class WindowedFilenamePolicy extends FilenamePolicy {
/**
* Possible sub-directory creation modes.
*/
public static enum SubDirectoryPolicy {
NONE("."),
PER_HOUR("yyyy-MM-dd/HH"),
PER_DAY("yyyy-MM-dd");
private final String subDirectoryPattern;
private SubDirectoryPolicy(String subDirectoryPattern) {
this.subDirectoryPattern = subDirectoryPattern;
}
public String getSubDirectoryPattern() {
return subDirectoryPattern;
}
public String format(Instant instant) {
DateTimeFormatter formatter = DateTimeFormat.forPattern(subDirectoryPattern);
return formatter.print(instant);
}
}
/**
* The formatter used to format the window timestamp for outputting to the filename.
*/
private static final DateTimeFormatter formatter = ISODateTimeFormat
.basicDateTimeNoMillis()
.withZone(DateTimeZone.getDefault());
/**
* The filename prefix.
*/
private final ValueProvider<String> prefix;
/**
* The filenmae suffix.
*/
private final ValueProvider<String> suffix;
/**
* The shard template used during file formatting.
*/
private final ValueProvider<String> shardTemplate;
/**
* The policy which dictates when or if sub-directories are created
* for the windowed file output.
*/
private ValueProvider<SubDirectoryPolicy> subDirectoryPolicy = StaticValueProvider.of(SubDirectoryPolicy.NONE);
/**
* Constructs a new {#link WindowedFilenamePolicy} with the
* supplied prefix used for output files.
*
* #param prefix The prefix to append to all files output by the policy.
* #param shardTemplate The template used to create uniquely named sharded files.
* #param suffix The suffix to append to all files output by the policy.
*/
public WindowedFilenamePolicy(String prefix, String shardTemplate, String suffix) {
this(StaticValueProvider.of(prefix),
StaticValueProvider.of(shardTemplate),
StaticValueProvider.of(suffix));
}
/**
* Constructs a new {#link WindowedFilenamePolicy} with the
* supplied prefix used for output files.
*
* #param prefix The prefix to append to all files output by the policy.
* #param shardTemplate The template used to create uniquely named sharded files.
* #param suffix The suffix to append to all files output by the policy.
*/
public WindowedFilenamePolicy(
ValueProvider<String> prefix,
ValueProvider<String> shardTemplate,
ValueProvider<String> suffix) {
this.prefix = prefix;
this.shardTemplate = shardTemplate;
this.suffix = suffix;
}
/**
* The subdirectory policy will create sub-directories on the
* filesystem based on the window which has fired.
*
* #param policy The subdirectory policy to apply.
* #return The filename policy instance.
*/
public WindowedFilenamePolicy withSubDirectoryPolicy(SubDirectoryPolicy policy) {
return withSubDirectoryPolicy(StaticValueProvider.of(policy));
}
/**
* The subdirectory policy will create sub-directories on the
* filesystem based on the window which has fired.
*
* #param policy The subdirectory policy to apply.
* #return The filename policy instance.
*/
public WindowedFilenamePolicy withSubDirectoryPolicy(ValueProvider<SubDirectoryPolicy> policy) {
this.subDirectoryPolicy = policy;
return this;
}
/**
* The windowed filename method will construct filenames per window in the
* format of output-yyyyMMdd'T'HHmmss-001-of-100.txt.
*/
#Override
public ResourceId windowedFilename(ResourceId outputDirectory, WindowedContext c, String extension) {
Instant windowInstant = c.getWindow().maxTimestamp();
String datetimeStr = formatter.print(windowInstant.toDateTime());
// Remove the prefix when it is null so we don't append the literal 'null'
// to the start of the filename
String filenamePrefix = prefix.get() == null ? datetimeStr : prefix.get() + "-" + datetimeStr;
String filename = DefaultFilenamePolicy.constructName(
filenamePrefix,
shardTemplate.get(),
StringUtils.defaultIfBlank(suffix.get(), extension), // Ignore the extension in favor of the suffix.
c.getShardNumber(),
c.getNumShards());
String subDirectory = subDirectoryPolicy.get().format(windowInstant);
return outputDirectory
.resolve(subDirectory, StandardResolveOptions.RESOLVE_DIRECTORY)
.resolve(filename, StandardResolveOptions.RESOLVE_FILE);
}
/**
* Unwindowed writes are unsupported by this filename policy so an {#link UnsupportedOperationException}
* will be thrown if invoked.
*/
#Override
public ResourceId unwindowedFilename(ResourceId outputDirectory, Context c, String extension) {
throw new UnsupportedOperationException("There is no windowed filename policy for unwindowed file"
+ " output. Please use the WindowedFilenamePolicy with windowed writes or switch filename policies.");
}
}
In Beam currently the DefaultFilenamePolicy supports windowed writes, so there's no need to write a custom FilenamePolicy. You can control the output filename by putting W and P placeholders (for the window and pane respectively) in the filename template. This exists in the head beam repository, and will also be in the upcoming Beam 2.1 release (which is being released as we speak).

Delete all files created till yesterday in directory (but not directory) - one liner solution

How can I delete all older files created till yesterday in directory (but not directory)?
I tried with this but unable to get delete files exists till yesterday.
for(File file: new File(strFile).listFiles())
if (!file.isDirectory() && file.lastModified() < 1.0)
file.delete();
First lastModified returns:
A long value representing the time the file was last modified,
measured in milliseconds since the epoch (00:00:00 GMT, January 1,
1970), or 0L if the file does not exist or if an I/O error occurs
You need to get current time in milliseconds then subtract what last modified returns and the verify if it was modified before your target period. Or perform the time calculation in any type you desire.
Following your code:
long day = 1000 * 60 * 60 * 24;
long now = System.currentTimeMillis();
for(File file: new File(DIRECTORY).listFiles())
if (!file.isDirectory() && (now - file.lastModified() > day))
file.delete();
Ideally you would be running this as an scheduled task
here is an example which deletes all temp files more than a day old:
import org.apache.commons.lang3.Validate;
import java.io.File;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
public class StackOverflow44850006 {
/**
* deletes all the files in a directory modified before a given timestamp
*
* #param directoryPath path to delete from
* #param stamp everything with a mod date before this timestamp will be deleted
* #return the number of items deleted
*/
public static long deleteOld(final String directoryPath, final Date stamp) {
final File[] files = new File(directoryPath).listFiles();
Validate.notNull(files, "Unable to open dir. Does it exist?");
return Arrays.stream(files)
.filter(f -> !f.isDirectory())
.filter(f -> f.lastModified() < stamp.getTime())
.map(File::delete)
.filter(Boolean::booleanValue)
.count();
}
public static void main(String[] args) {
final String tempDir = System.getProperty("java.io.tmpdir");
final Calendar yesterday = Calendar.getInstance();
yesterday.add(Calendar.DATE, -1);
System.out.println(deleteOld(tempDir, yesterday.getTime()));
}
}

Countdown Timer in Android out of sync?

I am trying to make a countdown timer. I have came up with this code and initially I thought it worked. However when I run the app two things happen which I don't know how to fix...
It is out of sync, although it is the system Time and Date it said there was only 60 days left not 61 when I know it is 61!!
If I close the app and go back it resets the counters...
Any help with this would be appreciated, its not for anything particular just a personal project.
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView dateBx = (TextView) findViewById(R.id.textView1);
final TextView mTextField = (TextView) findViewById(R.id.mTextField);
final TextView minBx = (TextView) findViewById(R.id.minBx);
final TextView hourBx = (TextView) findViewById(R.id.hourBx);
//set todays date
DateFormat dateFormat1 = new SimpleDateFormat("dd/MM/yyyy");
Calendar cal1 = Calendar.getInstance();
//target date
DateFormat dateFormat2 = new SimpleDateFormat("dd/MM/yyyy");
Calendar cal2 = Calendar.getInstance();
cal2.set(2012,11,25);
//set maths
long time1 = cal1.getTimeInMillis();
long time2 = cal2.getTimeInMillis();
//difference variable
long diff = time2 - time1;
//equations
long diffSec = diff / 1000;
long dMins = diff / (60 * 1000);
long dHour = diff / (60 * 60 * 1000);
long dDay = diff / (24 * 60 * 60 * 1000);
new CountDownTimer(diff, 1000)
{
public void onTick(long millisUntilFinished)
{
mTextField.setText("Seconds remaining: " + millisUntilFinished / 1000);
dateBx.setText("Days remaining: " + millisUntilFinished / (24 * 60 * 60 * 1000));
minBx.setText("Minutes remaining: " + millisUntilFinished / (60 * 1000));
hourBx.setText("hours remaining: " + millisUntilFinished / (60 * 60 * 1000));
}
public void onFinish() {
mTextField.setText("done!");
}
}.start();
}
My code is a combination of some things that I learnt, in java and android, if you think of a better way of doing it all together then please let me know :)
Thanks
Android is not a Real Time OS so you can count that your Timer will run exactly every 1000 ms.
The most practical approach is, for each invocation, get the current date, get the target date, and recalculate the days/hours/minutes/seconds remaining (just as you did as the beginning of your app).
That would also solve your problem when closing/opening your app.
Here is an example how to sync the countDownTimer.
(as "onTick()" is not accurate according to Android)
The trick is to use only "onFinish()" and start a new timer using an Interface.
make a EXTENDED CLASS of countDownTimer:
public class oneShotTimer extends CountDownTimer {
TickListener invokeOnFinish; // Interface
public oneShotTimer (Context parent, int tickTime, TickListener contextPointer) {
super(tickTime, tickTime); // DEBUG: (/60)
....
}
At the OnFinish():
public void onFinish() {
invokeOnFinish.restartTimer();
}
public void onTick() {
Log.e("Timer", "Not suppose to happen");
}
Define an interface:
public interface TickListener {
public void restartTimer();
}
Now at the parent Object do the following:
1) Before starting the timer, get the time in MillinSecond -> "startTime";
2) Create the method for "TickListener::restartTimer" (I wrote psadu code)
#Override
public void restartTimer() {
// check if timer is done (make your own variable to monitor that)
// get the currentTime in MilliSecond
// calc TickTime , if we know that 30 tick are already counted
// there for ((30+1) x wantedTickTime) give us the target time for the next tick.
// TickTime = (31 x wantedTickTime) - (currentTime - startTime)
// for example, we want 1sec ticks, after 30 ticks it finished at 30.2 sec
// => (31 x 1) - (40.2 - 10) = 0.8 is the time we need to re start timer.
// re-run a new Timer with the new TickTime
}
Now let see how Java garbage collection will handle it.

Running several instances of Netbeans RCP application simultaneously

We have an application based on the netbeans rich client platform.
Default behaviour is that only one instance of the software can run, and this can be overridden by specifying a different user-dir as argument when starting the application.
Are there any alternatives to this? Our clients rely on being able to run several instances of the application.
(We would prefer if we didnt have to implement our own launcher that examines netbeans userdir for locks to pass alternative userdirs)
Is there a build-in way in the netbeans RCP to easily support multiple instances?
cheers
/B
So, unable to find a proper way to do this (seems like it will be available with nb 7 though through a new launcher input parameter), my workmate implemented it in a somewhat hackish way.
He created an alternative main-class, which checks and manipulates the user-dir property so each instance gets its own userdir.
Im still surprised that netbeans does not have this functionality!
import java.io.File;
import org.netbeans.Main;
/**
* Launches Netbeans Rich Client Platform application where mulitple instances of
* the application can run simultaneously for the same user.
* <p>
* Netbeans does not allow to run several instances of an application for the same
* user simultaneously, but this class does the job by fooling Netbeans.
* If an instance is already running then a new folder is created and used for this instance.
* </p>
* <p>
* This is quite tricky and this class is indeed a hack to override Netbeans behaviour.
* An example makes it a little easier to understand, when application is first started
* Netbeans userdir is set from a configuration file like {#code etc/xxxxx.conf} to something like:<br>
* {#code /home/username/.xxxxx/dev/} which includes a lock file.
* <br>
* If application is started again then this lock file is checked and Netbeans tries to connect to the other instance through a socket.
* This class does the trick by never using this folder but instead creating unique directories for each instance like:<br>
* {#code /home/username/.xxxxx/instance_01/netbeans/}<br>
* {#code /home/username/.xxxxx/instance_02/netbeans/}<br>
* {#code /home/username/.xxxxx/instance_03/netbeans/}<br>
* ...
* </p>
*
*/
public class MultipleInstancesMain
{
/** Key for Netbeans default user dir */
private static final String NETBEANS_USER = "netbeans.user";
/** Argument to Netbeans for alternate user dir */
private static final String USERDIR_ARG = "--userdir";
/** Like "C:\Documents and Settings\username\Application Data\.xxxxx" or "/home/username/.xxxxx" */
private static final File MAIN_DIR = getMainDir();
/** Sub dir of MAIN_DIR for each instance of application */
private static final String INSTANCE_DIR = "instance";
/** Sub dir of INSTANCE_DIR where netbeans stores it's settings, logs and cache */
private static final String NETBEANS_SUBDIR = "netbeans";
/** Name of Netbeans lock file inside of NETBEANS_SUBDIR */
private static final String LOCKFILE = "lock";
/** Max number of instance directories we allow */
private static final int MAX_DIR_COUNT = 999;
/** Padding of instance dir */
private static final String PAD = "000";
private static final int PAD_LENGTH = PAD.length();
/**
* Lock file could be present even though application is not running (after crash).
* So we treat it as "dead" after some time, to prevent "dead directories".
*/
private static final long LOCKFILE_MAX_AGE_IN_MILLIS = 1000L * 60L * 60L * 24L * 30L; // 30 days
/**
* Launches application with multiple instances support.
* #param args command line arguments
*/
public static void main(String[] args) throws Exception
{
// Get directory for this instance
String[] userDir = new String[2];
userDir[0] = USERDIR_ARG;
userDir[1] = getNetbeansDir();
// Remove default dir and set this class not to run again
deleteDefaultNetbeansDir();
System.clearProperty("netbeans.mainclass");
// Start Netbeans again with new userdir and default main class
startNetbeans(args, userDir);
}
/**
* Starts Netbeans.
* #param oldArgs command line arguments
* #param newArgs new arguments added
*/
private static void startNetbeans(String[] oldArgs, String[] newArgs) throws Exception
{
String[] args = new String[oldArgs.length + newArgs.length];
for (int i = 0; i <oldArgs.length; i++)
{
args[i] = oldArgs[i];
}
for (int i = 0; i < newArgs.length; i++)
{
args[oldArgs.length + i] = newArgs[i];
}
Main.main(args);
}
/**
* #return the directory that Netbeans will use for this instance of the application
*/
private static String getNetbeansDir()
{
for(int i = 1; i <= MAX_DIR_COUNT; i++)
{
File instanceDir = getSuffixedInstanceDir(i);
if (isLockFileFree(instanceDir))
{
File dirToUse = new File(instanceDir, NETBEANS_SUBDIR);
return dirToUse.getAbsolutePath();
}
}
// This would probably never happen, but we don't want an eternal loop above
String errorMessage = String.format("Unable to find Netbeans userdir, %s dirs checked in '%s'",
MAX_DIR_COUNT, MAIN_DIR.getAbsolutePath());
throw new RuntimeException(errorMessage);
}
private static File getSuffixedInstanceDir(int count)
{
String suffix = PAD + count;
suffix = suffix.substring(suffix.length() - PAD_LENGTH);
File suffixedDir = new File(MAIN_DIR, INSTANCE_DIR + "_" + suffix);
return suffixedDir;
}
/**
* Checks is if Netbeans lock file is free.
* #param instanceDir directory to look for Netbeans directory and lock file in
* #return true if {#code instanceDir} does not have a Netbeans folder with a occupied lock file
*/
private static boolean isLockFileFree(File instanceDir)
{
File netbeansDir = new File(instanceDir, NETBEANS_SUBDIR);
File lockFile = new File(netbeansDir, LOCKFILE);
if (lockFile.exists())
{
return deleteLockFileIfOldEnough(lockFile);
}
else
{
return true;
}
}
/**
* Deletes the lock file if it's old enough.
* #param lockFile lock file to delete
* #return true if it was deleted
* #see #LOCKFILE_MAX_AGE_IN_MILLIS
*/
private static boolean deleteLockFileIfOldEnough(File lockFile)
{
long currentTime = System.currentTimeMillis();
long fileCreated = lockFile.lastModified();
long ageInMillis = currentTime - fileCreated;
if (ageInMillis > LOCKFILE_MAX_AGE_IN_MILLIS)
{
return lockFile.delete();
}
else
{
return false;
}
}
/**
* Netbeans is started with a default userdir, but we need to have a unique dir for each instance.
* Main dir is the directory where directories of all instances are.
* #return main directory in users home area where application settings, logs and cache is stored
*/
private static File getMainDir()
{
String defaultNetbeansDir = System.getProperty(NETBEANS_USER);
File mainDir = new File(defaultNetbeansDir).getParentFile();
return mainDir;
}
/**
* Since we don't use default Netbeans dir at all, we remove it completely.
*/
private static void deleteDefaultNetbeansDir()
{
File defaultNetbeansDir = new File(System.getProperty(NETBEANS_USER));
Thread t = new Thread(new DirectoryDeleter(defaultNetbeansDir), "NetbeansDirDeleter");
t.start();
}
/**
* There are unpredictable behaviour when deleting Netbeans default directory, sometime it succeeds and sometimes not.
* But after some attempts it always succeeds, by deleting it in the background it will eventually be deleted.
* #author username
*/
private static class DirectoryDeleter implements Runnable
{
private static final long SLEEP_TIME = 3000;
private final File dirToDelete;
DirectoryDeleter(File dirToDelete)
{
this.dirToDelete = dirToDelete;
}
/**
* #see java.lang.Runnable#run()
*/
public void run()
{
while(!deleteDirOrFile(dirToDelete))
{
try
{
Thread.sleep(SLEEP_TIME);
}
catch (InterruptedException e)
{
// No idea to do anything here, should never happen anyway...
continue;
}
}
}
/**
* Deletes a file or directory
* #param dirFile directory or file to delete
* #return true if deletion succeeded
*/
private boolean deleteDirOrFile(File dirFile)
{
if (dirFile.isDirectory())
{
for (File f : dirFile.listFiles())
{
boolean deleted = deleteDirOrFile(f);
if (!deleted)
{
return false;
}
}
}
// The directory is now empty so delete it
return dirFile.delete();
}
}
}
I'm the mysterious developer who wrote the MultipleInstancesMain class above =)
As answer to the questions by Swati Sharma and uvaraj above, these notes might help:
(1) The alternative main class above should work for applications built on Netbeans Platform 6.5.1 (our does), other versions I don't know about.
(2) We keep this class in a tiny project that is built by the main build.xml ant script of our application (the one in the "suite" module). The jar is then copied to the /[appname]/modules/ folder (so it's on Netbeans classpath)
(3) The separate project can NOT be a Netbeans module, I guess there will be cyclic dependency or Netbeans classloader can't handle it or something like that.
(4) Our application is then started by adding parameter to configuration file /[appname]/etc/.conf:
default_options="-J-Dnetbeans.mainclass=com.appname.startup.MultipleInstancesMain"
Hope it helps
// Uhlén

Categories