BATCHEE-107 improve getJobNames handling
diff --git a/extensions/shiro/src/main/java/org/apache/batchee/shiro/ShiroSecurityService.java b/extensions/shiro/src/main/java/org/apache/batchee/shiro/ShiroSecurityService.java
index 5ae20c0..bdccb58 100644
--- a/extensions/shiro/src/main/java/org/apache/batchee/shiro/ShiroSecurityService.java
+++ b/extensions/shiro/src/main/java/org/apache/batchee/shiro/ShiroSecurityService.java
@@ -25,6 +25,7 @@
 public class ShiroSecurityService extends DefaultSecurityService {
     private String instancePrefix;
     private String permissionPrefix;
+    private String jobNamePrefix;
 
     private boolean isAuthenticatedAndAuthorized(final String permission) {
         return getSubject().isAuthenticated() && getSubject().isPermitted(permission);
@@ -41,6 +42,11 @@
     }
 
     @Override
+    public boolean isAuthorizedJobName(String jobName) {
+        return isAuthenticatedAndAuthorized(jobNamePrefix + jobName);
+    }
+
+    @Override
     public String getLoggedUser() {
         if (getSubject().isAuthenticated()) {
             return getSubject().getPrincipal().toString();
@@ -53,5 +59,6 @@
         super.init(batchConfig);
         permissionPrefix = batchConfig.getProperty("security.job.permission-prefix", "jbatch:");
         instancePrefix = permissionPrefix + batchConfig.getProperty("security.job.permission-prefix", "instance:");
+        jobNamePrefix = permissionPrefix + batchConfig.getProperty("security.job.permission-prefix", "jobName:");
     }
 }
diff --git a/jbatch/src/main/java/org/apache/batchee/container/impl/JobOperatorImpl.java b/jbatch/src/main/java/org/apache/batchee/container/impl/JobOperatorImpl.java
index 790def2..bde0b87 100755
--- a/jbatch/src/main/java/org/apache/batchee/container/impl/JobOperatorImpl.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/impl/JobOperatorImpl.java
@@ -54,7 +54,6 @@
 import java.util.ArrayList;

 import java.util.HashSet;

 import java.util.List;

-import java.util.Map;

 import java.util.Properties;

 import java.util.Set;

 import java.util.logging.Level;

@@ -173,8 +172,8 @@
         final InternalJobExecution jobEx = persistenceManagerService.jobOperatorGetJobExecution(executionId);

 

         // if it is not in STARTED or STARTING state, mark it as ABANDONED

-        BatchStatus status = jobEx.getBatchStatus();
-        if (status == BatchStatus.STARTING ||  status == BatchStatus.STARTED) {
+        BatchStatus status = jobEx.getBatchStatus();

+        if (status == BatchStatus.STARTING ||  status == BatchStatus.STARTED) {

             throw new JobExecutionIsRunningException("Job Execution: " + executionId + " is still running");

         }

 

@@ -284,11 +283,10 @@
     @Override

     public Set<String> getJobNames() throws JobSecurityException {

         final Set<String> jobNames = new HashSet<String>();

-        final Map<Long, String> data = persistenceManagerService.jobOperatorGetExternalJobInstanceData();

-        for (final Map.Entry<Long, String> entry : data.entrySet()) {

-            long instanceId = entry.getKey();

-            if (securityService.isAuthorized(instanceId)) {

-                jobNames.add(entry.getValue());

+        final Set<String> jobNamesFromDb = persistenceManagerService.getJobNames();

+        for (String jobName : jobNamesFromDb) {

+            if (securityService.isAuthorizedJobName(jobName)) {

+                jobNames.add(jobName);

             }

         }

         return jobNames;

diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/persistence/JDBCPersistenceManagerService.java b/jbatch/src/main/java/org/apache/batchee/container/services/persistence/JDBCPersistenceManagerService.java
index d09abef..56101b5 100755
--- a/jbatch/src/main/java/org/apache/batchee/container/services/persistence/JDBCPersistenceManagerService.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/persistence/JDBCPersistenceManagerService.java
@@ -571,6 +571,30 @@
     }

 

     @Override

+    public Set<String> getJobNames() {

+        Connection conn = null;

+        PreparedStatement statement = null;

+        ResultSet rs = null;

+

+        Set<String> jobNames = new HashSet<String>();

+        try {

+            conn = getConnection();

+

+            statement = conn.prepareStatement(dictionary.getFindJobNames());

+            rs = statement.executeQuery();

+            while (rs.next()) {

+                jobNames.add(rs.getString(1));

+            }

+

+        } catch (final SQLException e) {

+            throw new PersistenceException(e);

+        } finally {

+            cleanupConnection(conn, rs, statement);

+        }

+        return jobNames;

+    }

+

+    @Override

     public Timestamp jobOperatorQueryJobExecutionTimestamp(final long key, final TimestampType timestampType) {

         Connection conn = null;

         PreparedStatement statement = null;

diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/persistence/JPAPersistenceManagerService.java b/jbatch/src/main/java/org/apache/batchee/container/services/persistence/JPAPersistenceManagerService.java
index 90eea56..be07489 100644
--- a/jbatch/src/main/java/org/apache/batchee/container/services/persistence/JPAPersistenceManagerService.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/persistence/JPAPersistenceManagerService.java
@@ -63,6 +63,7 @@
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -166,6 +167,22 @@
     }
 
     @Override
+    public Set<String> getJobNames() {
+        Set<String> jobNames = new TreeSet<String>();
+        final EntityManager em = emProvider.newEntityManager();
+
+        try {
+            final List<String> list = em.createNamedQuery(JobInstanceEntity.Queries.FIND_JOBNAMES, String.class)
+                                        .setParameter("pattern", PartitionedStepBuilder.JOB_ID_SEPARATOR + "%")
+                                        .getResultList();
+            jobNames.addAll(list);
+        } finally {
+            emProvider.release(em);
+        }
+        return jobNames;
+    }
+
+    @Override
     public JobStatus getJobStatusFromExecution(final long executionId) {
         final EntityManager em = emProvider.newEntityManager();
         try {
diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/persistence/MemoryPersistenceManagerService.java b/jbatch/src/main/java/org/apache/batchee/container/services/persistence/MemoryPersistenceManagerService.java
index a2165f8..78d5e68 100644
--- a/jbatch/src/main/java/org/apache/batchee/container/services/persistence/MemoryPersistenceManagerService.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/persistence/MemoryPersistenceManagerService.java
@@ -144,6 +144,17 @@
     }
 
     @Override
+    public Set<String> getJobNames() {
+        Set<String> jobNames = new HashSet<String>();
+        for (final Structures.JobInstanceData jobInstanceData : data.jobInstanceData.values()) {
+            if (jobInstanceData.instance.getJobName() != null && !jobInstanceData.instance.getJobName().startsWith(PartitionedStepBuilder.JOB_ID_SEPARATOR)) {
+                jobNames.add(jobInstanceData.instance.getJobName());
+            }
+        }
+        return jobNames;
+    }
+
+    @Override
     public List<Long> jobOperatorGetJobInstanceIds(final String jobName, final int start, final int count) {
         return jobOperatorGetJobInstanceIds(jobName, null, start, count);
     }
diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/persistence/jdbc/Dictionary.java b/jbatch/src/main/java/org/apache/batchee/container/services/persistence/jdbc/Dictionary.java
index 33b7608..8e24433 100644
--- a/jbatch/src/main/java/org/apache/batchee/container/services/persistence/jdbc/Dictionary.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/persistence/jdbc/Dictionary.java
@@ -20,7 +20,7 @@
 import org.apache.batchee.container.services.persistence.jdbc.database.Database;
 
 public class Dictionary {
-    public static interface SQL { // needs to be kept aligned with JPA mapping, we can't use reflection to find fields since order can change between executions with java 7
+    public interface SQL { // needs to be kept aligned with JPA mapping, we can't use reflection to find fields since order can change between executions with java 7
         String CREATE_TABLE = "create table ";
         String INSERT_INTO = "insert into ";
         String SELECT = "select ";
@@ -45,6 +45,7 @@
         String JOB_INSTANCE_COUNT = JOB_INSTANCE_COUNT_FROM_NAME + " and %s = ?";
         String JOB_INSTANCE_IDS = SELECT + "%s" + FROM + "%s" + WHERE + "%s = ? and %s = ? order by %s desc";
         String JOB_INSTANCE_IDS_FROM_NAME = SELECT + "%s" + FROM + "%s" + WHERE + " %s = ? order by %s desc";
+        String JOB_NAMES = SELECT + "distinct %s" + FROM + "%s" + WHERE + "%s not like '%s'";
         String EXTERNAL_JOB_INSTANCE = SELECT + "distinct %s, %s" + FROM + "%s" + WHERE + "%s not like '%s'";
         String JOB_INSTANCE_CREATE = INSERT_INTO + "%s" + "(%s, %s) VALUES(?, ?)";
         String JOB_INSTANCE_CREATE_WITH_JOB_XML = INSERT_INTO + "%s" + "(%s, %s, %s) VALUES(?, ?, ?)";
@@ -112,6 +113,7 @@
     private final String countJobInstanceByNameAndTag;
     private final String findJoBInstanceIds;
     private final String findJobInstanceIdsByName;
+    private final String findJobNames;
     private final String findExternalJobInstances;
     private final String createJobInstance;
     private final String createJobInstanceWithJobXml;
@@ -199,6 +201,7 @@
             this.findJoBInstanceIds = String.format(SQL.JOB_INSTANCE_IDS, jobInstanceColumns[0], jobInstanceTable, jobInstanceColumns[3],
                     jobInstanceColumns[8], jobInstanceColumns[0]);
             this.findJobInstanceIdsByName = String.format(SQL.JOB_INSTANCE_IDS_FROM_NAME, jobInstanceColumns[0], jobInstanceTable, jobInstanceColumns[3], jobInstanceColumns[0]);
+            this.findJobNames = String.format(SQL.JOB_NAMES, jobInstanceColumns[3], jobInstanceTable, jobInstanceColumns[3], PartitionedStepBuilder.JOB_ID_SEPARATOR + "%");
             this.findExternalJobInstances = String.format(SQL.EXTERNAL_JOB_INSTANCE, jobInstanceColumns[0], jobInstanceColumns[3], jobInstanceTable,
                     jobInstanceColumns[3], PartitionedStepBuilder.JOB_ID_SEPARATOR + "%");
             this.createJobInstance = String.format(SQL.JOB_INSTANCE_CREATE, jobInstanceTable, jobInstanceColumns[3], jobInstanceColumns[8]);
@@ -365,6 +368,10 @@
         return findExternalJobInstances;
     }
 
+    public String getFindJobNames() {
+        return findJobNames;
+    }
+
     public String getCreateJobInstance() {
         return createJobInstance;
     }
diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/persistence/jpa/domain/JobInstanceEntity.java b/jbatch/src/main/java/org/apache/batchee/container/services/persistence/jpa/domain/JobInstanceEntity.java
index a4e8862..c005d5f 100644
--- a/jbatch/src/main/java/org/apache/batchee/container/services/persistence/jpa/domain/JobInstanceEntity.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/persistence/jpa/domain/JobInstanceEntity.java
@@ -39,6 +39,7 @@
     @NamedQuery(name = JobInstanceEntity.Queries.COUNT_BY_NAME, query = "select count(j) from JobInstanceEntity j where j.name = :name"),
     @NamedQuery(name = JobInstanceEntity.Queries.FIND_FROM_EXECUTION, query = "select j from JobInstanceEntity j inner join j.executions e where e.executionId = :executionId"),
     @NamedQuery(name = JobInstanceEntity.Queries.FIND_EXTERNALS, query = "select j from JobInstanceEntity j where j.name not like :pattern"),
+    @NamedQuery(name = JobInstanceEntity.Queries.FIND_JOBNAMES, query = "select distinct(j.name) from JobInstanceEntity j where j.name not like :pattern"),
     @NamedQuery(name = JobInstanceEntity.Queries.FIND_BY_NAME_AND_TAG, query = "select j from JobInstanceEntity j where j.name = :name and j.tag = :tag"),
     @NamedQuery(name = JobInstanceEntity.Queries.FIND_BY_NAME, query = "select j from JobInstanceEntity j where j.name = :name"),
     @NamedQuery(name = JobInstanceEntity.Queries.DELETE_BY_INSTANCE_ID, query = "delete from JobInstanceEntity e where e.jobInstanceId = :instanceId"),
@@ -48,12 +49,13 @@
 })
 @Table(name=JobInstanceEntity.TABLE_NAME)
 public class JobInstanceEntity {
-    public static interface Queries {
+    public interface Queries {
         String COUNT_BY_NAME_AND_TAG = "org.apache.batchee.container.services.persistence.jpa.domain.JobInstanceEntity.countByNameAndTag";
         String COUNT_BY_NAME = "org.apache.batchee.container.services.persistence.jpa.domain.JobInstanceEntity.countByName";
         String FIND_BY_NAME = "org.apache.batchee.container.services.persistence.jpa.domain.JobInstanceEntity.findByName";
         String FIND_BY_NAME_AND_TAG = "org.apache.batchee.container.services.persistence.jpa.domain.JobInstanceEntity.findByNameAndTag";
         String FIND_EXTERNALS = "org.apache.batchee.container.services.persistence.jpa.domain.JobInstanceEntity.findExternals";
+        String FIND_JOBNAMES = "org.apache.batchee.container.services.persistence.jpa.domain.JobInstanceEntity.findJobNames";
         String FIND_FROM_EXECUTION = "org.apache.batchee.container.services.persistence.jpa.domain.JobInstanceEntity.findByExecution";
         String DELETE_BY_INSTANCE_ID = "org.apache.batchee.container.services.persistence.jpa.domain.JobInstanceEntity.deleteFromInstanceId";
         String DELETE_BY_DATE = "org.apache.batchee.container.services.persistence.jpa.domain.JobInstanceEntity.deleteByDate";
diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/security/DefaultSecurityService.java b/jbatch/src/main/java/org/apache/batchee/container/services/security/DefaultSecurityService.java
index f827226..0ace389 100644
--- a/jbatch/src/main/java/org/apache/batchee/container/services/security/DefaultSecurityService.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/security/DefaultSecurityService.java
@@ -35,6 +35,11 @@
     }
 
     @Override
+    public boolean isAuthorizedJobName(String jobName) {
+        return isDefaultUserAuthorized();
+    }
+
+    @Override
     public String getLoggedUser() {
         return defaultUser;
     }
diff --git a/jbatch/src/main/java/org/apache/batchee/container/services/security/JAASSecurityService.java b/jbatch/src/main/java/org/apache/batchee/container/services/security/JAASSecurityService.java
index 8891ae5..b97dea8 100644
--- a/jbatch/src/main/java/org/apache/batchee/container/services/security/JAASSecurityService.java
+++ b/jbatch/src/main/java/org/apache/batchee/container/services/security/JAASSecurityService.java
@@ -51,6 +51,11 @@
     }
 
     @Override
+    public boolean isAuthorizedJobName(String jobName) {
+        return isAuthenticatedAndAuthorized("read");
+    }
+
+    @Override
     public String getLoggedUser() {
         final Subject subject = getSubject();
         if (subject != null) {
diff --git a/jbatch/src/main/java/org/apache/batchee/spi/PersistenceManagerService.java b/jbatch/src/main/java/org/apache/batchee/spi/PersistenceManagerService.java
index a50e10a..0613ea0 100755
--- a/jbatch/src/main/java/org/apache/batchee/spi/PersistenceManagerService.java
+++ b/jbatch/src/main/java/org/apache/batchee/spi/PersistenceManagerService.java
@@ -49,6 +49,11 @@
 

     int jobOperatorGetJobInstanceCount(String jobName, String appTag);

 

+    Set<String> getJobNames();

+

+    /**

+     * @deprecated replaced by {@link #getJobNames()} 

+     */

     Map<Long, String> jobOperatorGetExternalJobInstanceData();

 

     List<Long> jobOperatorGetJobInstanceIds(String jobName, int start, int count);

diff --git a/jbatch/src/main/java/org/apache/batchee/spi/SecurityService.java b/jbatch/src/main/java/org/apache/batchee/spi/SecurityService.java
index cc9623f..c32dbdf 100644
--- a/jbatch/src/main/java/org/apache/batchee/spi/SecurityService.java
+++ b/jbatch/src/main/java/org/apache/batchee/spi/SecurityService.java
@@ -21,6 +21,12 @@
     boolean isAuthorized(String perm);
 
     /**
+     * @return whether the current user is allowed to see the given jobName
+     * @see javax.batch.operations.JobOperator#getJobNames()
+     */
+    boolean isAuthorizedJobName(String jobName);
+
+    /**
      * @return logged user if exists or a default name for anonymous launches
      */
     String getLoggedUser();