public interface IDriver {
	public ISubscriberShim getSubscriber();
	/**
	 * Create a new XDS result document
	 * @return
	 */
	public XDSResultDocument newResultDoc();
	
	/**
	 * Get all the driver parameters
	 * @return
	 */
	public Map<? extends String, ? extends Parameter> getDriverParams();
	
	/**
	 * Append the source node to the given query doc
	 * @param result
	 */
	public void appendSourceInfo(WriteableDocument doc);
	
	/**
	 * Get the drivers RDN (=cn)
	 * @return
	 */
	public String getDriverInstanceName();
	
	/**
	 * Get the driver's schema
	 * @return
	 */
	public Object getSchema();
	
	/**
	 * Create a new command doc
	 * @return
	 */
	public XDSCommandDocument newCommandDoc();
	
	/**
	 * Get the association value for the given record
	 * @param record
	 * @return
	 */
	public String getAssociationField(Map<String, String> record);
	/**
	 * Get the source field for the given record
	 * @param record
	 * @return
	 */
	public String getSourceField(Map<String, String> record);
	
	/**
	 * Get a pre-configured ECMA script evaluator
	 * 
	 */
	public EcmascriptBuilder getEcmaBuilder();

} public interface IFileNameStrategy extends ISubscriberStrategy{

    /**
     * Get the new fileName (not including folder name!).
     * @param trace
     * @param driverParams
     * @throws XDSParameterException
     */    
    public String getNewFileName(Map<String,String> record);
} public interface IFileStartStrategy extends ISubscriberStrategy{

    /**
     * Determines whether or not a new file is required prior to writing the given record.
     * This can be called multiple times for the same record.
     * @param trace
     * @param driverParams
     * @throws XDSParameterException
     */    
    public boolean requiresNewFile(Map<String,String> record);

    
} public interface IFileWriteStrategy extends ISubscriberStrategy{
	/**
	 * Tells the reader to prepare for reading a given file. The previous file (if any) was processed completly.
	 * @param trace
	 * @param f
	 * @throws IOException
	 */
	public void openFile(File f) throws WriteException;
    /**
     * Reads one record from the file. Return null if no more records can be read from the file.
     * @param trace
     * @return a map of key/value pairs. The key is the fieldname (String), the value is the String value for that field. Value can be null.
     * @throws IOException
     */
    public void writeRecord( Map<String,String> m) throws WriteException;
    /**
     * Tells the reader that no more records will be read from this file (readRecord returned null). 
     * The reader should release all resources associated with the file.
     * @throws IOException
     */
    public File close() throws WriteException;
} public interface IPostProcessStrategy  extends ISubscriberStrategy{

	void doPostProcess(File result);

}
 public interface IShutdown {
	/**
	 * Notification of a shutdown of the driver
	 * @param trace
	 * @param reasonXML If any of error happens, this is the place to add it.
	 */
	public void onShutdown(XDSResultDocument reasonXML);

} public interface IStrategy {
	/**
	 * Get a Map of parameter definitions. Key=parameter name, value=Parameter object
	 * @param
	 * @return the map with parameter definitions
	 */
	public Map<String,Parameter> getParameterDefinitions();

} public interface ISubscriberFileListener {
	//Future use. Nothing yet implemented.
	public class FileEvent {

	}
	public void afterFileOpened(FileEvent event);
	public void afterFileClose(FileEvent event);
	public void afterRecordAdded(FileEvent event);
	public void beforeRecordAdded(FileEvent event);
} public interface ISubscriberShim {
	/**
	 * Forcefully close the current file. This file already places a synchronized block on the file lock. If this
	 * is the only file operation you inted to do, no need to add an additional synchronized block on the file lock.
	 * Used to be part of IFileOwnerCallback
	 * @return true if a file was actually closed. False otherwise.
	 * @throws WriteException
	 */
	public boolean finishFile() throws WriteException;
	/**
	 * Get the filelock that is used by this object to read and write to the file or close a new file.
	 * Used to be part of IFileOwnerCallback. Synchronize on this object to perform tests on hte current file.
	 * @return
	 */
	public Object getFileLock();
	/**
	 * Get the number of records in the current file or -1 if no current file.
	 */
	public abstract int getCurrentFileRecordCount();
	/**
	 * Does the subscriber currently have an open file
	 */
	public abstract boolean isFileOpen();

	/**
	 * Add a file operation listener to the subscriber
	 * @param listener
	 */
	public void addFileListener(ISubscriberFileListener listener);
	/**
	 * Remove a file operation listener from the subscriber
	 * @param listener
	 */
	public void removeFileListener(ISubscriberFileListener listener);
	/**
	 * Returns a data wrapper holding the configured connection information.
	 * @return
	 */
	public ConnectionInfo getConnectionInfo();
} public interface ISubscriberStrategy extends IStrategy{
    /**
     * Initialize this publisher strategy
     * @param trace
     * @param driverParams
     * @throws XDSParameterException
     */
    public void init(Trace trace, Map<String,Parameter> driverParams, IDriver driver) throws Exception;

} public class WriteException extends Exception {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	/**
	 * @param message
	 * @param cause
	 */
	public WriteException(String message, Exception cause) {
		super(message,cause);
	}
	/**
	 * @param cause
	 */
	public WriteException(Exception cause) {
		super(cause);
	}
} public class SimpleDateFormatFileNameStrategy extends AbstractStrategy implements IFileNameStrategy {
	//private static final String DATE_NAME_FORMAT_STRING = "simpleDateNamer_FormatString";
	//private static final String DATE_NAME_TIMEZONE = "simpleDateNamer_TimeZone";
	
	   private static enum Parameters implements IStrategyParameters{
		   DATE_NAME_FORMAT_STRING {
			@Override
			public String getParameterName() {
				return "simpleDateNamer_FormatString";
			}

			@Override
			public String getDefaultValue() {
				return "'NEW'_'FILE'_yyyyMMdd-HHmmssSSS.'out'";
			}

			@Override
			public Constraint[] getConstraints() {
				return new Constraint[]{RequiredConstraint.REQUIRED};
			}
		},
		   DATE_NAME_TIMEZONE {
			@Override
			public String getParameterName() {
				return "simpleDateNamer_TimeZone";
			}

			@Override
			public String getDefaultValue() {
				return "GMT";
			}

			@Override
			public Constraint[] getConstraints() {
				return null;
			}
		};
	        
			public abstract String getParameterName();


			public abstract String getDefaultValue();


			public DataType getDataType() {
				return DataType.STRING;
			}


			public abstract Constraint[] getConstraints();
	    }
	
	private String formatString;
	private TimeZone timeZone;
	
	/* (non-Javadoc)
	 * @see info.vancauwenberge.filedriver.api.INewFileNamer#init(com.novell.nds.dirxml.driver.Trace, java.util.Map)
	 */
	public void init(Trace trace, Map<String, Parameter> driverParams, IDriver driver)
			throws XDSParameterException {
		formatString = getStringValueFor(Parameters.DATE_NAME_FORMAT_STRING,driverParams);
		
		String strTimeZone = getStringValueFor(Parameters.DATE_NAME_TIMEZONE,driverParams);
		if ("".equals(strTimeZone))
			timeZone = TimeZone.getDefault();
		else
			timeZone = TimeZone.getTimeZone(strTimeZone);
	}
	/* (non-Javadoc)
	 * @see info.vancauwenberge.filedriver.api.INewFileNamer#getNewFileName(com.novell.nds.dirxml.driver.Trace, java.util.Map)
	 */
	public String getNewFileName(Map<String,String> record) {
		SimpleDateFormat formatter = new SimpleDateFormat(formatString);
		formatter.setTimeZone(timeZone);
		return formatter.format(new Date());
	}
	
	public static void main(String[] args){
		SimpleDateFormatFileNameStrategy sdf = new SimpleDateFormatFileNameStrategy();
		HashMap<String, Parameter> map = new HashMap<String, Parameter>();
        map.put(Parameters.DATE_NAME_FORMAT_STRING.getParameterName(), new Parameter(Parameters.DATE_NAME_FORMAT_STRING.getParameterName(), "'NEWER'_'FILE'_yyyy-MM-dd_HH-mm-ss_SSS", DataType.STRING));
        map.put(Parameters.DATE_NAME_TIMEZONE.getParameterName(), new Parameter(Parameters.DATE_NAME_TIMEZONE.getParameterName(), "GMT", DataType.STRING));
        try {
			sdf.init(null, map, null);
		} catch (XDSParameterException e) {
			e.printStackTrace();
		}
		System.out.println(sdf.getNewFileName(null));
	}
	@SuppressWarnings("unchecked")
	@Override
	public <E extends Enum<?> & IStrategyParameters> Class<E> getParametersEnum() {
		return (Class<E>) Parameters.class;
	}
} public class ExternalExec extends AbstractStrategy implements IPostProcessStrategy {

	private class LineScanner{
		private final String stringToMatch;
		private final int stringToMatchLen;
		private int currentIndex;

		public LineScanner(final String stringToMatch){
			this.stringToMatchLen = stringToMatch==null?-1:stringToMatch.length();
			this.stringToMatch = stringToMatch;
			currentIndex=0;
		}

		/**
		 * Test if thisChar matches the next char in triggerString.
		 * If as a result the complete string is matched, true is returned, otherwise false.
		 * @param thisChar
		 * @return true of a match was found, false otherwise
		 * @throws IOException
		 */
		public boolean nextChar(final char thisChar) throws IOException {
			if (stringToMatch != null){
				if (thisChar == stringToMatch.charAt(currentIndex)) {
					currentIndex++;
				} else {
					currentIndex=0;
				}
			}
			if (currentIndex == stringToMatchLen){
				currentIndex=0;//Start over again
				return true;
			}
			return false;
		}
	}
	enum Parameters implements IStrategyParameters{
		/**
		 * Close the file after <i>nnn</i> seconds of inactivity
		 */
		EXTERNALEXEC_MAXWAITTIMESECONDS{
			@Override
			public String getParameterName() {
				return "externalExec_maxWaitTimeSeconds";
			}

			@Override
			public String getDefaultValue() {
				return "0";
			}

			@Override
			public DataType getDataType() {
				return DataType.INT;
			}

			@Override
			public Constraint[] getConstraints() {
				//Divide by 100 because we need to multiply to get millis
				return new Constraint[]{new RangeConstraint(0, (Integer.MAX_VALUE/1000))};
			}
		},
		/**
		 * Use given cron string to close the file 
		 */
		EXTERNALEXEC_WORKDIR{
			@Override
			public String getParameterName() {
				return "externalExec_workDir";
			}

			@Override
			public String getDefaultValue() {
				return "";
			}

			@Override
			public DataType getDataType() {
				return DataType.STRING;
			}

			@Override
			public Constraint[] getConstraints() {
				return null;
			}
		},
		/**
		 * Field to manually indicate that a new file should be started or not
		 */
		EXTERNALEXEC_COMMAND{
			@Override
			public String getParameterName() {
				return "externalExec_command";
			}

			@Override
			public String getDefaultValue() {
				return "";
			}

			@Override
			public DataType getDataType() {
				return DataType.STRING;
			}

			@Override
			public Constraint[] getConstraints() {
				return null;
			}
		},
		/**
		 * Field to manually indicate that a new file should be started or not
		 */
		EXTERNALEXEC_USERNAMETRIGGER{
			@Override
			public String getParameterName() {
				return "externalExec_trigger_username";
			}

			@Override
			public String getDefaultValue() {
				return "";
			}

			@Override
			public DataType getDataType() {
				return DataType.STRING;
			}

			@Override
			public Constraint[] getConstraints() {
				return null;
			}
		},
		/**
		 * Field to manually indicate that a new file should be started or not
		 */
		EXTERNALEXEC_PASSWORDTRIGGER{
			@Override
			public String getParameterName() {
				return "externalExec_trigger_password";
			}

			@Override
			public String getDefaultValue() {
				return "";
			}

			@Override
			public DataType getDataType() {
				return DataType.STRING;
			}

			@Override
			public Constraint[] getConstraints() {
				return null;
			}
		},
		/**
		 * Field to manually indicate that a new file should be started or not
		 */
		EXTERNALEXEC_CONNECTURLTRIGGER{
			@Override
			public String getParameterName() {
				return "externalExec_trigger_connectURL";
			}

			@Override
			public String getDefaultValue() {
				return "";
			}

			@Override
			public DataType getDataType() {
				return DataType.STRING;
			}

			@Override
			public Constraint[] getConstraints() {
				return null;
			}
		};

		@Override
		public abstract String getParameterName();
		@Override
		public abstract String getDefaultValue();
		@Override
		public abstract DataType getDataType();
		@Override
		public abstract Constraint[] getConstraints();

	}

	private ProcessBuilder pb=null;
	private List<String> commandAndParams=null;
	private Trace trace;
	private int maxWaitTime;
	private ConnectionInfo connectioninfo;
	private String userNameTriggerLine;
	private String passwordTriggerLine;
	private String connectURLTriggerLine;

	@Override
	public void init(final Trace trace, final Map<String, Parameter> driverParams, final IDriver driver) throws Exception {
		this.trace = trace;
		this.connectioninfo = driver.getSubscriber().getConnectionInfo();
		this.maxWaitTime = getIntValueFor(Parameters.EXTERNALEXEC_MAXWAITTIMESECONDS, driverParams);
		final String command = getStringValueFor(Parameters.EXTERNALEXEC_COMMAND,driverParams);
		if (!"".equals(command)){
			commandAndParams = new ArrayList<String>();
			//Parse the command as a CSV line with space as a separator
			final CSVFileParser parser = new CSVFileParser(' ', new String[]{}, true, false, false);
			parser.resetParser();
			parser.doParse(new StringReader(command));
			final Map<String,String> fields = parser.getQueue().getNextRecord();
			//loop over the fields and get the values
			for (int i = 0; i < fields.size(); i++) {
				final String value = fields.get("field"+i);
				if (! "".equals(value)) {
					commandAndParams.add(value);
				}
			}
			pb = new ProcessBuilder();
			pb.redirectErrorStream(true);
			final String workDir = getStringValueFor(Parameters.EXTERNALEXEC_WORKDIR,driverParams);
			if (!"".equals(workDir)) {
				final File workdirFile = new File(workDir);
				if (!workdirFile.isDirectory()) {
					throw new IllegalArgumentException("The workdirectory is not a directory.");
				}
				pb.directory(workdirFile);
			}else{
				//On the actual exec, it will be set to the parent of the output file (=the output folder)
			}
			//Get the trigger lines (if any)
			this.userNameTriggerLine = getStringValueFor(Parameters.EXTERNALEXEC_USERNAMETRIGGER,driverParams);
			if ((userNameTriggerLine != null) && userNameTriggerLine.equals("")) {
				userNameTriggerLine = null;
			}
			this.passwordTriggerLine = getStringValueFor(Parameters.EXTERNALEXEC_PASSWORDTRIGGER,driverParams);
			if ((passwordTriggerLine != null) && passwordTriggerLine.equals("")) {
				passwordTriggerLine = null;
			}
			this.connectURLTriggerLine = getStringValueFor(Parameters.EXTERNALEXEC_CONNECTURLTRIGGER,driverParams);
			if ((connectURLTriggerLine != null) && connectURLTriggerLine.equals("")) {
				connectURLTriggerLine = null;
			}

			if (trace.getTraceLevel()>=TraceLevel.TRACE){
				trace.trace("Initialization completed:", TraceLevel.TRACE);
				trace.trace("Post processes environment: "+pb.environment(), TraceLevel.TRACE);
				trace.trace("Post processes command and arguments: "+commandAndParams, TraceLevel.TRACE);
				trace.trace("Post processes workDir: "+workDir, TraceLevel.TRACE);
				trace.trace("Post processes max process wait time: "+maxWaitTime, TraceLevel.TRACE);
			}
		}
	}

	@Override
	public void doPostProcess(final File result) {
		try{
			if (pb != null){
				//Clone the commandAndArguments and search replace the tokens
				//$PARENTPATH$ C:\temp
				//$FILENAME$   out.csv
				//$FILEPATH$   C:\temp\out.csv
				//$CONNECTUSER$ userName
				//$CONNECTPASSWORD$   password
				//$CONNECTURL$   URL
				final String fileName = result.getName();//Name only
				final String filePath = result.getCanonicalPath();//Full path (folder+name)
				final File parentPathFile = result.getParentFile();
				final String parentPath = parentPathFile.getCanonicalPath();//Name only

				final List<String> commands = new ArrayList<String>(commandAndParams.size());
				for (final String aCmd : commandAndParams) {
					String thisValue = aCmd.replace("$PARENTPATH$", parentPath)
							.replace("$FILENAME$", fileName)
							.replace("$FILEPATH$", filePath);
					if (connectioninfo != null) {
						thisValue = thisValue.replace("$CONNECTUSER$", connectioninfo.getUserName())
								.replace("$CONNECTPASSWORD$", connectioninfo.getPassword())
								.replace("$CONNECTURL$", connectioninfo.getConnectURL());
					}
					commands.add(thisValue);
				}
				if (trace.getTraceLevel()>=TraceLevel.TRACE){
					trace.trace("Command and arguments:"+commands, TraceLevel.TRACE);
				}
				//Set the working directory if not yet set
				if (pb.directory()==null) {
					pb.directory(parentPathFile);
				}
				pb.command(commands);
				trace.trace("Process starting", TraceLevel.TRACE);
				final Process process = pb.start();

				//Read the result in a different thread: it is blocking!!!
				final Thread th = new Thread("postProcessReader"){
					@Override
					public void run() {
						try{
							//trace.trace("Start reading");
							final OutputStream os = process.getOutputStream();
							final OutputStreamWriter wr = new OutputStreamWriter(os);
							//Even if not tracing, we need to clear the buffer or the process might lock
							final InputStream is = process.getInputStream();
							final InputStreamReader isr = new InputStreamReader(is);
							final char[] buffer = new char[1024];
							int numCharsRead;

							final StringBuilder aLine = new StringBuilder();
							final LineScanner userNameScanner = new LineScanner(userNameTriggerLine);
							final LineScanner passwordScanner = new LineScanner(passwordTriggerLine);
							final LineScanner urlScanner = new LineScanner(connectURLTriggerLine);
							while((numCharsRead = isr.read(buffer)) > 0) {
								for (int c = 0; c < numCharsRead; c++) {
									final char thisChar = buffer[c];
									//System.out.println(thisChar);
									String stringToWrite=null;
									if (userNameScanner.nextChar(thisChar)){
										stringToWrite=connectioninfo.getUserName();
									}
									if (passwordScanner.nextChar(thisChar)){
										stringToWrite=connectioninfo.getPassword();
									}
									if (urlScanner.nextChar(thisChar)){
										stringToWrite=connectioninfo.getConnectURL();
									}
									if (stringToWrite != null){
										wr.write(stringToWrite );
										wr.write("\n");
										wr.flush();//We need to flush!!!!
										//Trace
										aLine.append(thisChar);
										trace.trace(aLine + stringToWrite , TraceLevel.TRACE);
										aLine.setLength(0);
									}else if (thisChar=='\n'){
										//Trace
										trace.trace(aLine.toString(), TraceLevel.TRACE);
										aLine.setLength(0);
									} else {
										//Trace
										aLine.append(thisChar);
									}
								}
							}
							trace.trace(aLine.toString(), TraceLevel.TRACE);
							isr.close();
							wr.close();
						}catch(final IOException e){
							Util.printStackTrace(trace, e);
						}
					}
				};
				th.start();
				//waitFor with timeOut is only since java 1.8
				final boolean terminated = waitFor18(process,maxWaitTime);
				if (terminated){
					trace.trace("Process terminated with exit value "+process.exitValue(), TraceLevel.TRACE);
				}else{
					trace.trace("WARN: process did not finish after "+maxWaitTime+" seconds.", TraceLevel.ERROR_WARN);
					trace.trace("If this happens to often, you might get a cumulative effect. To prevent this, increase the max wait time.", TraceLevel.ERROR_WARN);
				}
			}
		}catch(final Exception e){
			trace.trace("Unhandled exception while executing the external postprocessing command for file "+result, TraceLevel.ERROR_WARN);
			Util.printStackTrace(trace, e);
		}
	}


	/**
	 * Process.waitFor is only implemented since java 1.8
	 * For pre-java-8 compatibility, we do something ourself
	 * @param proces
	 * @param timeout
	 * @return
	 * @throws InterruptedException
	 */
	private static boolean waitFor18(final Process proces, final long timeout) throws InterruptedException{
		final long startTime = System.nanoTime();
		long rem = TimeUnit.SECONDS.toNanos(timeout);

		do {
			try {
				proces.exitValue();
				return true;
			} catch(final IllegalThreadStateException ex) {
				//System.out.println(rem);
				if (rem > 0) {
					Thread.sleep(
							Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 200));
				}
			}
			rem = TimeUnit.SECONDS.toNanos(timeout) - (System.nanoTime() - startTime);
		} while (rem > 0);
		return false;
	}



	@SuppressWarnings("unchecked")
	@Override
	public <E extends Enum<?> & IStrategyParameters> Class<E> getParametersEnum() {
		return (Class<E>) Parameters.class;
	}

}
 public class BasicNewFileDecider extends AbstractStrategy
		implements IFileStartStrategy, IShutdown, ISubscriberFileListener {
	/**
	 * positive seconds assumes that it needs to be converted to milliseconds,
	 * so it sets the max value to MAX_VALUE/1000
	 */
	private static final RangeConstraint positiveSecondsRange = new RangeConstraint(0, (Integer.MAX_VALUE / 1000));

	private class InactivityMonitor extends Thread {
		boolean keepRunning = true;

		public InactivityMonitor() {
			setDaemon(true);
			setName("InactivityMonitor");
		}

		@Override
		public void run() {
			trace.trace("InactivityMonitor - Starting.", TraceLevel.TRACE);
			synchronized (subscriber.getFileLock()) {
				while (keepRunning) {
					try {
						trace.trace("InactivityMonitor - Waiting for " + (fileInactiveSaveInterval / 1000)
								+ " seconds of inactivity.", TraceLevel.TRACE);
						subscriber.getFileLock().wait(fileInactiveSaveInterval);
						trace.trace("InactivityMonitor - Awake after " + (fileInactiveSaveInterval / 1000)
								+ " seconds of inactivity.", TraceLevel.TRACE);
						if (keepRunning && subscriber.finishFile()) {
							trace.trace("InactivityMonitor - Closed file after " + (fileInactiveSaveInterval / 1000)
									+ " seconds of inactivity.", TraceLevel.TRACE);
						}
					} catch (final InterruptedException e) {
						trace.trace("InactivityMonitor - Interupted", TraceLevel.TRACE);
					} catch (final WriteException e) {
						trace.trace("InactivityMonitor - Unable to close file:" + e.getMessage(),
								TraceLevel.ERROR_WARN);
						Util.printStackTrace(trace, e);
					}
				}
			}
		}

		public void notifyActivity() {
			this.interrupt();
		}

		public void killMonitor() {
			keepRunning = false;
			this.interrupt();
		}

	}

	private class MaxFileAgeMonitor extends Thread {

		public MaxFileAgeMonitor() {
			super();
			setDaemon(true);
			setName("MaxFileAgeMonitor");
		}

		@Override
		public void run() {
			trace.trace("MaxFileAgeMonitor - Starting.", TraceLevel.TRACE);
			synchronized (subscriber.getFileLock()) {
				try {
					trace.trace("MaxFileAgeMonitor - Waiting for " + (maxFileAge / 1000) + " seconds after cration.",
							TraceLevel.TRACE);
					subscriber.getFileLock().wait(maxFileAge);
					trace.trace("MaxFileAgeMonitor - Awake after " + (maxFileAge / 1000) + " seconds after cration.",
							TraceLevel.TRACE);
					if (subscriber.finishFile()) {
						trace.trace("MaxFileAgeMonitor - Closed file after " + (maxFileAge / 1000)
								+ " seconds of creation.", TraceLevel.TRACE);
					}
				} catch (final InterruptedException e) {
					trace.trace("MaxFileAgeMonitor - Interupted", TraceLevel.TRACE);
				} catch (final WriteException e) {
					trace.trace("MaxFileAgeMonitor - Unable to close file:" + e.getMessage(), TraceLevel.ERROR_WARN);
					Util.printStackTrace(trace, e);
				}
			}
			trace.trace("MaxFileAgeMonitor - Stopped.", TraceLevel.TRACE);
		};

		public void killMonitor() {
			synchronized (this) {
				this.interrupt();
			}
		}
	}

	private enum Parameters implements IStrategyParameters {
		/**
		 * The file should not contain more then <i>nnn</i> records
		 */
		NEW_FILE_MAX_RECORDS {
			@Override
			public String getParameterName() {
				return "newFile_MaxRecords";
			}

			@Override
			public String getDefaultValue() {
				return "100";
			}

			@Override
			public DataType getDataType() {
				return DataType.INT;
			}

			@Override
			public Constraint[] getConstraints() {
				return new Constraint[] { RangeConstraint.NON_NEGATIVE };
			}
		},
		/**
		 * Close the file after <i>nnn</i> seconds of adding the first record
		 */
		NEW_FILE_MAX_FILE_AGE {
			@Override
			public String getParameterName() {
				return "newFile_MaxFileAge";
			}

			@Override
			public String getDefaultValue() {
				return "60";
			}

			@Override
			public DataType getDataType() {
				return DataType.INT;
			}

			@Override
			public Constraint[] getConstraints() {
				return new Constraint[] { positiveSecondsRange };
			}
		},
		/**
		 * Close the file after <i>nnn</i> seconds of inactivity
		 */
		NEW_FILE_INACTIVE_SAVE_INTERVAL {
			@Override
			public String getParameterName() {
				return "newFile_InactiveSaveInterval";
			}

			@Override
			public String getDefaultValue() {
				return "0";
			}

			@Override
			public DataType getDataType() {
				return DataType.INT;
			}

			@Override
			public Constraint[] getConstraints() {
				return new Constraint[] { positiveSecondsRange };
			}
		},
		/**
		 * Use given cron string to close the file
		 */
		NEW_FILE_CRON_EXPRESSION {
			@Override
			public String getParameterName() {
				return "newFile_CronExpression";
			}

			@Override
			public String getDefaultValue() {
				return "";
			}

			@Override
			public DataType getDataType() {
				return DataType.STRING;
			}

			@Override
			public Constraint[] getConstraints() {
				return null;
			}
		},
		/**
		 * Field to manually indicate that a new file should be started or not
		 */
		NEW_FILE_FIELD {
			@Override
			public String getParameterName() {
				return "newFile_FieldName";
			}

			@Override
			public String getDefaultValue() {
				return "";
			}

			@Override
			public DataType getDataType() {
				return DataType.STRING;
			}

			@Override
			public Constraint[] getConstraints() {
				return null;
			}
		};

		@Override
		public abstract String getParameterName();

		@Override
		public abstract String getDefaultValue();

		@Override
		public abstract DataType getDataType();

		@Override
		public abstract Constraint[] getConstraints();

	}

	private String fieldName;
	private int maxRecordCount = 0;
	private int maxFileAge = 0;
	private MaxFileAgeMonitor maxFileAgeMonitor;
	private InactivityMonitor fileInactivityMonitor;

	private int fileInactiveSaveInterval;
	private ISubscriberShim subscriber;
	private String cronExpression;
	private org.quartz.Scheduler cronScheduler = null;
	private Trace trace;

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * info.vancauwenberge.filedriver.api.IStrategy#init(com.novell.nds.dirxml.
	 * driver.Trace, java.util.Map, info.vancauwenberge.filedriver.api.IDriver)
	 */
	@Override
	public void init(final Trace trace, final Map<String, Parameter> driverParams, final IDriver driver)
			throws Exception {
		this.trace = trace;

		this.subscriber = driver.getSubscriber();
		this.subscriber.addFileListener(this);
		maxRecordCount = getIntValueFor(Parameters.NEW_FILE_MAX_RECORDS, driverParams);
		// Integer.parseInt(driverParams.get(NEW_FILE_MAX_RECORDS).toString());
		maxFileAge = getIntValueFor(Parameters.NEW_FILE_MAX_FILE_AGE, driverParams) * 1000;
		// Integer.parseInt(driverParams.get(NEW_FILE_MAX_FILE_AGE).toString())
		// * 1000;
		fileInactiveSaveInterval = getIntValueFor(Parameters.NEW_FILE_INACTIVE_SAVE_INTERVAL, driverParams) * 1000;
		;
		// Integer.parseInt(driverParams.get(NEW_FILE_INACTIVE_SAVE_INTERVAL).toString())
		// * 1000;
		cronExpression = getStringValueFor(Parameters.NEW_FILE_CRON_EXPRESSION, driverParams);
		// driverParams.get(NEW_FILE_CRON_EXPRESSION).toString();
		fieldName = getStringValueFor(Parameters.NEW_FILE_FIELD, driverParams);
		// driverParams.get(NEW_FILE_FIELD).toString();

		// We can only initialize quartz when the rest is initialized.
		initQuartz();
		// Create file monitor if required:
		// if fileInactiveSaveInterval is set (>0)
		// and if the interval is smaller then the 'live' time of a file
		// (fileSaveInterval)
		if (fileInactiveSaveInterval > 0) {
			if ((maxFileAge == 0) | (fileInactiveSaveInterval < maxFileAge)) {
				fileInactivityMonitor = new InactivityMonitor();
				fileInactivityMonitor.start();
			} else {
				trace.trace(
						"Warn: file inactivity monitor set but not used since fileInactiveSaveInterval("
								+ fileInactiveSaveInterval + ") > maxFileAge(" + maxFileAge + ")",
						TraceLevel.ERROR_WARN);
			}
		}

		if (trace.getTraceLevel() >= TraceLevel.TRACE) {
			trace.trace("Initialization completed:", TraceLevel.TRACE);
			trace.trace(" MaxRecordCount:" + maxRecordCount + ((maxRecordCount == 0) ? "(disabled)" : "(enabled)"),
					TraceLevel.TRACE);
			trace.trace(" maxFileAge:" + maxFileAge + ((maxFileAge == 0) ? "(disabled)" : "(enabled)"),
					TraceLevel.TRACE);
			trace.trace(" fileInactiveSaveInterval:" + fileInactiveSaveInterval
					+ ((fileInactivityMonitor == null) ? "(disabled)" : "(enabled)"), TraceLevel.TRACE);
			trace.trace(" cronExpression:" + cronExpression, TraceLevel.TRACE);
			trace.trace(" fieldName:" + fieldName, TraceLevel.TRACE);
		}

	}

	/*
	 * Initialize the quartz schedular if needed
	 */
	private void initQuartz() throws Exception {
		if (cronScheduler == null) {
			// Do the test here, otherwise quartz will be imported.
			if ((cronExpression != null) && !"".equals(cronExpression.trim())) {
				cronScheduler = QuartzUtil.initQuartz(trace, cronExpression.trim(), subscriber, this);
			} else {
				trace.trace("No cron string defined. Skipping Quartz.", TraceLevel.TRACE);
			}
		}
	}

	/**
	 * Reschedule the maximum file age monitor (stop the old monitor and start a
	 * new one).
	 */
	/*
	 * private void rescheduleMaxAgeMonitor(){ //Only do something if
	 * fileInactiveSaveInterval is set (>0) // and if the interval is smaller
	 * then the 'live' time of a file (fileSaveInterval) if (maxFileAge>0){ if
	 * (maxFileAgeMonitor!=null) //We had one before. Finish it.
	 * maxFileAgeMonitor.interrupt(); else { maxFileAgeMonitor = new Thread(){
	 * public void run(){ synchronized(subscriber.getFileLock()){ while (true){
	 * try { subscriber.getFileLock().wait(maxFileAge); if
	 * (subscriber.finishFile()) trace.trace("Closed file "+(maxFileAge/1000)+
	 * " seconds after creation.",TraceLevel.TRACE); } catch
	 * (InterruptedException e) { trace.trace(getName()+ " Interupted"
	 * ,TraceLevel.TRACE); } catch (WriteException e) { trace.trace(
	 * "Unable to close file:"+e.getMessage()); } } } } };
	 * maxFileAgeMonitor.setDaemon(true);
	 * maxFileAgeMonitor.setName("MaxFileAgeMonitor");
	 * maxFileAgeMonitor.start(); } }
	 *
	 * }
	 */
	/**
	 * Reschedule the inactivity monitor (stop the old monitor and start a new
	 * one).
	 */
	/*
	 * private void rescheduleInactivityMonitor(){ //Only do something if
	 * fileInactiveSaveInterval is set (>0) // and if the interval is smaller
	 * then the 'live' time of a file (fileSaveInterval) if
	 * ((fileInactiveSaveInterval>0) & (fileInactiveSaveInterval<maxFileAge)){
	 * if (fileInactivityMonitor!=null) //We had one before. Finish it.
	 * fileInactivityMonitor.interrupt(); else{ //Save the file after nnn
	 * seconds of inactivity fileInactivityMonitor = new Thread(){ public void
	 * run(){ synchronized(subscriber.getFileLock()){ while(true){ try {
	 * subscriber.getFileLock().wait(fileInactiveSaveInterval); if
	 * (subscriber.finishFile()) trace.trace("Closed file after "
	 * +(fileInactiveSaveInterval/1000)+" seconds of inactivity."
	 * ,TraceLevel.TRACE); } catch (InterruptedException e) {
	 * trace.trace(getName()+ " Interupted",TraceLevel.TRACE); } catch
	 * (WriteException e) { trace.trace("Unable to close file:"
	 * +e.getMessage(),TraceLevel.ERROR_WARN); } } } }
	 *
	 * }; fileInactivityMonitor.setDaemon(true);
	 * fileInactivityMonitor.setName("FileInactivityMonitor");
	 * fileInactivityMonitor.start(); } }
	 *
	 * }
	 */

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * info.vancauwenberge.filedriver.api.INewFileDecider#requiresNewFile(com.
	 * novell.nds.dirxml.driver.Trace, java.util.Map) Returns true when nnn
	 * records have been saved in the file (nnn=value of parameter
	 * NEW_FILE_MAX_RECORDS)
	 */
	@Override
	public boolean requiresNewFile(final Map<String, String> record) {
		trace.trace("requiresNewFile?", TraceLevel.TRACE);
		synchronized (subscriber.getFileLock()) {
			if (subscriber.getCurrentFileRecordCount() <= 0) {// always return
																// true when no
																// records have
																// been saved
																// yet!
				trace.trace("No records have been written yet to the file.", TraceLevel.TRACE);
				return true;
			} else {
				final String value = record.get(fieldName);
				if (value != null) {// If we have a value, use that algorithm
					if ("true".equals(value)) {
						trace.trace("Closing file after setting '" + fieldName + "' to 'true'.", TraceLevel.TRACE);
						return true;
					} else {
						trace.trace("Not closing file after explicit setting of '" + fieldName + "' to NOT 'true'.",
								TraceLevel.TRACE);
						return false;
					}
				} else {// We do not have a value, use the maxFileRecords
						// algorithm (if not zero)
					if ((maxRecordCount != 0) && (subscriber.getCurrentFileRecordCount() >= maxRecordCount)) {
						trace.trace("Closing file after " + maxRecordCount + " records have been added.",
								TraceLevel.TRACE);
						return true;
					} else {
						trace.trace("Not required to close the file.", TraceLevel.TRACE);
						return false;
					}
				}
			}
		}
	}

	public String getCronExpression() {
		return cronExpression;
	}

	@Override
	public void onShutdown(final XDSResultDocument reasonXML) {
		subscriber.removeFileListener(this);
		if (maxFileAgeMonitor != null) {
			maxFileAgeMonitor.killMonitor();
		}
		if (fileInactivityMonitor != null) {
			fileInactivityMonitor.killMonitor();
		}

		if (cronScheduler != null) {
			try {
				cronScheduler.shutdown();
			} catch (final Exception e) {
				final StatusAttributes attrs = StatusAttributes.factory(StatusLevel.FATAL, StatusType.DRIVER_STATUS,
						null); // event-id
				XDSUtil.appendStatus(reasonXML, // do to append to
						attrs, // status attribute values
						null, // description
						e, // exception
						true, // append stack trace?
						null); // xml to append
				Util.printStackTrace(new Trace(""), e);
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public <E extends Enum<?> & IStrategyParameters> Class<E> getParametersEnum() {
		return (Class<E>) Parameters.class;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see info.vancauwenberge.filedriver.api.ISubscriberFileListener#
	 * afterFileOpened(info.vancauwenberge.filedriver.api.
	 * ISubscriberFileListener.FileEvent)
	 */
	@Override
	public void afterFileOpened(final FileEvent event) {
		if (maxFileAge > 0) {
			if (maxFileAgeMonitor != null) {
				maxFileAgeMonitor.killMonitor();
			}
			maxFileAgeMonitor = new MaxFileAgeMonitor();
			maxFileAgeMonitor.start();
		}

	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * info.vancauwenberge.filedriver.api.ISubscriberFileListener#afterFileClose
	 * (info.vancauwenberge.filedriver.api.ISubscriberFileListener.FileEvent)
	 */
	@Override
	public void afterFileClose(final FileEvent event) {
		if (maxFileAgeMonitor != null) {
			maxFileAgeMonitor.killMonitor();
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see info.vancauwenberge.filedriver.api.ISubscriberFileListener#
	 * afterRecordAdded(info.vancauwenberge.filedriver.api.
	 * ISubscriberFileListener.FileEvent)
	 */
	@Override
	public void afterRecordAdded(final FileEvent event) {
		if ((maxRecordCount != 0) && (subscriber.getCurrentFileRecordCount() >= maxRecordCount)) {
			trace.trace(maxRecordCount + " records have been added. Trying to close the file.", TraceLevel.TRACE);
			try {
				if (subscriber.finishFile()) {
					trace.trace("Closed file after " + maxRecordCount + " records have been added.", TraceLevel.TRACE);
				}
			} catch (final WriteException e) {
				Util.printStackTrace(trace, e);
			}

		}
	}

	@Override
	public void beforeRecordAdded(final FileEvent event) {
		if (fileInactivityMonitor != null) {
			fileInactivityMonitor.notifyActivity();
		}
	}
} public class DummyFileWriter implements IFileWriteStrategy{
	private File theFile;
	private OutputStreamWriter osw;
	private Trace trace;
	/* (non-Javadoc)
	 * @see info.vancauwenberge.filedriver.api.IFileWriteStrategy#getParameterDefinitions()
	 */
	public Map<String,Parameter> getParameterDefinitions() {
		return new HashMap<String,Parameter>();
	}

	/* (non-Javadoc)
	 * @see info.vancauwenberge.filedriver.api.IFileWriteStrategy#init(com.novell.nds.dirxml.driver.Trace, java.util.Map)
	 */
	public void init(Trace trace, Map<String,Parameter> driverParams, IDriver driver) throws XDSParameterException {
		this.trace = trace;
	}

	/* (non-Javadoc)
	 * @see info.vancauwenberge.filedriver.api.IFileWriteStrategy#openFile(com.novell.nds.dirxml.driver.Trace, java.io.File)
	 */
	public void openFile(File f) throws WriteException {
		try {
			FileOutputStream fos = new FileOutputStream(f);
			osw = new OutputStreamWriter(fos,"UTF-8");
			theFile = f;
		} catch (Exception e) {
			Util.printStackTrace(trace,e);
			throw new WriteException(e);
		}
	}

	/* (non-Javadoc)
	 * @see info.vancauwenberge.filedriver.api.IFileWriteStrategy#writeRecord(com.novell.nds.dirxml.driver.Trace, java.util.Map)
	 */
	public void writeRecord(Map<String,String> m) throws WriteException {
		try {
			osw.write(m.toString()+"\n");
		} catch (IOException e) {
			Util.printStackTrace(trace,e);
			throw new WriteException(e);
		}
	}

	/* (non-Javadoc)
	 * @see info.vancauwenberge.filedriver.api.IFileWriteStrategy#close()
	 */
	public File close() throws WriteException {
		try {
			osw.close();
			return theFile;
		} catch (IOException e) {
			Util.printStackTrace(trace,e);
			throw new WriteException(e);
		}
	}
} public class GenericFileDriverShim
extends AbstractDriverShim
implements DriverShim, IDriver
{

	//this constant identifies the channel/module being traced
	//  (e.g., Skeleton Driver\Driver)
	private static final String TRACE_SUFFIX = "GenFileDrv";


	/**
	 * All driver parameters
	 * Public for testclass access
	 */
	public enum DriverParam{
		//Driver specific parameters
		/**
		 * Schema to use. Note: we cannot use the filter to derive the schema: the filter has no defined sequence.
		 */
		SCHEMA("schema","LastName,FirstName,Title,Email,WorkPhone,Fax,WirelessPhone,Description",DataType.STRING, RequiredConstraint.REQUIRED),
		/**
		 * Object class to use in the generated events.
		 */
		OBJECT_CLASS("objectClass","User",DataType.STRING,RequiredConstraint.REQUIRED),
		/**
		 * Ecmascript creating the association
		 */
		ASSOCIATION_FIELD("association-field-script",null,DataType.STRING),
		/**
		 * Include Ecmascript libraries
		 */
		INCLUDE_ECMA_LIBRARIES("include-ecma-libraries","false",DataType.BOOLEAN),
		/**
		 * Ecmascript for creating the src-dn
		 */
		SOURCE_FIELD("source-field-script",null,DataType.STRING),
		/**
		 * Handling of special characters for field names in the above Ecma scripts
		 */
		JS_PROCESS("js-processing",EcmascriptBuilder.JsProcessing.NOPROCESSING.toString(),DataType.STRING,RequiredConstraint.REQUIRED,EcmascriptBuilder.JsProcessing.values()),
		/**
		 * Replacement char for when {@link GenericFileDriverShim.DriverParam.JS_PROCESS} is configured as {@link EcmaUtil.JsProcessing.NOPROCESSING}
		 */
		JS_REPLACEMENT_CHAR("js-replacement-char","_",DataType.CHAR),
		//Authentication parameters => do we need them???
		//SERVER(DTD.TAG_SERVER,null,DataType.STRING),
		//USER(DTD.TAG_USER,null,DataType.STRING),
		//PASSWORD(DTD.TAG_PASSWORD,null,DataType.STRING)
		;

		private final String paramName;
		private final String defaultValue;
		private final List<Constraint> cons;
		private final DataType dataType;

		private DriverParam(final String paramName, final String defaultValue, final DataType type){
			this(paramName,defaultValue,type,null,null);
		}

		private DriverParam(final String paramName, final String defaultValue, final DataType type, final Constraint cons){
			this(paramName,defaultValue,type,cons,null);
		}

		private DriverParam(final String paramName, final String defaultValue, final DataType type, final Constraint cons, final Object[] enumConstraint){
			this.paramName = paramName;
			this.defaultValue = defaultValue;
			this.cons=new LinkedList<Constraint>();
			if (cons != null) {
				this.cons.add(cons);
			}
			if (enumConstraint != null){
				final EnumConstraint enumC = new EnumConstraint();
				for (final Object object : enumConstraint) {
					enumC.addLiteral(object.toString());
				}
				this.cons.add(enumC);
			}
			this.dataType = type;
		}

		public String getParamName() {
			return paramName;
		}

		public String getDefaultValue() {
			return defaultValue;
		}

		public List<Constraint> getConstraints() {
			return cons;
		}

		public DataType getDataType() {
			return dataType;
		}
	}
	//put the tag names of the parameters here
	/*    private static final String TAG_SCHEMA = "schema";
    private static final String TAG_OBJECT_CLASS = "objectClass";
    private static final String TAG_ASSOCIATION_FIELD = "association-field-script";
    private static final String TAG_SOURCE_FIELD = "source-field-script";
    private static final String TAG_JS_PROCESS = "js-processing";
    private static final String TAG_JS_REPLACEMENT_CHAR = "js-replacement-char";

    //put the default values for the parameters here
    private static final String DEFAULT_SCHEMA = "LastName,FirstName,Title,Email,WorkPhone,Fax,WirelessPhone,Description";
    private static final String DEFAULT_OBJECT_CLASS = "User";
    private static final String DEFAULT_SERVER = null;
    private static final String DEFAULT_USER = null;
    private static final String DEFAULT_PASSWORD = null;
	private static final String DEFAULT_ASSOCIATION_FIELD = null;
	private static final String DEFAULT_SOURCE_FIELD = null;
	private static final String DEFAULT_JS_PROCESS = JsProcessing.NOPROCESSING.toString();
	private static final String DEFAULT_JS_REPLACEMENT_CHAR = "_";
	 */
	//MODIFY:  put the name of your application here
	private static final String APPLICATION_NAME = "Generic File Driver";

	/**
	 *  Object implementing
	 *  <code>com.novell.nds.dirxml.driver.SubscriptionShim</code>,
	 *  which is supplied to the DirXML engine via the
	 *  <code>getSubscriptionShim()</code> method.
	 *  <p>
	 *  @see #getSubscriptionShim()
	 */
	private FileDriverSubscriptionShim subscriptionShim = null;

	/**
	 *  Object implementing
	 *  <code>com.novell.nds.dirxml.driver.PublicationShim</code>,
	 *  which is supplied to the DirXML engine via the
	 *  <code>getPublicationShim()</code> method.
	 *  <p>
	 *  @see #getPublicationShim()
	 */
	private info.vancauwenberge.filedriver.shim.FileDriverPublicationShim publicationShim = null;

	/**
	 * The object class that this driver is supposed to handle.
	 */
	private String objectClass;

	private String associationField;

	private boolean includeEcmaLibraries = false;

	private String[] schema;


	private String sourceField;

	private EcmascriptBuilder.JsProcessing jsProcessingMode;
	private String jsIdentifierReplacement;

	/*
	 * All driver parameters as received during initialization
	 */
	private Map<String,Parameter> driverParams;

	private EcmascriptBuilder ecmaBuilder;

	/**
	 *  A Java driver shim <code>must</code> have a constructor which takes
	 *  no parameters.  The DirXML engine uses this to construct the driver
	 *  object.
	 */
	public GenericFileDriverShim()
	{
		//create temporary trace object until we know what the driver's
		//  RDN is
		setDriverRDN(TRACE_ID);
	}//SkeletonDriverShim()


	/**
	 *  A non-interface method that describes the parameters
	 *  this DriverShim is expecting.
	 *  <p>
	 *  @see GenericFileDriverShim#init(XmlDocument)
	 */
	@Override
	protected Map<String,Parameter> getParameterDefs()
	{
		//The XDS.jar library automatically checks parameter data
		//types for you.  When a RequiredConstraint is added to a parameter,
		//the library will check init documents to ensure the parameter is
		//present and has a value.  When you add RangeConstraints or
		//EnumConstraints to a parameter, the library will check parameter
		//values to see if they satisfy these constraints.
		trace.trace("getInitialDriverParams start",TraceLevel.TRACE);
		final DriverParam[] parameters = DriverParam.values();

		final HashMap<String,Parameter> driverParams = new HashMap<String,Parameter>(parameters.length);
		for (final DriverParam driverParam : parameters) {
			final Parameter param = new Parameter(driverParam.getParamName(), //tag name
					driverParam.getDefaultValue(), //default value
					driverParam.getDataType()); //data type
			final List<Constraint> cons = driverParam.getConstraints();
			for (final Constraint constraint : cons) {
				param.add(constraint);
			}
			driverParams.put(param.tagName(), param);			
		}
		trace.trace("getInitialDriverParams done",TraceLevel.TRACE);
		return driverParams;
	}//setDriverParams():void


	/**
	 *  <code>init</code> will be called after a driver is instantiated to allow
	 *  it to perform any necessary initialization before event processing begins.
	 *  Typically it will perform any setup that is common to both the subscriber
	 *  and publisher channels.
	 *  <p>
	 *  @param initXML XML document that contains the driver's
	 *      initialization parameters
	 *  @return an XML document containing status messages for this
	 *      operation
	 */
	@Override
	public XmlDocument init(final XmlDocument initXML)
	{
		//MODIFY:  initialize your driver here

		//example initialization document:

		/*
        <nds dtdversion="1.1" ndsversion="8.6">
            <source>
                <product version="1.1a">DirXML</product>
                <contact>Novell, Inc.</contact>
            </source>
            <input>
                <init-params src-dn="\NEW_DELL_TREE\NOVELL\Driver Set\Skeleton Driver (Java, XDS)">
                    <authentication-info>
                        <server>server.app:400</server>
                        <user>User1</user>
                    </authentication-info>
                    <driver-options>
                <option-1 display-name="Sample String option">This is a string</option-1>
                <option-2 display-name="Sample int option (enter an integer)">10</option-2>
                <option-3 display-name="Sample boolean option (enter a boolean value)">true</option-3>
                <option-4 display-name="Sample required option (enter some value)">not null</option-4>
            </driver-options>
                </init-params>
            </input>
        </nds>
		 */

		//example result document:

		/*
        <nds dtdversion="1.1" ndsversion="8.6">
            <source>
                <product build="20021214_0304" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                <contact>My Company Name</contact>
            </source>
            <output>
                <status level="success" type="driver-status">
                    <parameters>
                        <option-1 display-name="Sample String option">This is a string</option-1>
                        <option-2 display-name="Sample int option (enter an integer)">10</option-2>
                        <option-3 display-name="Sample boolean option (enter a boolean value)">true</option-3>
                        <option-4 display-name="Sample required option (enter some value)">not null</option-4>
                        <password display-name="password"><!-- content suppressed -->
                        </password>
                        <server display-name="server">server.app:400</server>
                        <user display-name="user">User1</user>
                    </parameters>
                </status>
            </output>
        </nds>
		 */

		trace.trace("init start", TraceLevel.TRACE);

		//create result document for reporting status to DirXML engine
		//can't add source info yet since we don't have the driver's RDN
		final XDSResultDocument result = new XDSResultDocument();
		StatusAttributes attrs;


		try
		{
			//parse initialization document
			final XDSInitDocument init = new XDSInitDocument(initXML);
			final XDSStatusElement status;


			setDriverRDN(init.rdn());
			setDriverSrcDN(init.srcDN());
			//create new trace object that uses driver's RDN
			setTrace(TRACE_SUFFIX);
			//append a <source> element now that we know the driver's RDN
			appendSourceInfo(result);

			//get driver parameters from the initialization doc
			//Field constraints are validated by XDS => XDSException
			driverParams = getParameterDefs();//getShimParameters();
			init.parameters(driverParams);

			//Get our data. Mandatory fields have been validated by XDS (RequiredConstraint.REQUIRED)
			objectClass = driverParams.get(DriverParam.OBJECT_CLASS.getParamName()).toString();
			Object param = driverParams.get(DriverParam.ASSOCIATION_FIELD.getParamName());
			if (param!= null) {
				associationField=param.toString();
			}

			param = driverParams.get(DriverParam.SOURCE_FIELD.getParamName());
			if (param!= null) {
				sourceField=param.toString();
			}
			schema = getSchemaAsArray(driverParams);

			//JS identifier processing
			param = driverParams.get(DriverParam.JS_PROCESS.getParamName());
			if (param!= null) {
				jsProcessingMode=EcmascriptBuilder.JsProcessing.valueOf(param.toString());
			}
			param = driverParams.get(DriverParam.JS_REPLACEMENT_CHAR.getParamName());
			if (param!= null) {
				jsIdentifierReplacement=param.toString();
			}

			param = driverParams.get(DriverParam.INCLUDE_ECMA_LIBRARIES.getParamName());
			if (param != null) {
				includeEcmaLibraries = Boolean.parseBoolean(param.toString());
			}

			//get the filter
			final DriverFilter driverFilter = init.driverFilter();
			trace.trace("Filter:"+driverFilter,TraceLevel.TRACE);
			if (driverFilter!=null){
				final ClassFilter classFilter = driverFilter.getClassFilter(objectClass);
				trace.trace("ClassFilter:"+classFilter,TraceLevel.TRACE);
				if (classFilter!= null) {
					trace.trace("ClassFilter:"+classFilter.toString(),TraceLevel.TRACE);
				}
			}


			//create a publisher and subscriber instance
			subscriptionShim = new FileDriverSubscriptionShim(this);
			publicationShim = new FileDriverPublicationShim(this);

			//perform any other initialization that might be required

			//append a successful <status> element to the result doc
			attrs = StatusAttributes.factory(StatusLevel.SUCCESS,
					StatusType.DRIVER_STATUS,
					null); //event-id
			status = XDSUtil.appendStatus(result, //doc to append to
					attrs, //status attribute values
					null); //description
			//append the parameter values the driver is actually using
			status.parametersAppend(driverParams);
		}//try
		catch (final Exception e) //don't want to catch Error class with Throwable
		{
			Util.printStackTrace(trace,e);
			//e instance of XDSException:
			//
			//  init document is malformed or invalid -- or --
			//  it is missing required parameters or contains
			//  illegal parameter values

			//e instance of RuntimeException:

			//  e.g., NullPointerException

			attrs = StatusAttributes.factory(StatusLevel.FATAL,
					StatusType.DRIVER_STATUS,
					null); //event-id
			XDSUtil.appendStatus(result, //doc to append to
					attrs, //status attribute values
					null, //description
					e, //exception
					XDSUtil.appendStackTrace(e), //append stack trace?
					initXML); //xml to append
		}//catch

		//return result doc w/ status to DirXML engine
		trace.trace("init done", TraceLevel.TRACE);
		return result.toXML();
	}//init(XmlDocument):XmlDocument


	/**
	 *  <code>shutdown</code> indicates to the DriverShim that
	 *  the driver is being terminated.
	 *  <p>
	 *  @param reasonXML unused
	 *  @return an XML document containing status messages for this
	 *      operation
	 */
	@Override
	public XmlDocument shutdown(final XmlDocument reasonXML)
	{
		//MODIFY:  put your shutdown code here

		//example result document:

		/*
        <nds dtdversion="1.1" ndsversion="8.6">
            <source>
                <product build="20021214_0304" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                <contact>My Company Name</contact>
            </source>
            <output>
                <status level="success" type="driver-status"/>
            </output>
        </nds>
		 */

		trace.trace("shutdown start", TraceLevel.TRACE);

		final XDSResultDocument result = newResultDoc();
		StatusAttributes attrs;

		try
		{
			//shutdown whatever needs shutting down
			//  which includes telling the publication shim to
			//  return control back to the DirXML engine and
			//  telling the subscription shim to free resources

			if (publicationShim != null)
			{
				publicationShim.shutdown(result);
			}

			if (subscriptionShim != null)
			{
				subscriptionShim.shutdown(result);
			}

			//TODO: test to see if we already have a status element (eg: an error from the shutdown somewhere)

			//append a successful <status> element to the result doc
			attrs = StatusAttributes.factory(StatusLevel.SUCCESS,
					StatusType.DRIVER_STATUS,
					null); //event-id
			XDSUtil.appendStatus(result, //doc to append to
					attrs, //status attribute values
					null); //description
		}//try
		catch (final Exception e) //don't want to catch Error class with Throwable
		{
			//something bad happened...
			attrs = StatusAttributes.factory(StatusLevel.FATAL,
					StatusType.DRIVER_STATUS,
					null); //event-id
			XDSUtil.appendStatus(result, //do to append to
					attrs, //status attribute values
					null, //description
					e, //exception
					true, //append stack trace?
					null); //xml to append
		}//catch

		//return the result doc w/ status to DirXML engine
		trace.trace("shutdown done", TraceLevel.TRACE);
		return result.toXML();
	}//shutdown(XmlDocument):XmlDocument


	/**
	 *  <code>getSubscriptionShim</code> gets the implementation of
	 *  SubscriptionShim that will be used to process commands on behalf of
	 *  the DirXML engine.
	 *  <p>
	 *  NOTE: the returned instance will be initialized by the DirXML engine
	 *  <p>
	 *  @see FileDriverSubscriptionShim#init(XmlDocument)
	 *  @return an instance of  SubscriptionShim that will be used to process
	 *      commands on behalf of the DirXML engine
	 */
	@Override
	public SubscriptionShim getSubscriptionShim()
	{
		trace.trace("getSubscriptionShim", TraceLevel.TRACE);

		return subscriptionShim;
	}//getSubscriptionShim():SubscriptionShim


	/**
	 *  <code>getPublicationShim</code> gets the implementation of
	 *  PublicationShim that will be invoked by the DirXML engine.
	 *  <p>
	 *  NOTE: the returned instance will be initialized by the DirXML engine
	 *  <p>
	 *  @see FileDriverPublicationShimImpl#init(XmlDocument)
	 *  @return an instance of PublicationShim that will be invoked by
	 *  the DirXML engine
	 */
	@Override
	public PublicationShim getPublicationShim()
	{
		trace.trace("getPublicationShim", TraceLevel.TRACE);

		return publicationShim;
	}//getPublicationShim():PublicationShim

	@Override
	public ISubscriberShim getSubscriber(){
		return subscriptionShim;
	}

	/**
	 *  <code>getSchema</code> returns the application schema encoded
	 *  in XDS XML.
	 *  <p>
	 *  This will be called only when the driver is not running. In other
	 *  words, if this method is called init()/shutdown() will not be called
	 *  for the current instance of the DriverShim.
	 *  <p>
	 *  @param initXML XML document containing the driver shim
	 *      initialization parameters as well as the subscription shim and
	 *      publication shim initialization parameters.
	 *  @return XML document containing the application schema or an
	 *      error status
	 */
	@Override
	public XmlDocument getSchema(final XmlDocument initXML)
	{
		//MODIFY:  get your application schema here

		//example initialization document:

		/*
        <nds dtdversion="1.1" ndsversion="8.6">
            <source>
                <product version="1.1a">DirXML</product>
                <contact>Novell, Inc.</contact>
            </source>
            <input>
                <init-params src-dn="\NEW_DELL_TREE\NOVELL\Driver Set\Skeleton Driver (Java, XDS)">
                    <authentication-info>
                        <server>server.app:400</server>
                        <user>User1</user>
                    </authentication-info>
                    <driver-options>
                <option-1 display-name="Sample String option">This is a string</option-1>
                <option-2 display-name="Sample int option (enter an integer)">10</option-2>
                <option-3 display-name="Sample boolean option (enter a boolean value)">true</option-3>
                <option-4 display-name="Sample required option (enter some value)">not null</option-4>
            </driver-options>
                    <subscriber-options>
                <sub-1 display-name="Sample Subscriber option">String for Subscriber</sub-1>
            </subscriber-options>
                    <publisher-options>
                <pub-1 display-name="Sample Publisher option">String for Publisher</pub-1>
                <polling-interval display-name="Polling interval in seconds">10</polling-interval>
            </publisher-options>
                </init-params>
            </input>
        </nds>
		 */

		//example result document:

		/*
        <nds dtdversion="1.1" ndsversion="8.6">
            <source>
                <product build="20021214_0304" instance="Skeleton Driver (Java, XDS)" version="1.1">DirXML Skeleton Driver (Java, XDS)</product>
                <contact>My Company Name</contact>
            </source>
            <output>
                <schema-def application-name="Skeleton Application" hierarchical="true">
                    <class-def class-name="fake-class-1" container="true">
                        <attr-def attr-name="fake-attr-1" case-sensitive="true" multi-valued="true" naming="true" read-only="true" required="true" type="string"/>
                    </class-def>
                    <class-def class-name="fake-class-2" container="false">
                        <attr-def attr-name="fake-attr-2" case-sensitive="false" multi-valued="false" naming="false" read-only="false" required="false" type="int"/>
                    </class-def>
                </schema-def>
                <status level="warning" type="driver-general">
                    <description>Get schema not implemented.</description>
                </status>
            </output>
        </nds>
		 */
		trace.trace("getSchema start", TraceLevel.TRACE);

		//create result document for reporting status to DirXML engine
		//can't add source info yet since we don't have the driver's instance name
		final XDSSchemaResultDocument result = new XDSSchemaResultDocument();
		StatusAttributes attrs;

		try
		{
			final XmlDocument initResultDoc = init(initXML);
			final StatusLevel level = Util.getFirstStatus(initResultDoc);

			if (level.compareTo(StatusLevel.WARNING) < 0){
				//Oops, some error occured. Return the error.
				result.empty();
				attrs = StatusAttributes.factory(level,
						StatusType.DRIVER_STATUS,
						null); //event-id

				//Search for the description
				final String descr = Util.getFirstStatusDescription(initXML);
				XDSUtil.appendStatus(result, //doc to append to
						attrs, //status attribute values
						descr, //description
						null, //exception
						false, //append stack trace?
						initXML); //xml to append
			}
			else //Init was succesfull
			{
				/*
       			 XDSInitDocument init;

				//parse initialization document
				 init = new XDSInitDocument(initXML);

				//append a <source> element to the result document
				 setDriverRDN(init.rdn());
				 setTrace(TRACE_SUFFIX);
				 */
				appendSourceInfo(result);

				//get driver parameters from the initialization doc
				//init.parameters(driverParams);

				//MODIFY:  put your application's schema in the result document

				XDSSchemaDefElement schemaDef; //the <schema-def> element from the result doc
				XDSClassDefElement userClassDef; //a <class-def> element from the result doc
				XDSAttrDefElement attrDef; //an <attr-def> element from the result doc

				//a driver that supports a real application would do whatever is
				//necessary to create an XDS schema description document and
				//return it; for example, it might create an instance of the
				//Subscriber object and query the application schema; if the
				//application has an invariant schema, the driver might just
				//have an embedded XML document describing the schema in XDS
				//terms; this embedded document could then be returned to DirXML
				//wrapped by an XmlDocument object
				//
				//however, since we are just a skeleton, return a fake schema with
				//a warning that this method isn't really implemented

				schemaDef = result.appendSchemaDefElement();
				schemaDef.setApplicationName(APPLICATION_NAME);
				schemaDef.setHierarchical(true);
				userClassDef = schemaDef.appendClassDefElement();
				userClassDef.setClassName(objectClass);
				userClassDef.setContainer(true);
				//for (int i = 0; i < schema.length; i++) {
				for (final String aField : schema) {
					attrDef = userClassDef.appendAttrDefElement();
					attrDef.setAttrName(aField);
					attrDef.setMultiValued(false);
				}
				//Let the publisher extend the schema if needed
				publicationShim.extendSchema(userClassDef,initXML);
				attrs = StatusAttributes.factory(StatusLevel.SUCCESS,
						StatusType.DRIVER_GENERAL,
						null); //event-id
				XDSUtil.appendStatus(result, //doc to append to
						attrs,  //status attribute values
						null);  //description
			}
		}//try
		catch (final Exception e) //don't want to catch Error class with Throwable
		{
			Util.printStackTrace(trace, e);
			//e instance of XDSException:
			//
			//  init document is malformed or invalid -- or --
			//  it is missing required parameters or contains
			//  illegal parameter values

			//e instance of RuntimeException:

			//  e.g., NullPointerException

			//clean out partial schema
			result.empty();
			attrs = StatusAttributes.factory(StatusLevel.FATAL,
					StatusType.DRIVER_STATUS,
					null); //event-id
			XDSUtil.appendStatus(result, //doc to append to
					attrs, //status attribute values
					null, //description
					e, //exception
					XDSUtil.appendStackTrace(e), //append stack trace?
					initXML); //xml to append
		}//catch

		trace.trace("getSchema done", TraceLevel.TRACE);
		//return result doc w/ schema and status to DirXML engine
		return result.toXML();
	}//getSchema(XmlDocument):XmlDocument

	/**
	 * Upon starting of the publisher, do whatever additional initialization that is required.
	 * @param processor
	 * @throws XDSParseException
	 */
	public void onStart(final XmlCommandProcessor processor){
		//Get the linked ecma script
		try{
			final Map<Integer, String> jsLibraries;
			if (includeEcmaLibraries){
				jsLibraries = getLinkedEcmaLibraries(processor);
			}else{
				jsLibraries = Collections.emptyMap();
			}
			this.ecmaBuilder = createEcmaBuilder(processor, jsLibraries);
		}catch (final Exception e) {
			//This should not happen, but you never know.
			//IF it happens, we do not load the libraries
			Util.printStackTrace(trace, e);
			trace.trace("Driver's ecmascript libraries not loaded.", 0);
			try {
				this.ecmaBuilder = createEcmaBuilder(processor,null);
			} catch (final XDSParseException e1) {
			}
		}
	}


	/**
	 * Create an ecma builder by loading the given ecma libraries into it's scope.
	 * @param processor
	 * @param jsLibraries
	 * @return
	 * @throws XDSParseException
	 */
	@SuppressWarnings("unchecked")
	private EcmascriptBuilder createEcmaBuilder(final XmlCommandProcessor processor,
			final Map<Integer, String> jsLibraries) throws XDSParseException {

		final EcmascriptBuilder ecmaBuilder = new EcmascriptBuilder(jsProcessingMode, jsIdentifierReplacement);

		if ((jsLibraries == null) || jsLibraries.isEmpty()) {
			return ecmaBuilder;
		}

		final Set<Integer> keys = jsLibraries.keySet();

		final List<Integer> list = new ArrayList<Integer>(keys);
		java.util.Collections.sort(list);

		for (final Integer index : list) {
			//Get the content of the library
			final ArrayList<String> readAttrDetails = new ArrayList<String>();
			readAttrDetails.add("DirXML-Data");
			final XDSQueryDocument queryDetail = Util.createQueryDoc(null, jsLibraries.get(index), null, QueryScope.ENTRY, readAttrDetails, null);
			final XmlDocument responseDetail = processor.execute(queryDetail.toXML(), null);
			final XDSQueryResultDocument resultDetail = new XDSQueryResultDocument(responseDetail);
			final List<XDSInstanceElement> instancesDetail =resultDetail.extractInstanceElements();
			if ((instancesDetail != null) && (instancesDetail.size()==1)){
				final XDSInstanceElement singleton = instancesDetail.get(0);
				final List<XDSAttrElement> attrs = singleton.extractAttrElements();
				if (attrs != null){
					for (final XDSAttrElement xdsAttrElement : attrs) {
						if ("DirXML-Data".equals(xdsAttrElement.getAttrName())){
							final List<XDSValueElement> values = xdsAttrElement.extractValueElements();
							for (final XDSValueElement xdsValueElement : values) {
								byte[] decoded;
								try {
									decoded = Base64Codec.decode(xdsValueElement.extractText());
									ecmaBuilder.addSharedLibrary(new String(decoded,"UTF-8"), jsLibraries.get(index));									
								} catch (final IOException e) {
									//This should not happen, so we just log in case it does.
									trace.trace("Failed to decode the given octet string. The ECMAscript library '"+jsLibraries.get(index)+"'will not be included.",TraceLevel.ERROR_WARN);
								}

							}
						}
					}
				}
			}
		}


		return ecmaBuilder;
	}


	/**
	 * Create a map with the ECMA library references.
	 * Key = index (the list of libraries is ordered)
	 * Value = the DN to the library
	 * @param processor
	 * @return
	 * @throws XDSParseException
	 */
	@SuppressWarnings("unchecked")
	private Map<Integer, String> getLinkedEcmaLibraries(
			final XmlCommandProcessor processor) throws XDSParseException {
		final List<String> readAttr = new ArrayList<String>();
		readAttr.add("DirXML-Policies");
		final XDSQueryDocument query = Util.createQueryDoc("DirXML-Driver", getDriverSrcDN(), null, QueryScope.ENTRY, readAttr, null);
		final XmlDocument response = processor.execute(query.toXML(), null);
		final XDSQueryResultDocument result = new XDSQueryResultDocument(response);
		final List<XDSInstanceElement> instances =result.extractInstanceElements();
		//We should have exactly 1 instance
		final Map<Integer, String> jsLibraries = new HashMap<Integer, String>();
		if ((instances != null) && (instances.size()==1)){
			final XDSInstanceElement singleton = instances.get(0);
			final List<XDSAttrElement> attrs = singleton.extractAttrElements();
			if (attrs != null) {
				for (final XDSAttrElement xdsAttrElement : attrs) {
					if ("DirXML-Policies".equals(xdsAttrElement.getAttrName())){
						final List<XDSValueElement> values = xdsAttrElement.extractValueElements();
						for (final XDSValueElement xdsValueElement : values) {
							final List<XDSComponentElement> components = xdsValueElement.extractComponentElements();
							String dn=null;
							String index=null;
							String path=null;
							//Policies have 3 components: dn,level and interval
							for (final XDSComponentElement xdsComponentElement : components) {
								if(xdsComponentElement.getName().equals("level")) {
									index=xdsComponentElement.extractText();
								} else if(xdsComponentElement.getName().equals("dn")) {
									dn=xdsComponentElement.extractText();
								} else if(xdsComponentElement.getName().equals("interval")) {
									path=xdsComponentElement.extractText();
								}
							}
							if ("3".equals(path)){//"3" indicates an ecmescript library
								trace.trace("Found ECMAscript library:"+dn,TraceLevel.DEBUG);
								jsLibraries.put(Integer.parseInt(index), dn);
							}else{
								trace.trace("Non ECMAscript policy:"+dn,TraceLevel.DEBUG);								
							}
						}
					}
				}
			}
		}
		//If we found any libraries: get the content and process them
		return jsLibraries;
	}


	@Override
	public String getAssociationField(final Map<String,String> thisRecord) {
		if ((associationField != null) && !associationField.equals("")){
			trace.trace("Calculating association value using ecmascript '"+associationField+"'",TraceLevel.TRACE);
			try{
				//final Object result = EcmaUtil.evaluateEcmaWithParams(trace, jsProcessingMode, jsIdentifierReplacement, thisRecord,associationField,DriverParam.ASSOCIATION_FIELD.getParamName());
				final Object result = ecmaBuilder.evaluateEcmaWithParams(trace, thisRecord,associationField,DriverParam.ASSOCIATION_FIELD.getParamName());
				trace.trace("Calculated association: "+ result,TraceLevel.TRACE);
				String assValue = Context.toString(result);
				if (assValue != null) {
					assValue = assValue.trim();
				}
				if ("".equals(assValue)) {
					assValue=null;
				}
				return assValue;
			}catch(final Exception e){
				final StringBuilder error = new StringBuilder("Error while calculating the association using ecmascript :'");
				error.append(associationField).append("'. ").append(e.getClass().getName()).append(":").append(e.getMessage()).append(".").append("Record read:").append(thisRecord);
				final String errorStr = error.toString();
				trace.trace(errorStr, TraceLevel.ERROR_WARN);
				throw new IllegalArgumentException(errorStr, e);
			}
		}
		else{
			trace.trace("No association script set.",TraceLevel.TRACE);
			return null;
		}
	}


	@Override
	public String getSourceField(final Map<String,String> thisRecord) {
		if ((sourceField != null) && !sourceField.equals("")){
			trace.trace("Calculating src-dn value using ecmascript '"+sourceField+"'",TraceLevel.TRACE);
			try{
				//final Object result = EcmaUtil.evaluateEcmaWithParams(trace, jsProcessingMode, jsIdentifierReplacement, thisRecord,sourceField,DriverParam.SOURCE_FIELD.getParamName());
				final Object result = ecmaBuilder.evaluateEcmaWithParams(trace, thisRecord,sourceField,DriverParam.SOURCE_FIELD.getParamName());
				trace.trace("Calculated src-dn: "+ result,TraceLevel.TRACE);
				String srcValue = Context.toString(result);
				if (srcValue != null){
					srcValue = srcValue.trim().replaceAll("[+=,\\.\\\\]", "-");
					srcValue=srcValue.trim();
				}
				if ("".equals(srcValue)) {
					srcValue=null;
				}
				return srcValue;
			}catch(final Exception e){
				final StringBuilder error = new StringBuilder("Error while calculating the src-dn value using ecmascript :'");
				error.append(sourceField).append("'. ").append(e.getClass().getName()).append(":").append(e.getMessage()).append(".").append("Record read:").append(thisRecord);
				final String errorStr = error.toString();
				trace.trace(errorStr, TraceLevel.ERROR_WARN);
				throw new IllegalArgumentException(errorStr, e);
			}
		}
		else{
			trace.trace("No src-dn script set.",TraceLevel.TRACE);
			return null;
		}
	}


	@Override
	public String[] getSchema() {
		return schema;
	}


	public String getObjectClass() {
		return objectClass;
	}


	@Override
	public Map<String,Parameter> getDriverParams() {
		return driverParams;
	}

	/*
	 * Parse the schema out of the parametermap as an array of strings
	 */
	public static String[] getSchemaAsArray(final Map<String,Parameter> parammap){
		final String tempFieldNames = parammap.get(DriverParam.SCHEMA.getParamName()).toString();
		return tempFieldNames.split(","); //We always have at least 1 element (RequiredConstraint.REQUIRED)
	}

	/**
	 * Get a preconfigured ECMA script evaluator
	 */
	@Override
	public EcmascriptBuilder getEcmaBuilder() {
		return ecmaBuilder;
	}

}//class SeletonDriverShim public class Errors
{

    public static final String INVALID_DOC      = "Document is invalid.";
    public static final String NO_ASSOC         = "Missing required association.";
	public static final String FILE_READ_ERROR  = "An error occured reading the input file.";
	public static final String FILE_WRITE_ERROR = "An error occured writing the output file.";
	public static final String PROCESS_ERROR    = "An error occured while processing a record.";
	public static final String FATAL            = "Fatal error. Stopping driver.";

}//class Errors public class TraceLevel {
	/**
	 * Exception level (0)
	 */
	public static final int EXCEPTION = 0;
	/**
	 * Error and warning messages (1)
	 */
	public static final int ERROR_WARN = 1;
	/**
	 * Debugging information (descisions taken etc)
	 */
	public static final int DEBUG = 2;
	/**
	 * TRACE level: method entry/exit
	 */
	public static final int TRACE = 3;
} public class Util {
	static final boolean supportsJava7Move;

	static {
		// Calculate supportsJava7Move using java reflection
		boolean methodFound = false;
		try {
			Class<?> c = Class.forName("java.nio.file.Files");
			Method moveMethod = c.getMethod("move", new Class[] { Class.forName("java.nio.file.Path"),
					Class.forName("java.nio.file.Path"), Class.forName("[Ljava.nio.file.CopyOption;") });
			methodFound = (moveMethod != null);
		} catch (Throwable e) {
			methodFound = false;
		}
		supportsJava7Move = methodFound;
	}

	static public StatusLevel getStatusLevel(String level) {
		if (StatusLevel.FATAL.equals(level)) {
			return StatusLevel.FATAL;
		}
		if (StatusLevel.ERROR.equals(level)) {
			return StatusLevel.ERROR;
		}
		if (StatusLevel.WARNING.equals(level)) {
			return StatusLevel.WARNING;
		}
		if (StatusLevel.SUCCESS.equals(level)) {
			return StatusLevel.SUCCESS;
		}
		if (StatusLevel.RETRY.equals(level)) {
			return StatusLevel.RETRY;
		} else {
			return null;
		}

	}

	/**
	 * Get the first statuslevel found in the given XmlDocument or null when no
	 * status level can be found.
	 *
	 * @param xmlDoc
	 * @return
	 */
	static public StatusLevel getFirstStatus(XmlDocument xmlDoc) {
		StatusLevel level = null;
		NodeList statusFields = xmlDoc.getDocument().getElementsByTagName("status");

		if (statusFields.getLength() > 0) {
			// We have at least one status element
			Node aStatus = statusFields.item(0);
			Node levelNode = aStatus.getAttributes().getNamedItem("level");
			if (levelNode != null) {
				level = getStatusLevel(levelNode.getNodeValue());
			}
		}
		return level;
	}

	/**
	 * Get the description of the first status level found in the given
	 * XmlDocument or null when no description is associated with the first
	 * status level.
	 *
	 * @param xmlDoc
	 * @return the description
	 */
	static public String getFirstStatusDescription(XmlDocument xmlDoc) {
		String descr = null;
		NodeList statusFields = xmlDoc.getDocument().getElementsByTagName("status");

		if (statusFields.getLength() > 0) {
			// We have at least one status element
			Node aStatus = statusFields.item(0);
			NodeList statusChilds = aStatus.getChildNodes();
			int size = statusChilds.getLength();
			for (int i = 0; (i < size); i++) {
				Node aChild = statusChilds.item(i);
				if (aChild.getLocalName().equals("description")) {
					descr = aChild.getFirstChild().getNodeValue();
					break;
				}

			}
		}
		return descr;
	}

	/**
	 * Get the worst found status level from the XmlDocument (or SUCCESS when no
	 * status level can be found)
	 *
	 * @param xmlDoc
	 * @return
	 */
	static public StatusLevel getWorstStatus(XmlDocument xmlDoc) {
		StatusLevel level = StatusLevel.SUCCESS;
		NodeList statusFields = xmlDoc.getDocument().getElementsByTagName("status");
		int length = statusFields.getLength();
		for (int i = 0; i < length; i++) {
			Node aStatus = statusFields.item(i);
			Node levelNode = aStatus.getAttributes().getNamedItem("level");
			if (levelNode != null) {
				StatusLevel thisLevel = getStatusLevel(levelNode.getNodeValue());
				if (thisLevel.compareTo(level) < 0) {
					level = thisLevel;
				}
			}
		}
		return level;
	}

	public static void main(String[] args) {
		System.out.println(StatusLevel.ERROR.compareTo(StatusLevel.WARNING));
		System.out.println(getSystemDefaultEncoding());
		System.out.println("supportsJava7Move:" + supportsJava7Move);
	}

	/**
	 * Prints the stacktrace of an exception to the trace object
	 *
	 * @param trace
	 * @param e
	 */
	public static void printStackTrace(Trace trace, Throwable e) {
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw);
		e.printStackTrace(pw);
		pw.close();
		trace.trace(sw.toString(), TraceLevel.EXCEPTION);
	}

	public static String getSystemDefaultEncoding() {
		return (new OutputStreamWriter(new ByteArrayOutputStream())).getEncoding();
	}

	private static FileLock getFileLock(Trace trace, FileChannel fileChannel, int counter) {
		FileLock lock = null;
		try {
			lock = fileChannel.tryLock();
		} catch (IOException e1) {
			Util.printStackTrace(trace, e1);
		}

		if (lock == null) {
			if (counter <= 0) {
				trace.trace("Unable to get lock on file. Giving up.", TraceLevel.ERROR_WARN);
				return null;
			}
			trace.trace("Failed to get write lock on file before copy. Will try again in 1 seconds",
					TraceLevel.ERROR_WARN);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
			}
			return getFileLock(trace, fileChannel, counter - 1);
		}
		trace.trace("File locked.", TraceLevel.TRACE);
		return lock;
	}

	/**
	 * Copy a file from 'from' to 'to'. Returns true if the file was copied.
	 * False otherwise.
	 *
	 * @param from
	 * @param to
	 * @return
	 * @throws IOException
	 */
	private static boolean copyFile(Trace trace, File from, File to) {
		FileInputStream fis = null;
		FileOutputStream fos = null;
		FileLock lock = null;
		try {
			fis = new FileInputStream(from);
			fos = new FileOutputStream(to);
			lock = getFileLock(trace, fos.getChannel(), 10);
			if (lock == null) {
				return false;
			}

			byte[] buffer = new byte[1024];
			int noOfBytes = 0;
			// read bytes from source file and write to destination file
			while ((noOfBytes = fis.read(buffer)) != -1) {
				fos.write(buffer, 0, noOfBytes);
			}
			return true;
		} catch (Exception e) {
			trace.trace("Exception while copying the file:", TraceLevel.ERROR_WARN);
			Util.printStackTrace(trace, e);
			return false;
		} finally {
			// close the streams using close method
			try {
				if (fos != null) {
					fos.close();
				}
			} catch (Exception ioe) {
				trace.trace("Error while closing copy output stream: ", TraceLevel.ERROR_WARN);
				Util.printStackTrace(trace, ioe);
			}
			try {
				if (fis != null) {
					fis.close();
				}
			} catch (Exception ioe) {
				trace.trace("Error while closing copy input stream: ", TraceLevel.ERROR_WARN);
				Util.printStackTrace(trace, ioe);
			}
			try {
				if ((lock != null) && lock.isValid()) {
					lock.release();
				}
			} catch (Exception ioe) {
				// It should have been closed by the fos.close();
				trace.trace("Error while releasing file lock: ", TraceLevel.ERROR_WARN);
				Util.printStackTrace(trace, ioe);
			}
		}
	}

	private static boolean moveFileJava7(Trace trace, File sourceFile, File destFile) {
		try {
			// First try the Files.move as an atomic operation
			trace.trace("Moving file from " + sourceFile.getAbsolutePath() + " to " + destFile.getAbsolutePath(),
					TraceLevel.TRACE);
			Files.move(sourceFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING,
					StandardCopyOption.ATOMIC_MOVE);
			return true;
		} catch (Throwable e) {
			try {
				// Next try the Files.move as a non-atomic operation
				trace.trace(
						"Non-atomic move from " + sourceFile.getAbsolutePath() + " to " + destFile.getAbsolutePath(),
						TraceLevel.TRACE);
				Files.move(sourceFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
				return true;
			} catch (Throwable e2) {
				Util.printStackTrace(trace, e2);
				return false;
			}
		}
	}

	private static boolean moveFileJava6(Trace trace, File sourceFile, File destFile) {
		// First try the File.renameTo as a java native operation
		if (sourceFile.renameTo(destFile)) {
			trace.trace("Renamed via java native.", TraceLevel.TRACE);
			return true;
		} else {
			// Next copy/paste the file. This is the least safe method.
			trace.trace("Trying to move via copy-delete sequence.", TraceLevel.TRACE);
			if (copyFile(trace, sourceFile, destFile)) {
				if (!sourceFile.delete()) {
					trace.trace("File was copied, but failed to delete the original file.", TraceLevel.ERROR_WARN);
				}
				trace.trace("Copy-delete sequence done.", TraceLevel.TRACE);
				return true;
			} else {
				trace.trace("Failed to move or copy the file to the destiantion.", TraceLevel.ERROR_WARN);
				return false;
			}
		}
	}

	/**
	 * Move a file from sourceFile to destFile.
	 *
	 * @param sourceFile
	 * @param destFile
	 * @return
	 */
	public static boolean moveFile(Trace trace, File sourceFile, File destFile) {
		if (supportsJava7Move) {
			trace.trace("Java7 compatibvle move", TraceLevel.TRACE);
			if (!moveFileJava7(trace, sourceFile, destFile)) {
				return moveFileJava6(trace, sourceFile, destFile);
			}
			return true;
		} else {
			trace.trace("Java6 compatibvle move", TraceLevel.TRACE);
			return moveFileJava6(trace, sourceFile, destFile);
		}

		// trace.trace("Moving file from " + sourceFile.getAbsolutePath() + " to
		// " + destFile.getAbsolutePath(),
		// TraceLevel.TRACE);
		// if (sourceFile.renameTo(destFile)) {
		// trace.trace("Moved via java native.", TraceLevel.TRACE);
		// return true;
		// } else {
		// trace.trace("Trying to move via copy-delete sequence.",
		// TraceLevel.TRACE);
		// if (copyFile(trace, sourceFile, destFile)) {
		// if (!sourceFile.delete()) {
		// trace.trace("File was copied, but failed to delete the original
		// file.", TraceLevel.ERROR_WARN);
		// }
		// trace.trace("Copy-delete sequence done.", TraceLevel.TRACE);
		// return true;
		// } else {
		// trace.trace("Failed to move or copy the file to the destiantion.",
		// TraceLevel.ERROR_WARN);
		// return false;
		// }
		// }
		/*
		 * Java 7 move not yet supported on all environments. try { // first try
		 * the renameTo method trace.trace("Moving file from "
		 * +sourceFile.getAbsolutePath()+" to "
		 * +destFile.getAbsolutePath(),TraceLevel.TRACE);
		 * Files.move(sourceFile.toPath(), destFile.toPath(),
		 * StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
		 * return true; }catch (Exception e) { try{ trace.trace(
		 * "Non-atomic move from "+sourceFile.getAbsolutePath()+" to "
		 * +destFile.getAbsolutePath(),TraceLevel.TRACE);
		 * Files.move(sourceFile.toPath(), destFile.toPath(),
		 * StandardCopyOption.REPLACE_EXISTING); return true; }catch (Exception
		 * e2) { Util.printStackTrace(trace, e2); return false; } }
		 */
		/*
		 * if (sourceFile.renameTo(destFile)) { trace.trace(
		 * "Moved via java native.",TraceLevel.TRACE); return true; } else {
		 * //assume linux. Call mv external trace.trace("Trying via unix: mv " +
		 * sourceFile.getAbsolutePath()+" "
		 * +destFile.getAbsolutePath(),TraceLevel.TRACE); Process p =
		 * Runtime.getRuntime().exec(new String[]{"/bin/sh","-c",
		 * "mv",sourceFile.getAbsolutePath(),destFile.getAbsolutePath()});
		 * trace.trace("Output from mv"); String line; BufferedReader input =
		 * new BufferedReader (new InputStreamReader(p.getInputStream())); while
		 * ((line = input.readLine()) != null) { System.out.println(line); }
		 * input.close(); trace.trace("Waiting for mv",TraceLevel.TRACE);
		 * p.waitFor();
		 *
		 * trace.trace("Return code: "+p.exitValue(),TraceLevel.TRACE); // try
		 * to copy file, delete original //copyFile(sourceFile, destFile, true,
		 * true); //sourceFile.delete(); } return true; } catch (IOException e)
		 * { trace.trace("IO Exception during moveFile "+sourceFile+" - "
		 * +destFile+" : "+ e.getMessage()); return false; } catch
		 * (InterruptedException e) { trace.trace(
		 * "InterruptedException during moveFile "+sourceFile+" - "+destFile+
		 * " : "+ e.getMessage()); return false; }
		 */
	}

	/**
	 * Append the exception cause to the given status element
	 *
	 * @param statusElem
	 * @param paramThrowable
	 * @param printStackTrace
	 */
	static void appendCause(XDSStatusElement statusElem, Throwable paramThrowable, boolean printStackTrace) {
		if (paramThrowable != null) {
			Throwable cause = paramThrowable.getCause();
			if (cause != null) {
				DocumentImpl paramDocumentImpl = statusElem.documentImpl();
				Element paramElement = statusElem.domElement();

				ExceptionElement exceptionElem = new ExceptionElement(paramDocumentImpl, paramElement);
				exceptionElem.attributeValueSet("class-name", cause.getClass().getName());
				exceptionElem.messageAppend(cause);
				if (printStackTrace) {
					exceptionElem.stackTraceAppend(cause);
				}
			}
		}
	}

	/**
	 * Append the statusAttributes to the statusDoc and return the created
	 * statusElement
	 *
	 * @param statusDoc
	 * @param statusAttr
	 * @param description
	 * @return
	 */
	public static XDSStatusElement appendStatus(StatusDocument statusDoc, StatusAttributes statusAttr,
			String description) {
		XDSStatusElement statusElem = statusDoc.appendStatusElement();
		if (statusAttr != null) {
			statusElem.setLevel(statusAttr.getLevel());
			statusElem.setType(statusAttr.getType());
			statusElem.setEventID(statusAttr.getEventID());
		}
		statusElem.descriptionAppend(description);
		return statusElem;
	}

	/**
	 * Append the given statusattributes to the status document.
	 *
	 * @param statusDoc
	 * @param statusAttr
	 * @param description
	 * @param paramException
	 * @param printStackTrace
	 * @param paramXmlDocument
	 * @return
	 */
	public static XDSStatusElement appendStatus(StatusDocument statusDoc, StatusAttributes statusAttr,
			String description, Throwable paramException, boolean printStackTrace, XmlDocument paramXmlDocument) {
		if (statusDoc != null) {
			XDSStatusElement statusElem = null;
			// Special processing for XDSParameterException: loop
			if ((paramException instanceof XDSParameterException)) {
				for (XDSParameterException localXDSParameterException = (XDSParameterException) paramException; localXDSParameterException != null; localXDSParameterException = localXDSParameterException
						.getNext()) {
					statusElem = appendStatus(statusDoc, statusAttr, localXDSParameterException.getMessage());
				}
				if ((statusElem != null) && (paramXmlDocument != null)) {
					statusElem.documentAppend(paramXmlDocument);
				}
			} else {
				// Make sure we have a decent description
				if (description == null) {
					description = paramException == null ? null : paramException.toString();
				}
				// Append the status element
				statusElem = appendStatus(statusDoc, statusAttr, description);
				// append the exception and the cause
				statusElem.exceptionAppend(paramException, printStackTrace);
				appendCause(statusElem, paramException, printStackTrace);
				statusElem.documentAppend(paramXmlDocument);
			}
			return statusElem;
		}
		return null;
	}

	public static XDSQueryDocument createQueryDoc(String className, String destDN, String association, QueryScope scope,
			Collection<String> readAttr, Map<String, String> matchAttributes) {
		XDSQueryDocument query = new XDSQueryDocument();
		XDSQueryElement queryElem = query.appendQueryElement();
		if (className != null) {
			queryElem.setClassName(className);
		}
		if (destDN != null) {
			queryElem.setDestDN(destDN);
		}
		if (scope != null) {
			queryElem.setScope(scope);
		}
		if (association != null) {
			queryElem.appendAssociationElement(association);
		}
		if (readAttr != null) {
			for (String string : readAttr) {
				XDSReadAttrElement readAttrs = queryElem.appendReadAttrElement();
				readAttrs.setAttrName(string);
			}
		} else {
			// adding an empty readattr element will make it read nothing
			queryElem.appendReadAttrElement();
		}

		if (matchAttributes != null) {
			Set<String> fields = matchAttributes.keySet();
			for (String string : fields) {
				String aFieldName = string;
				String value = matchAttributes.get(aFieldName);
				XDSSearchAttrElement search = queryElem.appendSearchAttrElement();
				search.setAttrName(aFieldName);
				search.appendValueElement(value);
			}
		}
		return query;
	}

	/**
	 * A non-interface method used to format trace values.
	 */
	public static String toLiteral(String string) {
		return (string == null) ? null : "'" + string + "'";
	}// toLiteral(String):String

} public class XDSInstanceDocument extends XDSQueryResultDocument {
	
	private static XmlDocument rename(XmlDocument xmlDoc, Trace trace){
		Document doc = xmlDoc.getDocument();
		NodeList nodeList = doc.getFirstChild().getChildNodes();
		for(int j=0; j<nodeList.getLength(); j++){ 
			Node element = nodeList.item(j);
			if ("input".equals(element.getNodeName())){
				trace.trace("Found node with name "+element.getNodeName());
			    Element element2 = doc.createElement("output");
			    NamedNodeMap attrs = element.getAttributes();
			    for (int i = 0; i < attrs.getLength(); i++) {
			      Attr attr2 = (Attr) doc.importNode(attrs.item(i), true);
			      element2.getAttributes().setNamedItem(attr2);
			    }
			    while (element.hasChildNodes()) {
			      element2.appendChild(element.getFirstChild());
			    }
			    element.getParentNode().replaceChild(element2, element);				
			}
		}
	    return new XmlDocument(doc);
	  }	
	
	public XDSInstanceDocument(XmlDocument xmlDoc, Trace trace) throws XDSParseException{
		super(rename(xmlDoc,trace));
	}
	

}
import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import com.novell.nds.dirxml.driver.SubscriptionShim;
import com.novell.nds.dirxml.driver.Trace;
import com.novell.nds.dirxml.driver.XmlDocument;
import com.novell.nds.dirxml.driver.XmlQueryProcessor;
import com.novell.nds.dirxml.driver.xds.CommandElement;
import com.novell.nds.dirxml.driver.xds.Constraint;
import com.novell.nds.dirxml.driver.xds.DataType;
import com.novell.nds.dirxml.driver.xds.ElementImpl;
import com.novell.nds.dirxml.driver.xds.NonXDSElement;
import com.novell.nds.dirxml.driver.xds.Parameter;
import com.novell.nds.dirxml.driver.xds.QueryScope;
import com.novell.nds.dirxml.driver.xds.RequiredConstraint;
import com.novell.nds.dirxml.driver.xds.StateDocument;
import com.novell.nds.dirxml.driver.xds.StatusLevel;
import com.novell.nds.dirxml.driver.xds.StatusType;
import com.novell.nds.dirxml.driver.xds.ValueType;
import com.novell.nds.dirxml.driver.xds.XDS;
import com.novell.nds.dirxml.driver.xds.XDSAddAssociationElement;
import com.novell.nds.dirxml.driver.xds.XDSAddAttrElement;
import com.novell.nds.dirxml.driver.xds.XDSAddElement;
import com.novell.nds.dirxml.driver.xds.XDSAddValueElement;
import com.novell.nds.dirxml.driver.xds.XDSAttrElement;
import com.novell.nds.dirxml.driver.xds.XDSAuthenticationInfoElement;
import com.novell.nds.dirxml.driver.xds.XDSCommandDocument;
import com.novell.nds.dirxml.driver.xds.XDSCommandResultDocument;
import com.novell.nds.dirxml.driver.xds.XDSComponentElement;
import com.novell.nds.dirxml.driver.xds.XDSException;
import com.novell.nds.dirxml.driver.xds.XDSInitDocument;
import com.novell.nds.dirxml.driver.xds.XDSInitParamsElement;
import com.novell.nds.dirxml.driver.xds.XDSInstanceElement;
import com.novell.nds.dirxml.driver.xds.XDSModifyAttrElement;
import com.novell.nds.dirxml.driver.xds.XDSModifyElement;
import com.novell.nds.dirxml.driver.xds.XDSParameterException;
import com.novell.nds.dirxml.driver.xds.XDSParseException;
import com.novell.nds.dirxml.driver.xds.XDSQueryDocument;
import com.novell.nds.dirxml.driver.xds.XDSQueryElement;
import com.novell.nds.dirxml.driver.xds.XDSQueryResultDocument;
import com.novell.nds.dirxml.driver.xds.XDSReadAttrElement;
import com.novell.nds.dirxml.driver.xds.XDSResultDocument;
import com.novell.nds.dirxml.driver.xds.XDSStatusElement;
import com.novell.nds.dirxml.driver.xds.XDSSubscriberStateElement;
import com.novell.nds.dirxml.driver.xds.XDSValueElement;
import com.novell.nds.dirxml.driver.xds.util.StatusAttributes;
import com.novell.nds.dirxml.driver.xds.util.XDSUtil;
import info.vancauwenberge.filedriver.api.IDriver;
import info.vancauwenberge.filedriver.api.IFileNameStrategy;
import info.vancauwenberge.filedriver.api.IFileStartStrategy;
import info.vancauwenberge.filedriver.api.IFileWriteStrategy;
import info.vancauwenberge.filedriver.api.IPostProcessStrategy;
import info.vancauwenberge.filedriver.api.IShutdown;
import info.vancauwenberge.filedriver.api.IStrategy;
import info.vancauwenberge.filedriver.api.ISubscriberFileListener;
import info.vancauwenberge.filedriver.api.ISubscriberShim;
import info.vancauwenberge.filedriver.api.ISubscriberStrategy;
import info.vancauwenberge.filedriver.exception.WriteException;
import info.vancauwenberge.filedriver.filename.SimpleDateFormatFileNameStrategy;
import info.vancauwenberge.filedriver.filepostprocess.ExternalExec;
import info.vancauwenberge.filedriver.filestart.BasicNewFileDecider;
import info.vancauwenberge.filedriver.filewriter.DummyFileWriter;
import info.vancauwenberge.filedriver.shim.driver.GenericFileDriverShim;
import info.vancauwenberge.filedriver.util.Errors;
import info.vancauwenberge.filedriver.util.TraceLevel;
import info.vancauwenberge.filedriver.util.Util;
import info.vancauwenberge.filedriver.util.XDSInstanceDocument;
/*******************************************************************************
 * Copyright (c) 2007, 2018 Stefaan Van Cauwenberge
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0 (the "License"). If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *  	 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Initial Developer of the Original Code is
 * Stefaan Van Cauwenberge. Portions created by
 *  the Initial Developer are Copyright (C) 2007, 2018 by
 * Stefaan Van Cauwenberge. All Rights Reserved.
 *
 * Contributor(s): none so far.
 *    Stefaan Van Cauwenberge: Initial API and implementation
 *******************************************************************************/
package info.vancauwenberge.filedriver.shim;






/**
 *  A basic skeleton for implementing the <code>SubscriptionShim</code>.
 *  <p>
 *  The <code>SubscriptionShim</code> defines an interface for an application
 *  driver to receive commands from the DirXML engine. These commands must be
 *  executed in the application on behalf of the DirXML engine.
 */
public class FileDriverSubscriptionShim
extends ChannelShim
implements SubscriptionShim, ISubscriberShim
{

	private final class ShutdownHook extends Thread {
		@Override
		public void run(){
			try {
				finishFile();
			} catch (final Throwable e) {
				e.printStackTrace();
			}
		}
	}


	/*
	 * Enum holding the general subscriber parameters
	 */
	private enum Parameters {
		/**
		 * java class that generates a new file name.
		 */
		SUB_FILE_NAME_STRATEGY("sub_FileNameStrategy",RequiredConstraint.REQUIRED,SimpleDateFormatFileNameStrategy.class.getName()),
		/**
		 * java class that determines when a new file must be started.
		 */
		SUB_FILE_START_STRATEGY("sub_FileStartStrategy",RequiredConstraint.REQUIRED,BasicNewFileDecider.class.getName()),
		/**
		 * java class strategy for writing a file
		 */
		SUB_FILE_WRITE_STRATEGY("sub_FileWriteStrategy",RequiredConstraint.REQUIRED,DummyFileWriter.class.getName()),
		/**
		 * Temporary work folder for the files while they are being created
		 */
		SUB_WORK_DIR("sub_WorkDir",RequiredConstraint.REQUIRED,"/var/opt/temp"),
		/**
		 * Path where the file will be moved to after completion
		 */
		SUB_DEST_DIR("sub_DestFolder",RequiredConstraint.REQUIRED,"/var/opt"),
		/**
		 * Path where a local copy of the file will be stored (as well as on the dest folder). Note: failing to copy this file
		 * will not generate an error or fatal status message. This is not guaranteed.
		 */
		SUB_LOCAL_COPY_PATH("sub_LocalCopyPath",null,""),
		SUB_FILE_POSTPROCESS_STRATEGY("sub_PostProcessStrategy",RequiredConstraint.REQUIRED,ExternalExec.class.getName());
		private String name;
		private String defaultValue;
		private Constraint constraint;
		Parameters(final String name, final Constraint constraint, final String defaultValue){
			this.name=name;
			this.defaultValue = defaultValue;
			this.constraint = constraint;
		}


		public String getValueFor(final Map<String,Parameter> subParams){
			return subParams.get(name).toString();
		}


		public static Map<String,Parameter> getIDMParameters(){
			final Parameters[] values = Parameters.values();
			final Map<String,Parameter> result = new HashMap<String, Parameter>(values.length);
			for (final Parameters subscriptionShimParameter : values) {
				final Parameter param = new Parameter(subscriptionShimParameter.name, //tag name
						subscriptionShimParameter.defaultValue, //default value (optional)
						DataType.STRING); //data type
				if (subscriptionShimParameter.constraint != null) {
					param.add(subscriptionShimParameter.constraint);
				}
				result.put(subscriptionShimParameter.name, param);
			}
			return result;

		}
	}

	//this constant identifies the channel/module being traced
	//  (e.g., Skeleton Driver\Driver)
	static private final String TRACE_SUFFIX = "ST";

	private static final String STATE_ADD_COUNT = "addCount";

	//MODIFY:  put your driver's activation id and activation level here
	/** This driver's activation id. */
	static private final String DRIVER_ID_VALUE = "SVCGENFILE";
	/** This driver's activation level. */
	static private final String DRIVER_MIN_ACTIVATION_VERSION = "0";

	private enum Strategies{
		FILENAMER(Parameters.SUB_FILE_NAME_STRATEGY,IFileNameStrategy.class), 
		FILESTARTER(Parameters.SUB_FILE_START_STRATEGY, IFileStartStrategy.class),
		FILEWRITER(Parameters.SUB_FILE_WRITE_STRATEGY, IFileWriteStrategy.class),