def HTCScheddFactory(URI, **kwargs):
	"""
	Return an interface for the GC-Schedd operations
	
	Required:
	URI string
	       The URI of the Schedd to connect to
	"""
	adapter, scheme = ProcessAdapterFactory(URI, externalSchemes=["spool"])
	if not adapter:
		raise NotImplementedError("Schedd interfacing via methods of scheme '%s' has not been implemented yet." % scheme)
	for HTCSchedd in [ HTCScheddLocal, HTCScheddSSH ]:
		if adapter.getType() in HTCSchedd.adapterTypes:
			return HTCSchedd(URI = URI, adapter = adapter, **kwargs)
	def __init__(self, URI="", adapter = None, parentPool = None):
		"""
		Optional:
		URI string
		       URI from which to construct an adapter if none given
		adapter ProcessAdapterInterface
		       adapter to use for issuing schedd commands
		parentPool HTCondorWMS
		       pool WMS the schedd belongs to
		"""
		self._initLogger()
		self._log(logging.INFO1, "Establishing HTC Schedd adapter of type %s" % self.__class__.__name__)
		if adapter:
			self._adapter = adapter
		else:
			self._adapter, _ = ProcessAdapterFactory(URI, externalSchemes=["spool"])
		self._URI = URI or self._adapter.getURI()
		assert self._adapter != None, "Bug! Schedd initialization with invalid adapter data."
		assert adapter.getType() in self.adapterTypes, "Bug! Got adapter of type '%s', expected '%s'" % (adapter.getType(), "' or '".join(self.adapterType))
		self.parentPool = parentPool
class HTCScheddBase(Plugin):
	"""
	Base Interface for interactions with a Schedd
	"""
	adapterTypes = []
	_submitScale = 0
	_adapterMaxWait   = 10
	def __init__(self, URI="", adapter = None, parentPool = None):
		"""
		Optional:
		URI string
		       URI from which to construct an adapter if none given
		adapter ProcessAdapterInterface
		       adapter to use for issuing schedd commands
		parentPool HTCondorWMS
		       pool WMS the schedd belongs to
		"""
		self._initLogger()
		self._log(logging.INFO1, "Establishing HTC Schedd adapter of type %s" % self.__class__.__name__)
		if adapter:
			self._adapter = adapter
		else:
			self._adapter, _ = ProcessAdapterFactory(URI, externalSchemes=["spool"])
		self._URI = URI or self._adapter.getURI()
		assert self._adapter != None, "Bug! Schedd initialization with invalid adapter data."
		assert adapter.getType() in self.adapterTypes, "Bug! Got adapter of type '%s', expected '%s'" % (adapter.getType(), "' or '".join(self.adapterType))
		self.parentPool = parentPool

	def getDomain(self):
		return self._adapter.getDomain()
	def getURI(self):
		return self._URI

	# public interfaces for HTC Pool/WMS
	def submitJobs(self, jobNumList, task, queryArguments):
		"""
		Submit a batch of jobs from the sandbox
		
		Returns:
		JobInfoMaps  { HTCJobID : InfoData,...]
		       Sequence of per job information
		"""
		raise AbstractError

	def checkJobs(self, htcIDs, queryArguments):
		"""
		Get the status of a number of jobs
		
		Rquired:
		htcIDs [HTCJobID, ...]
		
		Returns:
		JobInfoMapMaps  { HTCJobID : InfoData,...]
		       Sequence of per checked job information maps
		"""
		raise AbstractError

	def getJobsOutput(self, htcIDs):
		"""
		Return output of a finished job to the sandbox
		
		Rquired:
		htcIDs [HTCJobID, ...]
		
		Returns:
		ReturnedJobs  [HTCJobID,...]
		       Sequence of retrieved jobs
		"""
		raise AbstractError

	def cancelJobs(self, htcIDs):
		"""
		Cancel/Abort/Delete a number of jobs
		
		Rquired:
		htcIDs [HTCJobID, ...]
		
		Returns:
		ReturnedJobs  [HTCJobID,...]
		       Sequence of removed jobs"""
		raise AbstractError

	def getTimings(self):
		"""Return suggested Idle/Active polling interval"""
		return utils.Result(waitOnIdle = 60, waitBetweenSteps = 10)

	def getCanSubmit(self):
		"""Return whether submission to this Schedd is possible"""
		return ( self._adapter.LoggedExecute("which condor_submit").wait(timeout = self._adapterMaxWait) == 0 )
	
	def getSubmitScale(self):
		"""Return number of jobs to submit as one batch"""
		return self._submitScale

	def getHTCVersion(self):
		"""Return the version of the attached HTC installation as tuple(X,Y,Z)"""
		raise AbstractError

	# internal interfaces for HTC Schedds
	def getStagingDir(self, htcID = None, taskID = None):
		"""Return path in the Schedd domain where HTC picks up and returns files"""
		raise AbstractError
	def cleanStagingDir(self, htcID = None, taskID = None):
		"""Clean path in the Schedd domain where HTC picks up and returns files"""
		raise AbstractError

	def _getBaseJDLData(self, task, queryArguments):
		"""Create a sequence of default attributes for a submission JDL"""
		jdlData = [
			'+submitTool              = "GridControl (version %s)"' % utils.getVersion(),
			'should_transfer_files    = YES',
			'when_to_transfer_output  = ON_EXIT',
			'periodic_remove          = (JobStatus == 5 && HoldReasonCode != 16)',
			'environment              = CONDOR_WMS_DASHID=https://%s:/$(Cluster).$(Process)' % self.parentPool.wmsName,
			'Universe                 = %s' % self.parentPool._jobSettings["Universe"],	# TODO: Unhack me
			'+GcID                    = "%s"' % self.parentPool._createGcId(
				HTCJobID(
					gcJobNum  = '$(GcJobNum)',
					gcTaskID  = task.taskID,
					clusterID = '$(Cluster)',
					procID    = '$(Process)',
					scheddURI = self.getURI(),
					typed     = False)
				),
			'+GcJobNumToWmsID         = "$(GcJobNum)@$(Cluster).$(Process)"',
			'+GcJobNumToGcID          = "$(GcJobNum)@$(GcID)"',
			'Log                      = %s' % os.path.join(self.getStagingDir(), 'gcJobs.log'),
			'job_ad_information_attrs = %s' %','.join([ arg for arg in queryArguments if arg not in ['JobStatus']]),
			]
		for key in queryArguments:
			try:
				# is this a match string? '+JOB_GLIDEIN_Entry_Name = "$$(GLIDEIN_Entry_Name:Unknown)"' -> MATCH_GLIDEIN_Entry_Name = "CMS_T2_DE_RWTH_grid-ce2" && MATCH_EXP_JOB_GLIDEIN_Entry_Name = "CMS_T2_DE_RWTH_grid-ce2"
				matchKey=re.match("(?:MATCH_EXP_JOB_|MATCH_|JOB_)(.*)",key).group(1)
				jdlData['Head']['+JOB_%s'%matchKey] = "$$(%s:Unknown)" % matchKey
			except AttributeError:
				pass
		for line in self.parentPool._jobSettings["ClassAd"]:
			jdlData.append( '+' + line )
		for line in self.parentPool._jobSettings["JDL"]:
			jdlData.append( line )
		return jdlData
		return jdlData

	def _getRequirementJdlData(self, task, jobNum):
		"""Create JDL attributes corresponding to job requirements"""
		jdlData      = []
		requirements = task.getRequirements(jobNum)
		poolRequMap  = self.parentPool.jdlRequirementMap
		for reqType, reqValue in requirements:
			# ('WALLTIME', 'CPUTIME', 'MEMORY', 'CPUS', 'BACKEND', 'SITES', 'QUEUES', 'SOFTWARE', 'STORAGE')
			if reqType == WMS.SITES:
				(wantSites, vetoSites) = utils.splitBlackWhiteList(reqValue[1])
				if "+SITES" in poolRequMap:
					jdlData.append( '%s = "%s"' % (
						poolRequMap["+SITES"][0],
						poolRequMap["+SITES"][1] % ','.join(wantSites)
						)
					)
				if "-SITES" in poolRequMap:
					jdlData.append( '%s = "%s"' % (
						poolRequMap["-SITES"][0],
						poolRequMap["-SITES"][1] % ','.join(vetoSites)
						)
					)
				continue
			if reqType == WMS.STORAGE:
				if ("STORAGE" in poolRequMap) and reqValue > 0:
					jdlData.append( '%s = %s ' % (
						poolRequMap["STORAGE"][0],
						poolRequMap["STORAGE"][1] % ','.join(reqValue)
						)
					)
				continue
			#HACK
			if reqValue > 0 and WMS.reqTypes[reqType] in poolRequMap:
				jdlData.append( "%s = %s" % (
					poolRequMap[WMS.reqTypes[reqType]][0],
					poolRequMap[WMS.reqTypes[reqType]][1] % reqValue
					)
				)
				continue
			try:
				if int(reqValue) <= 0:
					continue
			except TypeError:
				pass
			self._log(logging.INFO3, "Requirement '%s' cannot be mapped to pool and will be ignored!" % WMS.reqTypes[reqType])
		return jdlData

	# GC internals
	@classmethod
	def _initLogger(self):
		self._logger = logging.getLogger('backend.htcschedd.%s' % self.__name__)
		self._log = self._logger.log