code: @Override
    public synchronized void put(JobSpec jobSpec) {
      Preconditions.checkState(state() == State.RUNNING, String.format("%s is not running.", this.getClass().getName()));
      Preconditions.checkNotNull(jobSpec);
      try {
        long startTime = System.currentTimeMillis();
        Path jobSpecPath = getPathForURI(this.jobConfDirPath, jobSpec.getUri());
        boolean isUpdate = fs.exists(jobSpecPath);
  
        materializedJobSpec(jobSpecPath, jobSpec, this.fs);
        this.mutableMetrics.updatePutJobTime(startTime);
        if (isUpdate) {
          this.listeners.onUpdateJob(jobSpec);
        } else {
          this.listeners.onAddJob(jobSpec);
        }
      } catch (IOException e) {
        throw new RuntimeException("When persisting a new JobSpec, unexpected issues happen:" + e.getMessage());
      } catch (JobSpecNotFoundException e) {
        throw new RuntimeException("When replacing a existed JobSpec, unexpected issue happen:" + e.getMessage());
      }
    }
docs: Allow user to programmatically add a new JobSpec.
  The method will materialized the jobSpec into real file.
  
  @param jobSpec The target JobSpec Object to be materialized.
  Noted that the URI return by getUri is a relative path.