Kaynağa Gözat

Merge pull request #372 from codelibs/10.0.x

Merge from 10.0.x
Shinsuke Sugaya 9 yıl önce
ebeveyn
işleme
44ee85f31d
89 değiştirilmiş dosya ile 2705 ekleme ve 931 silme
  1. 57 30
      pom.xml
  2. 4 0
      src/main/java/org/codelibs/fess/Constants.java
  3. 1 1
      src/main/java/org/codelibs/fess/api/es/EsApiManager.java
  4. 1 1
      src/main/java/org/codelibs/fess/app/job/AllJobScheduler.java
  5. 1 1
      src/main/java/org/codelibs/fess/app/job/ScriptExecutorJob.java
  6. 4 3
      src/main/java/org/codelibs/fess/app/logic/AccessContextLogic.java
  7. 15 0
      src/main/java/org/codelibs/fess/app/service/GroupService.java
  8. 3 0
      src/main/java/org/codelibs/fess/app/service/PathMappingService.java
  9. 14 0
      src/main/java/org/codelibs/fess/app/service/RoleService.java
  10. 9 9
      src/main/java/org/codelibs/fess/app/service/ScheduledJobService.java
  11. 25 0
      src/main/java/org/codelibs/fess/app/service/UserService.java
  12. 9 3
      src/main/java/org/codelibs/fess/app/web/RootAction.java
  13. 4 0
      src/main/java/org/codelibs/fess/app/web/admin/general/AdminGeneralAction.java
  14. 6 0
      src/main/java/org/codelibs/fess/app/web/admin/general/EditForm.java
  15. 18 38
      src/main/java/org/codelibs/fess/app/web/admin/group/AdminGroupAction.java
  16. 1 1
      src/main/java/org/codelibs/fess/app/web/admin/joblog/AdminJoblogAction.java
  17. 18 38
      src/main/java/org/codelibs/fess/app/web/admin/role/AdminRoleAction.java
  18. 1 1
      src/main/java/org/codelibs/fess/app/web/admin/scheduler/AdminSchedulerAction.java
  19. 13 83
      src/main/java/org/codelibs/fess/app/web/admin/searchlist/AdminSearchlistAction.java
  20. 26 6
      src/main/java/org/codelibs/fess/app/web/admin/user/AdminUserAction.java
  21. 1 1
      src/main/java/org/codelibs/fess/app/web/admin/wizard/AdminWizardAction.java
  22. 2 2
      src/main/java/org/codelibs/fess/app/web/base/FessAdminAction.java
  23. 5 3
      src/main/java/org/codelibs/fess/app/web/base/login/FessLoginAssist.java
  24. 1 2
      src/main/java/org/codelibs/fess/app/web/cache/CacheAction.java
  25. 12 5
      src/main/java/org/codelibs/fess/app/web/go/GoAction.java
  26. 6 1
      src/main/java/org/codelibs/fess/app/web/login/LoginAction.java
  27. 102 0
      src/main/java/org/codelibs/fess/app/web/profile/ProfileAction.java
  28. 44 0
      src/main/java/org/codelibs/fess/app/web/profile/ProfileForm.java
  29. 1 2
      src/main/java/org/codelibs/fess/app/web/screenshot/ScreenshotAction.java
  30. 1 1
      src/main/java/org/codelibs/fess/app/web/search/SearchAction.java
  31. 17 14
      src/main/java/org/codelibs/fess/crawler/transformer/AbstractFessFileTransformer.java
  32. 1 1
      src/main/java/org/codelibs/fess/crawler/transformer/FessXpathTransformer.java
  33. 1 1
      src/main/java/org/codelibs/fess/es/client/FessEsClient.java
  34. 2 1
      src/main/java/org/codelibs/fess/es/config/exentity/ScheduledJob.java
  35. 5 0
      src/main/java/org/codelibs/fess/es/user/exentity/Group.java
  36. 5 0
      src/main/java/org/codelibs/fess/es/user/exentity/Role.java
  37. 17 0
      src/main/java/org/codelibs/fess/es/user/exentity/User.java
  38. 26 0
      src/main/java/org/codelibs/fess/exception/FessUserNotFoundException.java
  39. 30 0
      src/main/java/org/codelibs/fess/exception/LdapOperationException.java
  40. 2 2
      src/main/java/org/codelibs/fess/exception/WebApiException.java
  41. 2 2
      src/main/java/org/codelibs/fess/exec/Crawler.java
  42. 2 2
      src/main/java/org/codelibs/fess/helper/ProcessHelper.java
  43. 2 2
      src/main/java/org/codelibs/fess/helper/QueryHelper.java
  44. 202 8
      src/main/java/org/codelibs/fess/helper/RoleQueryHelper.java
  45. 2 10
      src/main/java/org/codelibs/fess/helper/SambaHelper.java
  46. 23 7
      src/main/java/org/codelibs/fess/helper/SystemHelper.java
  47. 127 4
      src/main/java/org/codelibs/fess/helper/UserInfoHelper.java
  48. 0 161
      src/main/java/org/codelibs/fess/helper/impl/CookieUserInfoHelperImpl.java
  49. 0 231
      src/main/java/org/codelibs/fess/helper/impl/RoleQueryHelperImpl.java
  50. 1 1
      src/main/java/org/codelibs/fess/job/CrawlJob.java
  51. 1 1
      src/main/java/org/codelibs/fess/job/SuggestJob.java
  52. 478 69
      src/main/java/org/codelibs/fess/ldap/LdapManager.java
  53. 8 0
      src/main/java/org/codelibs/fess/ldap/LdapUser.java
  54. 2 8
      src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java
  55. 48 0
      src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java
  56. 54 3
      src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java
  57. 427 60
      src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java
  58. 97 13
      src/main/java/org/codelibs/fess/mylasta/direction/FessProp.java
  59. 6 2
      src/main/java/org/codelibs/fess/taglib/FessFunctions.java
  60. 1 1
      src/main/resources/app.xml
  61. 9 1
      src/main/resources/fess.xml
  62. 36 7
      src/main/resources/fess_config.properties
  63. 18 0
      src/main/resources/fess_label.properties
  64. 16 0
      src/main/resources/fess_label_en.properties
  65. 19 2
      src/main/resources/fess_label_ja.properties
  66. 4 1
      src/main/resources/fess_message.properties
  67. 4 1
      src/main/resources/fess_message_en.properties
  68. 4 1
      src/main/resources/fess_message_ja.properties
  69. 15 15
      src/main/resources/mail/crawler.dfmail
  70. 3 2
      src/main/resources/mail/es_status.dfmail
  71. 2 2
      src/main/resources/mail/testmail.dfmail
  72. 12 0
      src/main/webapp/WEB-INF/fe.tld
  73. 3 0
      src/main/webapp/WEB-INF/orig/view/header.jsp
  74. 4 0
      src/main/webapp/WEB-INF/orig/view/index.jsp
  75. 102 0
      src/main/webapp/WEB-INF/orig/view/login/index.jsp
  76. 116 0
      src/main/webapp/WEB-INF/orig/view/profile/index.jsp
  77. 22 0
      src/main/webapp/WEB-INF/view/admin/general/admin_general.jsp
  78. 43 1
      src/main/webapp/WEB-INF/view/admin/group/admin_group_details.jsp
  79. 43 1
      src/main/webapp/WEB-INF/view/admin/role/admin_role_details.jsp
  80. 3 0
      src/main/webapp/WEB-INF/view/header.jsp
  81. 4 0
      src/main/webapp/WEB-INF/view/index.jsp
  82. 0 8
      src/main/webapp/WEB-INF/view/login/footer.jsp
  83. 0 6
      src/main/webapp/WEB-INF/view/login/header.jsp
  84. 8 1
      src/main/webapp/WEB-INF/view/login/index.jsp
  85. 0 37
      src/main/webapp/WEB-INF/view/login/logout.jsp
  86. 116 0
      src/main/webapp/WEB-INF/view/profile/index.jsp
  87. 4 0
      src/main/webapp/css/admin/style.css
  88. 9 9
      src/test/java/org/codelibs/fess/helper/RoleQueryHelperTest.java
  89. 92 0
      src/test/java/org/codelibs/fess/util/DocumentUtilTest.java

+ 57 - 30
pom.xml

@@ -59,7 +59,7 @@
 		<utflute.version>0.6.0F</utflute.version>
 
 		<!-- Crawler -->
-		<crawler.version>1.0.4-SNAPSHOT</crawler.version>
+		<crawler.version>1.0.5-SNAPSHOT</crawler.version>
 
 		<!-- Suggest -->
 		<suggest.version>2.1.1</suggest.version>
@@ -103,34 +103,6 @@
 			<filter>src/packaging/common/packaging.properties</filter>
 		</filters>
 
-		<pluginManagement>
-			<plugins>
-				<plugin>
-					<groupId>org.eclipse.m2e</groupId>
-					<artifactId>lifecycle-mapping</artifactId>
-					<version>1.0.0</version>
-					<configuration>
-						<lifecycleMappingMetadata>
-							<pluginExecutions>
-								<pluginExecution>
-									<pluginExecutionFilter>
-										<groupId>org.apache.maven.plugins</groupId>
-										<artifactId>maven-dependency-plugin</artifactId>
-										<versionRange>[0,)</versionRange>
-										<goals>
-											<goal>unpack-dependencies</goal>
-										</goals>
-									</pluginExecutionFilter>
-									<action>
-										<ignore /> <!-- to avoid warning -->
-									</action>
-								</pluginExecution>
-							</pluginExecutions>
-						</lifecycleMappingMetadata>
-					</configuration>
-				</plugin>
-			</plugins>
-		</pluginManagement>
 		<plugins>
 			<plugin>
 				<artifactId>maven-compiler-plugin</artifactId>
@@ -619,6 +591,61 @@
 				</configuration>
 			</plugin>
 		</plugins>
+		<pluginManagement>
+			<plugins>
+				<plugin>
+					<groupId>org.eclipse.m2e</groupId>
+					<artifactId>lifecycle-mapping</artifactId>
+					<version>1.0.0</version>
+					<configuration>
+						<lifecycleMappingMetadata>
+							<pluginExecutions>
+								<pluginExecution>
+									<pluginExecutionFilter>
+										<groupId>
+											net.revelc.code
+										</groupId>
+										<artifactId>
+											formatter-maven-plugin
+										</artifactId>
+										<versionRange>
+											[0.5.2,)
+										</versionRange>
+										<goals>
+											<goal>format</goal>
+										</goals>
+									</pluginExecutionFilter>
+									<action>
+										<ignore></ignore>
+									</action>
+								</pluginExecution>
+								<pluginExecution>
+									<pluginExecutionFilter>
+										<groupId>
+											org.apache.maven.plugins
+										</groupId>
+										<artifactId>
+											maven-dependency-plugin
+										</artifactId>
+										<versionRange>
+											[2.10,)
+										</versionRange>
+										<goals>
+											<goal>
+												unpack-dependencies
+											</goal>
+										</goals>
+									</pluginExecutionFilter>
+									<action>
+										<ignore></ignore>
+									</action>
+								</pluginExecution>
+							</pluginExecutions>
+						</lifecycleMappingMetadata>
+					</configuration>
+				</plugin>
+			</plugins>
+		</pluginManagement>
 	</build>
 	<pluginRepositories>
 		<pluginRepository>
@@ -734,7 +761,7 @@
 		<dependency>
 			<groupId>org.codelibs</groupId>
 			<artifactId>corelib</artifactId>
-			<version>0.3.2</version>
+			<version>0.3.3</version>
 		</dependency>
 		<dependency>
 			<groupId>org.apache.commons</groupId>

+ 4 - 0
src/main/java/org/codelibs/fess/Constants.java

@@ -359,6 +359,10 @@ public class Constants extends CoreLibConstants {
 
     public static final String LDAP_ACCOUNT_FILTER = "ldap.account.filter";
 
+    public static final String NOTIFICATION_LOGIN = "notification.login";
+
+    public static final String NOTIFICATION_SEARCH_TOP = "notification.search.top";
+
     public static final String MAPPING_TYPE_ARRAY = "array";
 
     public static final String MAPPING_TYPE_STRING = "string";

+ 1 - 1
src/main/java/org/codelibs/fess/api/es/EsApiManager.java

@@ -92,7 +92,7 @@ public class EsApiManager extends BaseApiManager {
             }).orElse(() -> {
                 throw new WebApiException(HttpServletResponse.SC_FORBIDDEN, "Invalid session.");
             });
-        } catch (WebApiException e) {
+        } catch (final WebApiException e) {
             logger.debug("Web API access error. ", e);
             e.sendError(response);
         }

+ 1 - 1
src/main/java/org/codelibs/fess/app/job/AllJobScheduler.java

@@ -54,7 +54,7 @@ public class AllJobScheduler implements LaJobScheduler {
     protected Class<? extends LaJob> jobClass = ScriptExecutorJob.class;
 
     @Override
-    public void schedule(LaCron cron) {
+    public void schedule(final LaCron cron) {
         scheduledJobService.start(cron);
 
         final String myName = fessConfig.getSchedulerTargetName();

+ 1 - 1
src/main/java/org/codelibs/fess/app/job/ScriptExecutorJob.java

@@ -35,7 +35,7 @@ public class ScriptExecutorJob implements LaJob {
     private static final Logger logger = LoggerFactory.getLogger(ScriptExecutorJob.class);
 
     @Override
-    public void run(LaJobRuntime runtime) {
+    public void run(final LaJobRuntime runtime) {
         final ScheduledJob scheduledJob = (ScheduledJob) runtime.getParameterMap().get(Constants.SCHEDULED_JOB); // TODO null check
         final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
         final JobManager jobManager = ComponentUtil.getJobManager();

+ 4 - 3
src/main/java/org/codelibs/fess/app/logic/AccessContextLogic.java

@@ -52,15 +52,16 @@ public class AccessContextLogic {
     // ===================================================================================
     //                                                                      Create Context
     //                                                                      ==============
-    public AccessContext create(AccessContextResource resource, UserTypeSupplier userTypeSupplier, UserBeanSupplier userBeanSupplier,
-            AppTypeSupplier appTypeSupplier) {
+    public AccessContext create(final AccessContextResource resource, final UserTypeSupplier userTypeSupplier,
+            final UserBeanSupplier userBeanSupplier, final AppTypeSupplier appTypeSupplier) {
         final AccessContext context = new AccessContext();
         context.setAccessLocalDateTimeProvider(() -> timeManager.currentDateTime());
         context.setAccessUserProvider(() -> buildAccessUserTrace(resource, userTypeSupplier, appTypeSupplier));
         return context;
     }
 
-    private String buildAccessUserTrace(AccessContextResource resource, UserTypeSupplier userTypeSupplier, AppTypeSupplier appTypeSupplier) {
+    private String buildAccessUserTrace(final AccessContextResource resource, final UserTypeSupplier userTypeSupplier,
+            final AppTypeSupplier appTypeSupplier) {
         final StringBuilder sb = new StringBuilder();
         sb.append(userTypeSupplier.supply().orElse("_"));
         sb.append(",").append(appTypeSupplier.supply()).append(",").append(resource.getModuleName());

+ 15 - 0
src/main/java/org/codelibs/fess/app/service/GroupService.java

@@ -25,8 +25,11 @@ import org.codelibs.fess.Constants;
 import org.codelibs.fess.app.pager.GroupPager;
 import org.codelibs.fess.es.user.cbean.GroupCB;
 import org.codelibs.fess.es.user.exbhv.GroupBhv;
+import org.codelibs.fess.es.user.exbhv.UserBhv;
 import org.codelibs.fess.es.user.exentity.Group;
 import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.util.ComponentUtil;
+import org.codelibs.fess.util.StreamUtil;
 import org.dbflute.cbean.result.PagingResultBean;
 import org.dbflute.optional.OptionalEntity;
 
@@ -40,6 +43,9 @@ public class GroupService implements Serializable {
     @Resource
     protected FessConfig fessConfig;
 
+    @Resource
+    protected UserBhv userBhv;
+
     public List<Group> getGroupList(final GroupPager groupPager) {
 
         final PagingResultBean<Group> groupList = groupBhv.selectPage(cb -> {
@@ -61,6 +67,7 @@ public class GroupService implements Serializable {
     }
 
     public void store(final Group group) {
+        ComponentUtil.getLdapManager().insert(group);
 
         groupBhv.insertOrUpdate(group, op -> {
             op.setRefresh(true);
@@ -69,11 +76,19 @@ public class GroupService implements Serializable {
     }
 
     public void delete(final Group group) {
+        ComponentUtil.getLdapManager().delete(group);
 
         groupBhv.delete(group, op -> {
             op.setRefresh(true);
         });
 
+        userBhv.selectCursor(cb -> {
+            cb.query().setGroups_Equal(group.getId());
+        }, entity -> {
+            entity.setGroups(StreamUtil.of(entity.getGroups()).filter(s -> !s.equals(group.getId())).toArray(n -> new String[n]));
+            userBhv.insertOrUpdate(entity);
+        });
+
     }
 
     protected void setupListCondition(final GroupCB cb, final GroupPager groupPager) {

+ 3 - 0
src/main/java/org/codelibs/fess/app/service/PathMappingService.java

@@ -27,6 +27,7 @@ import org.codelibs.fess.app.pager.PathMapPager;
 import org.codelibs.fess.es.config.cbean.PathMappingCB;
 import org.codelibs.fess.es.config.exbhv.PathMappingBhv;
 import org.codelibs.fess.es.config.exentity.PathMapping;
+import org.codelibs.fess.util.ComponentUtil;
 import org.dbflute.cbean.result.PagingResultBean;
 import org.dbflute.optional.OptionalEntity;
 
@@ -63,6 +64,7 @@ public class PathMappingService implements Serializable {
             op.setRefresh(true);
         });
 
+        ComponentUtil.getPathMappingHelper().init();
     }
 
     public void delete(final PathMapping pathMapping) {
@@ -71,6 +73,7 @@ public class PathMappingService implements Serializable {
             op.setRefresh(true);
         });
 
+        ComponentUtil.getPathMappingHelper().init();
     }
 
     public List<PathMapping> getPathMappingList(final Collection<String> processTypeList) {

+ 14 - 0
src/main/java/org/codelibs/fess/app/service/RoleService.java

@@ -25,8 +25,11 @@ import org.codelibs.fess.Constants;
 import org.codelibs.fess.app.pager.RolePager;
 import org.codelibs.fess.es.user.cbean.RoleCB;
 import org.codelibs.fess.es.user.exbhv.RoleBhv;
+import org.codelibs.fess.es.user.exbhv.UserBhv;
 import org.codelibs.fess.es.user.exentity.Role;
 import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.util.ComponentUtil;
+import org.codelibs.fess.util.StreamUtil;
 import org.dbflute.cbean.result.PagingResultBean;
 import org.dbflute.optional.OptionalEntity;
 
@@ -40,6 +43,9 @@ public class RoleService implements Serializable {
     @Resource
     protected FessConfig fessConfig;
 
+    @Resource
+    protected UserBhv userBhv;
+
     public List<Role> getRoleList(final RolePager rolePager) {
 
         final PagingResultBean<Role> roleList = roleBhv.selectPage(cb -> {
@@ -61,6 +67,7 @@ public class RoleService implements Serializable {
     }
 
     public void store(final Role role) {
+        ComponentUtil.getLdapManager().insert(role);
 
         roleBhv.insertOrUpdate(role, op -> {
             op.setRefresh(true);
@@ -69,11 +76,18 @@ public class RoleService implements Serializable {
     }
 
     public void delete(final Role role) {
+        ComponentUtil.getLdapManager().delete(role);
 
         roleBhv.delete(role, op -> {
             op.setRefresh(true);
         });
 
+        userBhv.selectCursor(cb -> {
+            cb.query().setRoles_Equal(role.getId());
+        }, entity -> {
+            entity.setRoles(StreamUtil.of(entity.getRoles()).filter(s -> !s.equals(role.getId())).toArray(n -> new String[n]));
+            userBhv.insertOrUpdate(entity);
+        });
     }
 
     protected void setupListCondition(final RoleCB cb, final RolePager rolePager) {

+ 9 - 9
src/main/java/org/codelibs/fess/app/service/ScheduledJobService.java

@@ -109,7 +109,7 @@ public class ScheduledJobService implements Serializable {
         scheduledJobBhv.insertOrUpdate(scheduledJob, op -> {
             op.setRefresh(true);
         });
-        JobManager jobManager = ComponentUtil.getJobManager();
+        final JobManager jobManager = ComponentUtil.getJobManager();
         jobManager.schedule(cron -> register(cron, scheduledJob));
     }
 
@@ -121,7 +121,7 @@ public class ScheduledJobService implements Serializable {
         });
     }
 
-    protected void register(LaCron cron, final ScheduledJob scheduledJob) {
+    protected void register(final LaCron cron, final ScheduledJob scheduledJob) {
         if (scheduledJob == null) {
             throw new ScheduledJobException("No job.");
         }
@@ -146,7 +146,7 @@ public class ScheduledJobService implements Serializable {
         }
 
         final CronParamsSupplier paramsOp = () -> {
-            Map<String, Object> params = new HashMap<>();
+            final Map<String, Object> params = new HashMap<>();
             params.put(Constants.SCHEDULED_JOB, scheduledJob);
             return params;
         };
@@ -162,7 +162,7 @@ public class ScheduledJobService implements Serializable {
                 }
             } else if (StringUtil.isNotBlank(scheduledJob.getCronExpression())) {
                 logger.info("Starting Job " + id + ":" + scheduledJob.getName());
-                String cronExpression = scheduledJob.getCronExpression();
+                final String cronExpression = scheduledJob.getCronExpression();
                 job.reschedule(cronExpression, op -> op.changeNoticeLogToDebug().params(paramsOp));
             }
         }).orElse(
@@ -181,18 +181,18 @@ public class ScheduledJobService implements Serializable {
                 });
     }
 
-    private OptionalThing<LaScheduledJob> findJobByUniqueOf(LaJobUnique jobUnique) {
+    private OptionalThing<LaScheduledJob> findJobByUniqueOf(final LaJobUnique jobUnique) {
         final JobManager jobManager = ComponentUtil.getJobManager();
         try {
             return jobManager.findJobByUniqueOf(jobUnique);
-        } catch (Exception e) {
+        } catch (final Exception e) {
             return OptionalThing.empty();
         }
     }
 
     public void unregister(final ScheduledJob scheduledJob) {
         try {
-            JobManager jobManager = ComponentUtil.getJobManager();
+            final JobManager jobManager = ComponentUtil.getJobManager();
             if (jobManager.isSchedulingDone()) {
                 jobManager.findJobByUniqueOf(LaJobUnique.of(scheduledJob.getId())).ifPresent(job -> {
                     job.unschedule();
@@ -222,7 +222,7 @@ public class ScheduledJobService implements Serializable {
         return false;
     }
 
-    public void start(LaCron cron) {
+    public void start(final LaCron cron) {
         scheduledJobBhv.selectCursor(cb -> {
             cb.query().setAvailable_Equal(Constants.T);
             cb.query().addOrderBy_SortOrder_Asc();
@@ -230,7 +230,7 @@ public class ScheduledJobService implements Serializable {
         }, scheduledJob -> {
             try {
                 register(cron, scheduledJob);
-            } catch (Exception e) {
+            } catch (final Exception e) {
                 logger.error("Failed to start Job " + scheduledJob.getId(), e);
             }
         });

+ 25 - 0
src/main/java/org/codelibs/fess/app/service/UserService.java

@@ -23,9 +23,13 @@ import javax.annotation.Resource;
 import org.codelibs.core.beans.util.BeanUtil;
 import org.codelibs.fess.Constants;
 import org.codelibs.fess.app.pager.UserPager;
+import org.codelibs.fess.app.web.base.login.FessLoginAssist;
 import org.codelibs.fess.es.user.cbean.UserCB;
 import org.codelibs.fess.es.user.exbhv.UserBhv;
 import org.codelibs.fess.es.user.exentity.User;
+import org.codelibs.fess.exception.FessUserNotFoundException;
+import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.util.ComponentUtil;
 import org.dbflute.cbean.result.PagingResultBean;
 import org.dbflute.optional.OptionalEntity;
 
@@ -36,6 +40,9 @@ public class UserService implements Serializable {
     @Resource
     protected UserBhv userBhv;
 
+    @Resource
+    protected FessLoginAssist fessLoginAssist;
+
     public List<User> getUserList(final UserPager userPager) {
 
         final PagingResultBean<User> userList = userBhv.selectPage(cb -> {
@@ -57,6 +64,7 @@ public class UserService implements Serializable {
     }
 
     public void store(final User user) {
+        ComponentUtil.getLdapManager().insert(user);
 
         userBhv.insertOrUpdate(user, op -> {
             op.setRefresh(true);
@@ -64,7 +72,24 @@ public class UserService implements Serializable {
 
     }
 
+    public void chnagePassword(final String username, final String password) {
+        ComponentUtil.getLdapManager().changePassword(username, password);
+
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (fessConfig.isLdapAdminEnabled() && fessConfig.isLdapAdminSyncPassword() || !fessConfig.isLdapAdminEnabled()) {
+            userBhv.selectEntity(cb -> cb.query().setName_Equal(username)).ifPresent(entity -> {
+                final String encodedPassword = fessLoginAssist.encryptPassword(password);
+                entity.setPassword(encodedPassword);
+                userBhv.insertOrUpdate(entity, op -> op.setRefresh(true));
+            }).orElse(() -> {
+                throw new FessUserNotFoundException(username);
+            });
+        }
+
+    }
+
     public void delete(final User user) {
+        ComponentUtil.getLdapManager().delete(user);
 
         userBhv.delete(user, op -> {
             op.setRefresh(true);

+ 9 - 3
src/main/java/org/codelibs/fess/app/web/RootAction.java

@@ -15,8 +15,11 @@
  */
 package org.codelibs.fess.app.web;
 
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
 import org.codelibs.fess.app.web.base.FessSearchAction;
 import org.codelibs.fess.app.web.base.SearchForm;
+import org.codelibs.fess.util.RenderDataUtil;
 import org.lastaflute.web.Execute;
 import org.lastaflute.web.response.HtmlResponse;
 
@@ -43,9 +46,12 @@ public class RootAction extends FessSearchAction {
             op.setup(form -> {
                 buildLabelParams(form.fields);
             });
-        }).renderWith(data -> {
-            buildInitParams();
-        });
+        }).renderWith(
+                data -> {
+                    buildInitParams();
+                    RenderDataUtil.register(data, "notification",
+                            systemProperties.getProperty(Constants.NOTIFICATION_SEARCH_TOP, StringUtil.EMPTY));
+                });
     }
 
 }

+ 4 - 0
src/main/java/org/codelibs/fess/app/web/admin/general/AdminGeneralAction.java

@@ -142,6 +142,8 @@ public class AdminGeneralAction extends FessAdminAction {
         updateProperty(Constants.LDAP_SECURITY_PRINCIPAL, form.ldapSecurityPrincipal);
         updateProperty(Constants.LDAP_BASE_DN, form.ldapBaseDn);
         updateProperty(Constants.LDAP_ACCOUNT_FILTER, form.ldapAccountFilter);
+        updateProperty(Constants.NOTIFICATION_LOGIN, form.notificationLogin);
+        updateProperty(Constants.NOTIFICATION_SEARCH_TOP, form.notificationSearchTop);
 
         fessConfig.storeSystemProperties();
         saveInfo(messages -> messages.addSuccessUpdateCrawlerParams(GLOBAL));
@@ -185,6 +187,8 @@ public class AdminGeneralAction extends FessAdminAction {
         form.ldapSecurityPrincipal = systemProperties.getProperty(Constants.LDAP_SECURITY_PRINCIPAL, StringUtil.EMPTY);
         form.ldapBaseDn = systemProperties.getProperty(Constants.LDAP_BASE_DN, StringUtil.EMPTY);
         form.ldapAccountFilter = systemProperties.getProperty(Constants.LDAP_ACCOUNT_FILTER, StringUtil.EMPTY);
+        form.notificationLogin = systemProperties.getProperty(Constants.NOTIFICATION_LOGIN, StringUtil.EMPTY);
+        form.notificationSearchTop = systemProperties.getProperty(Constants.NOTIFICATION_SEARCH_TOP, StringUtil.EMPTY);
     }
 
     private void updateProperty(final String key, final String value) {

+ 6 - 0
src/main/java/org/codelibs/fess/app/web/admin/general/EditForm.java

@@ -127,4 +127,10 @@ public class EditForm implements Serializable {
 
     @Size(max = 1000)
     public String ldapAccountFilter;
+
+    @Size(max = 3000)
+    public String notificationLogin;
+
+    @Size(max = 3000)
+    public String notificationSearchTop;
 }

+ 18 - 38
src/main/java/org/codelibs/fess/app/web/admin/group/AdminGroupAction.java

@@ -32,6 +32,8 @@ import org.lastaflute.web.Execute;
 import org.lastaflute.web.response.HtmlResponse;
 import org.lastaflute.web.response.render.RenderData;
 import org.lastaflute.web.ruts.process.ActionRuntime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * @author shinsuke
@@ -39,6 +41,8 @@ import org.lastaflute.web.ruts.process.ActionRuntime;
  */
 public class AdminGroupAction extends FessAdminAction {
 
+    private static final Logger logger = LoggerFactory.getLogger(AdminGroupAction.class);
+
     // ===================================================================================
     //                                                                           Attribute
     //                                                                           =========
@@ -116,26 +120,6 @@ public class AdminGroupAction extends FessAdminAction {
         });
     }
 
-    @Execute
-    public HtmlResponse edit(final EditForm form) {
-        validate(form, messages -> {}, () -> asListHtml());
-        final String id = form.id;
-        groupService.getGroup(id).ifPresent(entity -> {
-            copyBeanToBean(entity, form, op -> {});
-        }).orElse(() -> {
-            throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), () -> asListHtml());
-        });
-        saveToken();
-        if (form.crudMode.intValue() == CrudMode.EDIT) {
-            // back
-            form.crudMode = CrudMode.DETAILS;
-            return asDetailsHtml();
-        } else {
-            form.crudMode = CrudMode.EDIT;
-            return asEditHtml();
-        }
-    }
-
     // -----------------------------------------------------
     //                                               Details
     //                                               -------
@@ -166,28 +150,19 @@ public class AdminGroupAction extends FessAdminAction {
         validate(form, messages -> {}, () -> asEditHtml());
         verifyToken(() -> asEditHtml());
         getGroup(form).ifPresent(entity -> {
-            groupService.store(entity);
-            saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
+            try {
+                groupService.store(entity);
+                saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
+            } catch (final Exception e) {
+                logger.error("Failed to add " + entity, e);
+                throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL), () -> asEditHtml());
+            }
         }).orElse(() -> {
             throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL), () -> asEditHtml());
         });
         return redirect(getClass());
     }
 
-    @Execute
-    public HtmlResponse update(final EditForm form) {
-        verifyCrudMode(form.crudMode, CrudMode.EDIT);
-        validate(form, messages -> {}, () -> asEditHtml());
-        verifyToken(() -> asEditHtml());
-        getGroup(form).ifPresent(entity -> {
-            groupService.store(entity);
-            saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
-        }).orElse(() -> {
-            throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.id), () -> asEditHtml());
-        });
-        return redirect(getClass());
-    }
-
     @Execute
     public HtmlResponse delete(final EditForm form) {
         verifyCrudMode(form.crudMode, CrudMode.DETAILS);
@@ -195,8 +170,13 @@ public class AdminGroupAction extends FessAdminAction {
         verifyToken(() -> asDetailsHtml());
         final String id = form.id;
         groupService.getGroup(id).ifPresent(entity -> {
-            groupService.delete(entity);
-            saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+            try {
+                groupService.delete(entity);
+                saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+            } catch (final Exception e) {
+                logger.error("Failed to delete " + entity, e);
+                throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), () -> asDetailsHtml());
+            }
         }).orElse(() -> {
             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), () -> asDetailsHtml());
         });

+ 1 - 1
src/main/java/org/codelibs/fess/app/web/admin/joblog/AdminJoblogAction.java

@@ -152,7 +152,7 @@ public class AdminJoblogAction extends FessAdminAction {
     @Execute
     public HtmlResponse deleteall() {
         verifyToken(() -> asListHtml());
-        List<String> jobStatusList = new ArrayList<>();
+        final List<String> jobStatusList = new ArrayList<>();
         jobStatusList.add(Constants.OK);
         jobStatusList.add(Constants.FAIL);
         jobLogService.deleteByJobStatus(jobStatusList);

+ 18 - 38
src/main/java/org/codelibs/fess/app/web/admin/role/AdminRoleAction.java

@@ -32,6 +32,8 @@ import org.lastaflute.web.Execute;
 import org.lastaflute.web.response.HtmlResponse;
 import org.lastaflute.web.response.render.RenderData;
 import org.lastaflute.web.ruts.process.ActionRuntime;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * @author shinsuke
@@ -39,6 +41,8 @@ import org.lastaflute.web.ruts.process.ActionRuntime;
  */
 public class AdminRoleAction extends FessAdminAction {
 
+    private static final Logger logger = LoggerFactory.getLogger(AdminRoleAction.class);
+
     // ===================================================================================
     //                                                                           Attribute
     //                                                                           =========
@@ -116,26 +120,6 @@ public class AdminRoleAction extends FessAdminAction {
         });
     }
 
-    @Execute
-    public HtmlResponse edit(final EditForm form) {
-        validate(form, messages -> {}, () -> asListHtml());
-        final String id = form.id;
-        roleService.getRole(id).ifPresent(entity -> {
-            copyBeanToBean(entity, form, op -> {});
-        }).orElse(() -> {
-            throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), () -> asListHtml());
-        });
-        saveToken();
-        if (form.crudMode.intValue() == CrudMode.EDIT) {
-            // back
-            form.crudMode = CrudMode.DETAILS;
-            return asDetailsHtml();
-        } else {
-            form.crudMode = CrudMode.EDIT;
-            return asEditHtml();
-        }
-    }
-
     // -----------------------------------------------------
     //                                               Details
     //                                               -------
@@ -166,28 +150,19 @@ public class AdminRoleAction extends FessAdminAction {
         validate(form, messages -> {}, () -> asEditHtml());
         verifyToken(() -> asEditHtml());
         getRole(form).ifPresent(entity -> {
-            roleService.store(entity);
-            saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
+            try {
+                roleService.store(entity);
+                saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
+            } catch (final Exception e) {
+                logger.error("Failed to add " + entity, e);
+                throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL), () -> asEditHtml());
+            }
         }).orElse(() -> {
             throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL), () -> asEditHtml());
         });
         return redirect(getClass());
     }
 
-    @Execute
-    public HtmlResponse update(final EditForm form) {
-        verifyCrudMode(form.crudMode, CrudMode.EDIT);
-        validate(form, messages -> {}, () -> asEditHtml());
-        verifyToken(() -> asEditHtml());
-        getRole(form).ifPresent(entity -> {
-            roleService.store(entity);
-            saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
-        }).orElse(() -> {
-            throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.id), () -> asEditHtml());
-        });
-        return redirect(getClass());
-    }
-
     @Execute
     public HtmlResponse delete(final EditForm form) {
         verifyCrudMode(form.crudMode, CrudMode.DETAILS);
@@ -195,8 +170,13 @@ public class AdminRoleAction extends FessAdminAction {
         verifyToken(() -> asDetailsHtml());
         final String id = form.id;
         roleService.getRole(id).ifPresent(entity -> {
-            roleService.delete(entity);
-            saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+            try {
+                roleService.delete(entity);
+                saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+            } catch (final Exception e) {
+                logger.error("Failed to delete " + entity, e);
+                throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), () -> asDetailsHtml());
+            }
         }).orElse(() -> {
             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), () -> asDetailsHtml());
         });

+ 1 - 1
src/main/java/org/codelibs/fess/app/web/admin/scheduler/AdminSchedulerAction.java

@@ -354,7 +354,7 @@ public class AdminSchedulerAction extends FessAdminAction {
         return asHtml(path_AdminScheduler_AdminSchedulerEditJsp);
     }
 
-    private HtmlResponse asDetailsHtml(String id) {
+    private HtmlResponse asDetailsHtml(final String id) {
         return asHtml(path_AdminScheduler_AdminSchedulerDetailsJsp).renderWith(data -> {
             RenderDataUtil.register(data, "systemJobId", fessConfig.isSystemJobId(id));
         });

+ 13 - 83
src/main/java/org/codelibs/fess/app/web/admin/searchlist/AdminSearchlistAction.java

@@ -121,16 +121,13 @@ public class AdminSearchlistAction extends FessAdminAction {
             // query matches on all documents.
             form.q = Constants.MATCHES_ALL_QUERY;
         }
-        return asListHtml().renderWith(data -> {
-            doSearchInternal(data, form);
-        });
-    }
-
-    private void doSearchInternal(final RenderData data, final ListForm form) {
+        final WebRenderData renderData = new WebRenderData();
         form.initialize();
         try {
-            final WebRenderData renderData = new WebRenderData(data);
             searchService.search(request, form, renderData);
+            return asListHtml().renderWith(data -> {
+                renderData.register(data);
+            });
         } catch (final InvalidQueryException e) {
             if (logger.isDebugEnabled()) {
                 logger.debug(e.getMessage(), e);
@@ -140,10 +137,11 @@ public class AdminSearchlistAction extends FessAdminAction {
             if (logger.isDebugEnabled()) {
                 logger.debug(e.getMessage(), e);
             }
-            throwValidationError(messages -> {
-                messages.addErrorsResultSizeExceeded(GLOBAL);
-            }, () -> asHtml(path_AdminError_AdminErrorJsp));
+            throwValidationError(messages -> messages.addErrorsResultSizeExceeded(GLOBAL), () -> asListHtml());
         }
+
+        throwValidationError(messages -> messages.addErrorsInvalidQueryUnknown(GLOBAL), () -> asListHtml());
+        return null; // ignore
     }
 
     @Execute
@@ -233,94 +231,26 @@ public class AdminSearchlistAction extends FessAdminAction {
     }
 
     protected static class WebRenderData extends SearchRenderData {
-        private final RenderData data;
-
-        WebRenderData(final RenderData data) {
-            this.data = data;
-        }
 
-        @Override
-        public void setDocumentItems(final List<Map<String, Object>> documentItems) {
+        public void register(final RenderData data) {
+            RenderDataUtil.register(data, "queryId", queryId);
             RenderDataUtil.register(data, "documentItems", documentItems);
-            super.setDocumentItems(documentItems);
-        }
-
-        @Override
-        public void setExecTime(final String execTime) {
+            RenderDataUtil.register(data, "facetResponse", facetResponse);
+            RenderDataUtil.register(data, "appendHighlightParams", appendHighlightParams);
             RenderDataUtil.register(data, "execTime", execTime);
-            super.setExecTime(execTime);
-        }
-
-        @Override
-        public void setPageSize(final int pageSize) {
             RenderDataUtil.register(data, "pageSize", pageSize);
-            super.setPageSize(pageSize);
-        }
-
-        @Override
-        public void setCurrentPageNumber(final int currentPageNumber) {
             RenderDataUtil.register(data, "currentPageNumber", currentPageNumber);
-            super.setCurrentPageNumber(currentPageNumber);
-        }
-
-        @Override
-        public void setAllRecordCount(final long allRecordCount) {
             RenderDataUtil.register(data, "allRecordCount", allRecordCount);
-            super.setAllRecordCount(allRecordCount);
-        }
-
-        @Override
-        public void setAllPageCount(final int allPageCount) {
             RenderDataUtil.register(data, "allPageCount", allPageCount);
-            super.setAllPageCount(allPageCount);
-        }
-
-        @Override
-        public void setExistNextPage(final boolean existNextPage) {
             RenderDataUtil.register(data, "existNextPage", existNextPage);
-            super.setExistNextPage(existNextPage);
-        }
-
-        @Override
-        public void setExistPrevPage(final boolean existPrevPage) {
             RenderDataUtil.register(data, "existPrevPage", existPrevPage);
-            super.setExistPrevPage(existPrevPage);
-        }
-
-        @Override
-        public void setCurrentStartRecordNumber(final long currentStartRecordNumber) {
             RenderDataUtil.register(data, "currentStartRecordNumber", currentStartRecordNumber);
-            super.setCurrentStartRecordNumber(currentStartRecordNumber);
-        }
-
-        @Override
-        public void setCurrentEndRecordNumber(final long currentEndRecordNumber) {
             RenderDataUtil.register(data, "currentEndRecordNumber", currentEndRecordNumber);
-            super.setCurrentEndRecordNumber(currentEndRecordNumber);
-        }
-
-        @Override
-        public void setPageNumberList(final List<String> pageNumberList) {
             RenderDataUtil.register(data, "pageNumberList", pageNumberList);
-            super.setPageNumberList(pageNumberList);
-        }
-
-        @Override
-        public void setPartialResults(final boolean partialResults) {
             RenderDataUtil.register(data, "partialResults", partialResults);
-            super.setPartialResults(partialResults);
-        }
-
-        @Override
-        public void setQueryTime(final long queryTime) {
             RenderDataUtil.register(data, "queryTime", queryTime);
-            super.setQueryTime(queryTime);
-        }
-
-        @Override
-        public void setSearchQuery(final String searchQuery) {
             RenderDataUtil.register(data, "searchQuery", searchQuery);
-            super.setSearchQuery(searchQuery);
+            RenderDataUtil.register(data, "requestedTime", requestedTime);
         }
     }
 }

+ 26 - 6
src/main/java/org/codelibs/fess/app/web/admin/user/AdminUserAction.java

@@ -39,6 +39,8 @@ import org.lastaflute.web.response.HtmlResponse;
 import org.lastaflute.web.response.render.RenderData;
 import org.lastaflute.web.ruts.process.ActionRuntime;
 import org.lastaflute.web.validation.VaErrorHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * @author shinsuke
@@ -46,6 +48,8 @@ import org.lastaflute.web.validation.VaErrorHook;
  */
 public class AdminUserAction extends FessAdminAction {
 
+    private static final Logger logger = LoggerFactory.getLogger(AdminUserAction.class);
+
     // ===================================================================================
     //                                                                           Attribute
     //                                                                           =========
@@ -188,8 +192,13 @@ public class AdminUserAction extends FessAdminAction {
         verifyPassword(form, () -> asEditHtml());
         verifyToken(() -> asEditHtml());
         getUser(form).ifPresent(entity -> {
-            userService.store(entity);
-            saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
+            try {
+                userService.store(entity);
+                saveInfo(messages -> messages.addSuccessCrudCreateCrudTable(GLOBAL));
+            } catch (final Exception e) {
+                logger.error("Failed to add " + entity, e);
+                throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL), () -> asEditHtml());
+            }
         }).orElse(() -> {
             throwValidationError(messages -> messages.addErrorsCrudFailedToCreateCrudTable(GLOBAL), () -> asEditHtml());
         });
@@ -203,8 +212,13 @@ public class AdminUserAction extends FessAdminAction {
         verifyPassword(form, () -> asEditHtml());
         verifyToken(() -> asEditHtml());
         getUser(form).ifPresent(entity -> {
-            userService.store(entity);
-            saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
+            try {
+                userService.store(entity);
+                saveInfo(messages -> messages.addSuccessCrudUpdateCrudTable(GLOBAL));
+            } catch (final Exception e) {
+                logger.error("Failed to update " + entity, e);
+                throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.id), () -> asEditHtml());
+            }
         }).orElse(() -> {
             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, form.id), () -> asEditHtml());
         });
@@ -217,8 +231,13 @@ public class AdminUserAction extends FessAdminAction {
         validate(form, messages -> {}, () -> asDetailsHtml());
         final String id = form.id;
         userService.getUser(id).ifPresent(entity -> {
-            userService.delete(entity);
-            saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+            try {
+                userService.delete(entity);
+                saveInfo(messages -> messages.addSuccessCrudDeleteCrudTable(GLOBAL));
+            } catch (final Exception e) {
+                logger.error("Failed to delete " + entity, e);
+                throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), () -> asDetailsHtml());
+            }
         }).orElse(() -> {
             throwValidationError(messages -> messages.addErrorsCrudCouldNotFindCrudTable(GLOBAL, id), () -> asDetailsHtml());
         });
@@ -251,6 +270,7 @@ public class AdminUserAction extends FessAdminAction {
             copyBeanToBean(form, entity, op -> op.exclude(ArrayUtils.addAll(Constants.COMMON_CONVERSION_RULE, "password")));
             if (form.crudMode.intValue() == CrudMode.CREATE || StringUtil.isNotBlank(form.password)) {
                 final String encodedPassword = fessLoginAssist.encryptPassword(form.password);
+                entity.setOriginalPassword(form.password);
                 entity.setPassword(encodedPassword);
             }
             return entity;

+ 1 - 1
src/main/java/org/codelibs/fess/app/web/admin/wizard/AdminWizardAction.java

@@ -288,7 +288,7 @@ public class AdminWizardAction extends FessAdminAction {
         verifyToken(() -> asIndexHtml());
         if (!processHelper.isProcessRunning()) {
             final List<ScheduledJob> scheduledJobList = scheduledJobService.getCrawlerJobList();
-            JobManager jobManager = ComponentUtil.getJobManager();
+            final JobManager jobManager = ComponentUtil.getJobManager();
             for (final ScheduledJob scheduledJob : scheduledJobList) {
                 jobManager.findJobByUniqueOf(LaJobUnique.of(scheduledJob.getId())).ifPresent(job -> {
                     job.launchNow();

+ 2 - 2
src/main/java/org/codelibs/fess/app/web/base/FessAdminAction.java

@@ -107,7 +107,7 @@ public abstract class FessAdminAction extends FessBaseAction {
     }
 
     @Override
-    public ActionResponse hookBefore(ActionRuntime runtime) {
+    public ActionResponse hookBefore(final ActionRuntime runtime) {
         final String username = getUserBean().map(u -> u.getUserId()).orElse("-");
         final String requestPath = runtime.getRequestPath();
         final String executeName = runtime.getExecuteMethod().getName();
@@ -116,7 +116,7 @@ public abstract class FessAdminAction extends FessBaseAction {
     }
 
     @Override
-    public void hookFinally(ActionRuntime runtime) {
+    public void hookFinally(final ActionRuntime runtime) {
         super.hookFinally(runtime);
     }
 

+ 5 - 3
src/main/java/org/codelibs/fess/app/web/base/login/FessLoginAssist.java

@@ -68,9 +68,11 @@ public class FessLoginAssist extends TypicalLoginAssist<String, FessUserBean, Fe
 
     @Override
     public OptionalEntity<FessUser> findLoginUser(final String username, final String password) {
-        final OptionalEntity<FessUser> ldapUser = ComponentUtil.getLdapManager().login(username, password);
-        if (ldapUser.isPresent()) {
-            return ldapUser;
+        if (!fessConfig.isAdminUser(username)) {
+            final OptionalEntity<FessUser> ldapUser = ComponentUtil.getLdapManager().login(username, password);
+            if (ldapUser.isPresent()) {
+                return ldapUser;
+            }
         }
         return doFindLoginUser(username, encryptPassword(password));
     }

+ 1 - 2
src/main/java/org/codelibs/fess/app/web/cache/CacheAction.java

@@ -48,12 +48,11 @@ public class CacheAction extends FessSearchAction {
     //                                                                      ==============
     @Execute
     public ActionResponse index(final CacheForm form) {
+        validate(form, messages -> {}, () -> asHtml(path_Error_ErrorJsp));
         if (isLoginRequired()) {
             return redirectToLogin();
         }
 
-        validate(form, messages -> {}, () -> asHtml(path_Error_ErrorJsp));
-
         Map<String, Object> doc = null;
         try {
             doc =

+ 12 - 5
src/main/java/org/codelibs/fess/app/web/go/GoAction.java

@@ -20,6 +20,8 @@ import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.Map;
 
+import javax.annotation.Resource;
+
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.core.net.URLUtil;
 import org.codelibs.fess.Constants;
@@ -27,6 +29,7 @@ import org.codelibs.fess.app.web.base.FessSearchAction;
 import org.codelibs.fess.app.web.error.ErrorAction;
 import org.codelibs.fess.crawler.util.CharUtil;
 import org.codelibs.fess.es.log.exentity.ClickLog;
+import org.codelibs.fess.helper.PathMappingHelper;
 import org.codelibs.fess.helper.SearchLogHelper;
 import org.codelibs.fess.helper.ViewHelper;
 import org.codelibs.fess.util.ComponentUtil;
@@ -47,6 +50,9 @@ public class GoAction extends FessSearchAction {
     //
     private static final Logger logger = LoggerFactory.getLogger(GoAction.class);
 
+    @Resource
+    protected PathMappingHelper pathMappingHelper;
+
     // ===================================================================================
     //                                                                           Attribute
     //
@@ -60,10 +66,10 @@ public class GoAction extends FessSearchAction {
     //                                                                      ==============
     @Execute
     public ActionResponse index(final GoForm form) throws IOException {
+        validate(form, messages -> {}, () -> asHtml(path_Error_ErrorJsp));
         if (isLoginRequired()) {
             return redirectToLogin();
         }
-        validate(form, messages -> {}, () -> asHtml(path_Error_ErrorJsp));
 
         Map<String, Object> doc = null;
         try {
@@ -126,21 +132,22 @@ public class GoAction extends FessSearchAction {
             hash = StringUtil.EMPTY;
         }
 
-        if (isFileSystemPath(url)) {
+        final String targetUrl = pathMappingHelper.replaceUrl(url);
+        if (isFileSystemPath(targetUrl)) {
             if (Constants.TRUE.equals(systemProperties.getProperty(Constants.SEARCH_FILE_PROXY_PROPERTY, Constants.TRUE))) {
                 final ViewHelper viewHelper = ComponentUtil.getViewHelper();
                 try {
                     return viewHelper.asContentResponse(doc);
                 } catch (final Exception e) {
                     logger.debug("Failed to load: " + doc, e);
-                    saveError(messages -> messages.addErrorsNotLoadFromServer(GLOBAL, url));
+                    saveError(messages -> messages.addErrorsNotLoadFromServer(GLOBAL, targetUrl));
                     return redirect(ErrorAction.class);
                 }
             } else {
-                return HtmlResponse.fromRedirectPathAsIs(url + hash);
+                return HtmlResponse.fromRedirectPathAsIs(targetUrl + hash);
             }
         } else {
-            return HtmlResponse.fromRedirectPathAsIs(url + hash);
+            return HtmlResponse.fromRedirectPathAsIs(targetUrl + hash);
         }
     }
 

+ 6 - 1
src/main/java/org/codelibs/fess/app/web/login/LoginAction.java

@@ -15,10 +15,13 @@
  */
 package org.codelibs.fess.app.web.login;
 
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
 import org.codelibs.fess.app.web.admin.dashboard.AdminDashboardAction;
 import org.codelibs.fess.app.web.base.FessSearchAction;
 import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.util.ActivityUtil;
+import org.codelibs.fess.util.RenderDataUtil;
 import org.lastaflute.web.Execute;
 import org.lastaflute.web.login.exception.LoginFailureException;
 import org.lastaflute.web.response.HtmlResponse;
@@ -43,7 +46,9 @@ public class LoginAction extends FessSearchAction {
 
     @Execute
     public HtmlResponse index() {
-        return getHtmlResponse().useForm(LoginForm.class);
+        return asHtml(path_Login_IndexJsp).renderWith(data -> {
+            RenderDataUtil.register(data, "notification", systemProperties.getProperty(Constants.NOTIFICATION_LOGIN, StringUtil.EMPTY));
+        }).useForm(LoginForm.class);
     }
 
     @Execute

+ 102 - 0
src/main/java/org/codelibs/fess/app/web/profile/ProfileAction.java

@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/**
+ * @author Keiichi Watanabe
+ * @author shinsuke
+ */
+package org.codelibs.fess.app.web.profile;
+
+import javax.annotation.Resource;
+
+import org.codelibs.fess.app.service.UserService;
+import org.codelibs.fess.app.web.base.FessSearchAction;
+import org.codelibs.fess.app.web.login.LoginAction;
+import org.lastaflute.web.Execute;
+import org.lastaflute.web.response.HtmlResponse;
+import org.lastaflute.web.validation.VaErrorHook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProfileAction extends FessSearchAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ProfileAction.class);
+
+    // ===================================================================================
+    // Constant
+    //
+
+    // ===================================================================================
+    // Attribute
+    //
+    @Resource
+    private UserService userService;
+
+    // ===================================================================================
+    // Hook
+    // ======
+
+    // ===================================================================================
+    // Search Execute
+    // ==============
+
+    @Execute
+    public HtmlResponse index() {
+        return asIndexHtml();
+    }
+
+    @Execute
+    public HtmlResponse changePassword(final ProfileForm form) {
+        final VaErrorHook toIndexPage = () -> {
+            form.clearSecurityInfo();
+            return asIndexHtml();
+        };
+        validatePasswordForm(form, toIndexPage);
+        final String username = getUserBean().map(u -> u.getUserId()).get();
+        try {
+            userService.chnagePassword(username, form.newPassword);
+            saveInfo(messages -> messages.addSuccessChangedPassword(GLOBAL));
+        } catch (final Exception e) {
+            logger.error("Failed to change password for " + username, e);
+            throwValidationError(messages -> messages.addErrorsFailedToChangePassword(GLOBAL), toIndexPage);
+        }
+        return redirect(getClass());
+    }
+
+    private void validatePasswordForm(final ProfileForm form, final VaErrorHook validationErrorLambda) {
+        validate(form, messages -> {}, validationErrorLambda);
+
+        if (!form.newPassword.equals(form.confirmNewPassword)) {
+            form.newPassword = null;
+            form.confirmNewPassword = null;
+            throwValidationError(messages -> {
+                messages.addErrorsInvalidConfirmPassword(GLOBAL);
+            }, validationErrorLambda);
+        }
+
+        fessLoginAssist.findLoginUser(getUserBean().get().getUserId(), form.oldPassword).orElseGet(() -> {
+            throwValidationError(messages -> {
+                messages.addErrorsNoUserForChangingPassword(GLOBAL);
+            }, validationErrorLambda);
+            return null;
+        });
+    }
+
+    protected HtmlResponse asIndexHtml() {
+        return getUserBean().map(u -> asHtml(path_Profile_IndexJsp).useForm(ProfileForm.class))
+                .orElseGet(() -> redirect(LoginAction.class));
+    }
+}

+ 44 - 0
src/main/java/org/codelibs/fess/app/web/profile/ProfileForm.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+/**
+ * @author Keiichi Watanabe
+ */
+package org.codelibs.fess.app.web.profile;
+
+import java.io.Serializable;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+public class ProfileForm implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @NotBlank
+    public String oldPassword;
+
+    @NotBlank
+    public String newPassword;
+
+    @NotBlank
+    public String confirmNewPassword;
+
+    public void clearSecurityInfo() {
+        oldPassword = null;
+        newPassword = null;
+        confirmNewPassword = null;
+    }
+
+}

+ 1 - 2
src/main/java/org/codelibs/fess/app/web/screenshot/ScreenshotAction.java

@@ -48,12 +48,11 @@ public class ScreenshotAction extends FessSearchAction {
     //                                                                      ==============
     @Execute
     public ActionResponse index(final ScreenshotForm form) {
+        validate(form, messages -> {}, () -> asHtml(path_Error_ErrorJsp));
         if (isLoginRequired()) {
             return redirectToLogin();
         }
 
-        validate(form, messages -> {}, () -> asHtml(path_Error_ErrorJsp));
-
         final Map<String, Object> doc =
                 fessEsClient.getDocument(fessConfig.getIndexDocumentSearchIndex(), fessConfig.getIndexDocumentType(),
                         queryRequestBuilder -> {

+ 1 - 1
src/main/java/org/codelibs/fess/app/web/search/SearchAction.java

@@ -96,10 +96,10 @@ public class SearchAction extends FessSearchAction {
     }
 
     protected HtmlResponse doSearch(final SearchForm form) {
+        validate(form, messages -> {}, () -> asHtml(path_SearchJsp));
         if (isLoginRequired()) {
             return redirectToLogin();
         }
-        validate(form, messages -> {}, () -> asHtml(path_SearchJsp));
 
         if (viewHelper.isUseSession()) {
             final HttpSession session = request.getSession(false);

+ 17 - 14
src/main/java/org/codelibs/fess/crawler/transformer/AbstractFessFileTransformer.java

@@ -82,6 +82,19 @@ public abstract class AbstractFessFileTransformer extends AbstractTransformer im
             throw new CrawlingAccessException("No response body.");
         }
 
+        final ResultData resultData = new ResultData();
+        resultData.setTransformerName(getName());
+        try {
+            resultData.setData(SerializeUtil.fromObjectToBinary(generateData(responseData)));
+        } catch (final Exception e) {
+            throw new CrawlingAccessException("Could not serialize object", e);
+        }
+        resultData.setEncoding(fessConfig.getCrawlerCrawlingDataEncoding());
+
+        return resultData;
+    }
+
+    protected Map<String, Object> generateData(final ResponseData responseData) {
         final Extractor extractor = getExtractor(responseData);
         final Map<String, String> params = new HashMap<String, String>();
         params.put(TikaMetadataKeys.RESOURCE_NAME_KEY, getResourceName(responseData));
@@ -119,7 +132,7 @@ public abstract class AbstractFessFileTransformer extends AbstractTransformer im
                                 }
                             }
 
-                            Pair<String, String> mapping = fessConfig.getCrawlerMetadataNameMapping(key);
+                            final Pair<String, String> mapping = fessConfig.getCrawlerMetadataNameMapping(key);
                             if (mapping != null) {
                                 if (Constants.MAPPING_TYPE_ARRAY.equalsIgnoreCase(mapping.getSecond())) {
                                     dataMap.put(mapping.getFirst(), values);
@@ -135,7 +148,7 @@ public abstract class AbstractFessFileTransformer extends AbstractTransformer im
                                         } else {
                                             logger.warn("Unknown mapping type: {}={}", key, mapping);
                                         }
-                                    } catch (NumberFormatException e) {
+                                    } catch (final NumberFormatException e) {
                                         logger.warn("Failed to parse " + values[0], e);
                                     }
                                 }
@@ -152,9 +165,6 @@ public abstract class AbstractFessFileTransformer extends AbstractTransformer im
         }
         final String contentMeta = contentMetaBuf.toString();
 
-        final ResultData resultData = new ResultData();
-        resultData.setTransformerName(getName());
-
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final CrawlingInfoHelper crawlingInfoHelper = ComponentUtil.getCrawlingInfoHelper();
         final String sessionId = crawlingInfoHelper.getCanonicalSessionId(responseData.getSessionId());
@@ -208,7 +218,7 @@ public abstract class AbstractFessFileTransformer extends AbstractTransformer im
             putResultDataBody(dataMap, fessConfig.getIndexFieldContent(), StringUtil.EMPTY);
         }
         if ((Constants.TRUE.equalsIgnoreCase(fieldConfigMap.get(fessConfig.getIndexFieldCache())) || fessConfig
-                .isCrawlerDocumentCacheEnable()) && fessConfig.isSupportedDocumentCacheMimetypes(mimeType)) {
+                .isCrawlerDocumentCacheEnabled()) && fessConfig.isSupportedDocumentCacheMimetypes(mimeType)) {
             if (responseData.getContentLength() > 0
                     && responseData.getContentLength() <= fessConfig.getCrawlerDocumentCacheMaxSizeAsInteger().longValue()) {
 
@@ -335,14 +345,7 @@ public abstract class AbstractFessFileTransformer extends AbstractTransformer im
             putResultDataWithTemplate(dataMap, key, entry.getValue(), scriptConfigMap.get(key));
         }
 
-        try {
-            resultData.setData(SerializeUtil.fromObjectToBinary(dataMap));
-        } catch (final Exception e) {
-            throw new CrawlingAccessException("Could not serialize object: " + url, e);
-        }
-        resultData.setEncoding(fessConfig.getCrawlerCrawlingDataEncoding());
-
-        return resultData;
+        return dataMap;
     }
 
     protected String abbreviate(final String str, final int maxWidth) {

+ 1 - 1
src/main/java/org/codelibs/fess/crawler/transformer/FessXpathTransformer.java

@@ -210,7 +210,7 @@ public class FessXpathTransformer extends XpathTransformer implements FessTransf
         // content
         putResultDataBody(dataMap, fessConfig.getIndexFieldContent(), getDocumentContent(responseData, document));
         if ((Constants.TRUE.equalsIgnoreCase(fieldConfigMap.get(fessConfig.getIndexFieldCache())) || fessConfig
-                .isCrawlerDocumentCacheEnable()) && fessConfig.isSupportedDocumentCacheMimetypes(mimeType)) {
+                .isCrawlerDocumentCacheEnabled()) && fessConfig.isSupportedDocumentCacheMimetypes(mimeType)) {
             if (responseData.getContentLength() > 0
                     && responseData.getContentLength() <= fessConfig.getCrawlerDocumentCacheMaxSizeAsInteger().longValue()) {
                 String charSet = responseData.getCharSet();

+ 1 - 1
src/main/java/org/codelibs/fess/es/client/FessEsClient.java

@@ -540,7 +540,7 @@ public class FessEsClient implements Client {
 
             try {
                 searchResponse = searchRequestBuilder.execute().actionGet();
-            } catch (SearchPhaseExecutionException e) {
+            } catch (final SearchPhaseExecutionException e) {
                 throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryParseError(UserMessages.GLOBAL_PROPERTY_KEY),
                         "Invalid query: " + searchRequestBuilder, e);
             }

+ 2 - 1
src/main/java/org/codelibs/fess/es/config/exentity/ScheduledJob.java

@@ -28,8 +28,9 @@ public class ScheduledJob extends BsScheduledJob {
 
     private static final long serialVersionUID = 1L;
 
+    @Override
     public String getScriptType() {
-        String st = super.getScriptType();
+        final String st = super.getScriptType();
         if (StringUtil.isBlank(st)) {
             return "groovy";
         }

+ 5 - 0
src/main/java/org/codelibs/fess/es/user/exentity/Group.java

@@ -40,4 +40,9 @@ public class Group extends BsGroup {
         asDocMeta().id(id);
     }
 
+    @Override
+    public String toString() {
+        return "Group [name=" + name + "]";
+    }
+
 }

+ 5 - 0
src/main/java/org/codelibs/fess/es/user/exentity/Role.java

@@ -39,4 +39,9 @@ public class Role extends BsRole {
     public void setId(final String id) {
         asDocMeta().id(id);
     }
+
+    @Override
+    public String toString() {
+        return "Role [name=" + name + "]";
+    }
 }

+ 17 - 0
src/main/java/org/codelibs/fess/es/user/exentity/User.java

@@ -15,6 +15,7 @@
  */
 package org.codelibs.fess.es.user.exentity;
 
+import java.util.Arrays;
 import java.util.Base64;
 
 import org.codelibs.fess.Constants;
@@ -29,6 +30,8 @@ public class User extends BsUser implements FessUser {
 
     private static final long serialVersionUID = 1L;
 
+    private String originalPassword;
+
     public Long getVersionNo() {
         return asDocMeta().version();
     }
@@ -57,4 +60,18 @@ public class User extends BsUser implements FessUser {
                 .toArray(n -> new String[n]);
     }
 
+    @Override
+    public String toString() {
+        return "User [name=" + name + ", roles=" + Arrays.toString(roles) + ", groups=" + Arrays.toString(groups) + "]";
+    }
+
+    public void setOriginalPassword(final String originalPassword) {
+        this.originalPassword = originalPassword;
+
+    }
+
+    public String getOriginalPassword() {
+        return originalPassword;
+    }
+
 }

+ 26 - 0
src/main/java/org/codelibs/fess/exception/FessUserNotFoundException.java

@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.exception;
+
+public class FessUserNotFoundException extends FessSystemException {
+
+    private static final long serialVersionUID = 1L;
+
+    public FessUserNotFoundException(final String username) {
+        super("User is not found: " + username);
+    }
+
+}

+ 30 - 0
src/main/java/org/codelibs/fess/exception/LdapOperationException.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.exception;
+
+public class LdapOperationException extends FessSystemException {
+
+    private static final long serialVersionUID = 1L;
+
+    public LdapOperationException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+
+    public LdapOperationException(final String message) {
+        super(message);
+    }
+
+}

+ 2 - 2
src/main/java/org/codelibs/fess/exception/WebApiException.java

@@ -43,10 +43,10 @@ public class WebApiException extends FessSystemException {
         this(statusCode, e.getMessage(), e);
     }
 
-    public void sendError(HttpServletResponse response) {
+    public void sendError(final HttpServletResponse response) {
         try {
             response.sendError(statusCode, getMessage());
-        } catch (IOException e) {
+        } catch (final IOException e) {
             throw new FessSystemException("SC:" + statusCode + ": " + getMessage(), e);
         }
     }

+ 2 - 2
src/main/java/org/codelibs/fess/exec/Crawler.java

@@ -312,8 +312,8 @@ public class Crawler implements Serializable {
         }
     }
 
-    private String getValueFromMap(Map<String, String> dataMap, String key, String defaultValue) {
-        String value = dataMap.get(key);
+    private String getValueFromMap(final Map<String, String> dataMap, final String key, final String defaultValue) {
+        final String value = dataMap.get(key);
         if (StringUtil.isBlank(value)) {
             return defaultValue;
         }

+ 2 - 2
src/main/java/org/codelibs/fess/helper/ProcessHelper.java

@@ -44,8 +44,8 @@ public class ProcessHelper {
         }
     }
 
-    public synchronized JobProcess startProcess(String sessionId, List<String> cmdList, Consumer<ProcessBuilder> pbCall) {
-        ProcessBuilder pb = new ProcessBuilder(cmdList);
+    public synchronized JobProcess startProcess(final String sessionId, final List<String> cmdList, final Consumer<ProcessBuilder> pbCall) {
+        final ProcessBuilder pb = new ProcessBuilder(cmdList);
         pbCall.accept(pb);
         destroyProcess(sessionId);
         JobProcess jobProcess;

+ 2 - 2
src/main/java/org/codelibs/fess/helper/QueryHelper.java

@@ -434,8 +434,8 @@ public class QueryHelper implements Serializable {
         }
     }
 
-    private QueryBuilder convertPhraseQuery(QueryContext context, PhraseQuery query) {
-        Term[] terms = query.getTerms();
+    private QueryBuilder convertPhraseQuery(final QueryContext context, final PhraseQuery query) {
+        final Term[] terms = query.getTerms();
         if (terms.length == 0) {
             throw new InvalidQueryException(messages -> messages.addErrorsInvalidQueryUnknown(UserMessages.GLOBAL_PROPERTY_KEY),
                     "Unknown phrase query: " + query);

+ 202 - 8
src/main/java/org/codelibs/fess/helper/RoleQueryHelper.java

@@ -15,15 +15,209 @@
  */
 package org.codelibs.fess.helper;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
-public interface RoleQueryHelper {
+import javax.annotation.PostConstruct;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
 
-    /**
-     * Returns roles.
-     *
-     * @return a list of a role. (not null)
-     */
-    Set<String> build();
+import org.codelibs.core.crypto.CachedCipher;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.mylasta.action.FessUserBean;
+import org.codelibs.fess.util.ComponentUtil;
+import org.codelibs.fess.util.StreamUtil;
+import org.lastaflute.web.servlet.request.RequestManager;
+import org.lastaflute.web.util.LaRequestUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-}
+/**
+ * This class returns a list of a role from a request parameter,
+ * a request header and a cookie. The format of the default value
+ * is "[\d]+\nrole1,role2,role3", which you can encrypt.
+ *
+ * @author shinsuke
+ *
+ */
+public class RoleQueryHelper {
+
+    private static final Logger logger = LoggerFactory.getLogger(RoleQueryHelper.class);
+
+    public CachedCipher cipher;
+
+    public String valueSeparator = "\n";
+
+    public String roleSeparator = ",";
+
+    public String parameterKey;
+
+    public boolean encryptedParameterValue = true;
+
+    public String headerKey;
+
+    public boolean encryptedHeaderValue = true;
+
+    public String cookieKey;
+
+    public boolean encryptedCookieValue = true;
+
+    protected Map<String, String> cookieNameMap;
+
+    private final List<String> defaultRoleList = new ArrayList<>();
+
+    @PostConstruct
+    public void init() {
+        StreamUtil.of(ComponentUtil.getFessConfig().getSearchDefaultRoles().split(",")).filter(name -> StringUtil.isNotBlank(name))
+                .forEach(name -> {
+                    defaultRoleList.add(name);
+                });
+    }
+
+    public Set<String> build() {
+        final Set<String> roleList = new HashSet<>();
+        final HttpServletRequest request = LaRequestUtil.getOptionalRequest().orElse(null);
+
+        // request parameter
+        if (request != null && StringUtil.isNotBlank(parameterKey)) {
+            roleList.addAll(buildByParameter(request));
+        }
+
+        // request header
+        if (request != null && StringUtil.isNotBlank(headerKey)) {
+            roleList.addAll(buildByHeader(request));
+        }
+
+        // cookie
+        if (request != null && StringUtil.isNotBlank(cookieKey)) {
+            roleList.addAll(buildByCookie(request));
+        }
+
+        // cookie mapping
+        if (cookieNameMap != null) {
+            roleList.addAll(buildByCookieNameMapping(request));
+        }
+
+        final RequestManager requestManager = ComponentUtil.getRequestManager();
+        requestManager.findUserBean(FessUserBean.class).ifPresent(
+                fessUserBean -> StreamUtil.of(fessUserBean.getRoles()).forEach(roleList::add));
+
+        if (defaultRoleList != null) {
+            roleList.addAll(defaultRoleList);
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("roleList: " + roleList);
+        }
+
+        return roleList;
+    }
+
+    protected Set<String> buildByParameter(final HttpServletRequest request) {
+
+        final String parameter = request.getParameter(parameterKey);
+        if (logger.isDebugEnabled()) {
+            logger.debug(parameterKey + ":" + parameter);
+        }
+        if (StringUtil.isNotEmpty(parameter)) {
+            return decodedRoleList(parameter, encryptedParameterValue);
+        }
+
+        return Collections.emptySet();
+    }
+
+    protected Set<String> buildByHeader(final HttpServletRequest request) {
+
+        final String parameter = request.getHeader(headerKey);
+        if (logger.isDebugEnabled()) {
+            logger.debug(headerKey + ":" + parameter);
+        }
+        if (StringUtil.isNotEmpty(parameter)) {
+            return decodedRoleList(parameter, encryptedHeaderValue);
+        }
+
+        return Collections.emptySet();
+
+    }
+
+    protected Set<String> buildByCookie(final HttpServletRequest request) {
+
+        final Cookie[] cookies = request.getCookies();
+        if (cookies != null) {
+            for (final Cookie cookie : cookies) {
+                if (cookieKey.equals(cookie.getName())) {
+                    final String value = cookie.getValue();
+                    if (logger.isDebugEnabled()) {
+                        logger.debug(cookieKey + ":" + value);
+                    }
+                    if (StringUtil.isNotEmpty(value)) {
+                        return decodedRoleList(value, encryptedCookieValue);
+                    }
+                }
+            }
+        }
+
+        return Collections.emptySet();
+    }
+
+    protected Set<String> buildByCookieNameMapping(final HttpServletRequest request) {
+
+        final Set<String> roleNameSet = new HashSet<>();
+        final Cookie[] cookies = request.getCookies();
+        if (cookies != null) {
+            for (final Cookie cookie : cookies) {
+                addRoleFromCookieMapping(roleNameSet, cookie);
+            }
+        }
+
+        return roleNameSet;
+    }
+
+    protected void addRoleFromCookieMapping(final Set<String> roleNameList, final Cookie cookie) {
+        final String roleName = cookieNameMap.get(cookie.getName());
+        if (StringUtil.isNotBlank(roleName)) {
+            roleNameList.add(roleName);
+        }
+    }
+
+    protected Set<String> decodedRoleList(final String value, final boolean encrypted) {
+        String rolesStr = value;
+        if (encrypted && cipher != null) {
+            rolesStr = cipher.decryptoText(rolesStr);
+        }
+
+        final Set<String> roleSet = new HashSet<>();
+        if (valueSeparator.length() > 0) {
+            final String[] values = rolesStr.split(valueSeparator);
+            if (values.length > 1) {
+                final String[] roles = values[1].split(roleSeparator);
+                for (final String role : roles) {
+                    if (StringUtil.isNotEmpty(role)) {
+                        roleSet.add(role);
+                    }
+                }
+            }
+        } else {
+            final String[] roles = rolesStr.split(roleSeparator);
+            for (final String role : roles) {
+                if (StringUtil.isNotEmpty(role)) {
+                    roleSet.add(role);
+                }
+            }
+        }
+        return roleSet;
+    }
+
+    public void addCookieNameMapping(final String cookieName, final String roleName) {
+        if (cookieNameMap == null) {
+            cookieNameMap = new HashMap<String, String>();
+        }
+        cookieNameMap.put(cookieName, roleName);
+    }
+
+}

+ 2 - 10
src/main/java/org/codelibs/fess/helper/SambaHelper.java

@@ -51,20 +51,12 @@ public class SambaHelper {
 
     public String getAccountId(final SID sid) {
         if (fessConfig.isAvailableSmbSidType(sid.getType())) {
-            return convert(sid.getType(), sid.getAccountName());
+            return createSearchRole(sid.getType(), sid.getAccountName());
         }
         return null;
     }
 
-    public String getRoleByUser(final String name) {
-        return convert(SID_TYPE_USER, name);
-    }
-
-    public String getRoleByDomainGroup(final String name) {
-        return convert(SID_TYPE_DOM_GRP, name);
-    }
-
-    protected String convert(final int type, final String name) {
+    protected String createSearchRole(final int type, final String name) {
         return type + name;
     }
 }

+ 23 - 7
src/main/java/org/codelibs/fess/helper/SystemHelper.java

@@ -110,7 +110,7 @@ public class SystemHelper implements Serializable {
         shutdownHookList.forEach(action -> {
             try {
                 action.run();
-            } catch (Exception e) {
+            } catch (final Exception e) {
                 logger.warn("Failed to process shutdown task.", e);
             }
         });
@@ -242,20 +242,20 @@ public class SystemHelper implements Serializable {
         }
     }
 
-    public void sleep(int sec) {
+    public void sleep(final int sec) {
         try {
             Thread.sleep(sec * 1000L);
-        } catch (InterruptedException e) {
+        } catch (final InterruptedException e) {
             // ignore
         }
     }
 
-    public void addShutdownHook(Runnable hook) {
+    public void addShutdownHook(final Runnable hook) {
         shutdownHookList.add(hook);
     }
 
     public String getHostname() {
-        Map<String, String> env = System.getenv();
+        final Map<String, String> env = System.getenv();
         if (env.containsKey("COMPUTERNAME")) {
             return env.get("COMPUTERNAME");
         } else if (env.containsKey("HOSTNAME")) {
@@ -263,13 +263,29 @@ public class SystemHelper implements Serializable {
         }
         try {
             return InetAddress.getLocalHost().getHostAddress();
-        } catch (UnknownHostException e) {
+        } catch (final UnknownHostException e) {
             logger.debug("Unknown hostname.", e);
         }
         return "Unknown";
     }
 
-    public void setupAdminHtmlData(TypicalAction action, ActionRuntime runtime) {
+    public void setupAdminHtmlData(final TypicalAction action, final ActionRuntime runtime) {
         // nothing
     }
+
+    public String getSearchRoleByUser(final String name) {
+        return createSearchRole(ComponentUtil.getFessConfig().getLdapRoleSearchUserPrefix(), name);
+    }
+
+    public String getSearchRoleByGroup(final String name) {
+        return createSearchRole(ComponentUtil.getFessConfig().getLdapRoleSearchGroupPrefix(), name);
+    }
+
+    public String getSearchRoleByRole(final String name) {
+        return createSearchRole(ComponentUtil.getFessConfig().getLdapRoleSearchRolePrefix(), name);
+    }
+
+    protected String createSearchRole(final String type, final String name) {
+        return type + name;
+    }
 }

+ 127 - 4
src/main/java/org/codelibs/fess/helper/UserInfoHelper.java

@@ -15,15 +15,138 @@
  */
 package org.codelibs.fess.helper;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
-public interface UserInfoHelper {
+import javax.annotation.Resource;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
 
-    String getUserCode();
+import org.codelibs.core.collection.LruHashMap;
+import org.codelibs.core.lang.StringUtil;
+import org.codelibs.fess.Constants;
+import org.codelibs.fess.mylasta.direction.FessConfig;
+import org.codelibs.fess.util.ComponentUtil;
+import org.lastaflute.web.util.LaRequestUtil;
+import org.lastaflute.web.util.LaResponseUtil;
 
-    void storeQueryId(String query, List<Map<String, Object>> documentItems);
+public class UserInfoHelper {
+    @Resource
+    protected SearchLogHelper searchLogHelper;
 
-    String[] getResultDocIds(String queryId);
+    public int resultDocIdsCacheSize = 20;
 
+    public String cookieName = "fsid";
+
+    public String cookieDomain;
+
+    public int cookieMaxAge = 30 * 24 * 60 * 60;// 1 month
+
+    public String cookiePath;
+
+    public Boolean cookieSecure;
+
+    public String getUserCode() {
+        final HttpServletRequest request = LaRequestUtil.getRequest();
+
+        String userCode = (String) request.getAttribute(Constants.USER_CODE);
+
+        if (StringUtil.isBlank(userCode)) {
+            userCode = getUserCodeFromCookie(request);
+        }
+
+        if (!request.isRequestedSessionIdValid()) {
+            return null;
+        }
+
+        if (StringUtil.isBlank(userCode)) {
+            userCode = getId();
+        }
+
+        if (StringUtil.isNotBlank(userCode)) {
+            updateUserSessionId(userCode);
+        }
+        return userCode;
+    }
+
+    protected String getId() {
+        return UUID.randomUUID().toString().replace("-", StringUtil.EMPTY);
+    }
+
+    protected void updateUserSessionId(final String userCode) {
+        searchLogHelper.updateUserInfo(userCode);
+
+        final HttpServletRequest request = LaRequestUtil.getRequest();
+        request.setAttribute(Constants.USER_CODE, userCode);
+
+        final Cookie cookie = new Cookie(cookieName, userCode);
+        cookie.setMaxAge(cookieMaxAge);
+        if (StringUtil.isNotBlank(cookieDomain)) {
+            cookie.setDomain(cookieDomain);
+        }
+        if (StringUtil.isNotBlank(cookiePath)) {
+            cookie.setPath(cookiePath);
+        }
+        if (cookieSecure != null) {
+            cookie.setSecure(cookieSecure);
+        }
+        LaResponseUtil.getResponse().addCookie(cookie);
+    }
+
+    protected String getUserCodeFromCookie(final HttpServletRequest request) {
+        final Cookie[] cookies = request.getCookies();
+        if (cookies != null) {
+            for (final Cookie cookie : cookies) {
+                if (cookieName.equals(cookie.getName())) {
+                    return cookie.getValue();
+                }
+            }
+        }
+        return null;
+    }
+
+    public void storeQueryId(final String queryId, final List<Map<String, Object>> documentItems) {
+        final HttpSession session = LaRequestUtil.getRequest().getSession(false);
+        if (session != null) {
+            final FessConfig fessConfig = ComponentUtil.getFessConfig();
+
+            final List<String> docIdList = new ArrayList<String>();
+            for (final Map<String, Object> map : documentItems) {
+                final Object docId = map.get(fessConfig.getIndexFieldDocId());
+                if (docId != null && docId.toString().length() > 0) {
+                    docIdList.add(docId.toString());
+                }
+            }
+
+            if (!docIdList.isEmpty()) {
+                final Map<String, String[]> resultDocIdsCache = getResultDocIdsCache(session);
+                resultDocIdsCache.put(queryId, docIdList.toArray(new String[docIdList.size()]));
+            }
+        }
+    }
+
+    public String[] getResultDocIds(final String queryId) {
+        final HttpSession session = LaRequestUtil.getRequest().getSession(false);
+        if (session != null) {
+            final Map<String, String[]> resultUrlCache = getResultDocIdsCache(session);
+            final String[] urls = resultUrlCache.get(queryId);
+            if (urls != null) {
+                return urls;
+            }
+        }
+        return StringUtil.EMPTY_STRINGS;
+    }
+
+    private Map<String, String[]> getResultDocIdsCache(final HttpSession session) {
+        @SuppressWarnings("unchecked")
+        Map<String, String[]> resultDocIdsCache = (Map<String, String[]>) session.getAttribute(Constants.RESULT_DOC_ID_CACHE);
+        if (resultDocIdsCache == null) {
+            resultDocIdsCache = new LruHashMap<String, String[]>(resultDocIdsCacheSize);
+            session.setAttribute(Constants.RESULT_DOC_ID_CACHE, resultDocIdsCache);
+        }
+        return resultDocIdsCache;
+    }
 }

+ 0 - 161
src/main/java/org/codelibs/fess/helper/impl/CookieUserInfoHelperImpl.java

@@ -1,161 +0,0 @@
-/*
- * Copyright 2012-2016 CodeLibs Project and the Others.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language
- * governing permissions and limitations under the License.
- */
-package org.codelibs.fess.helper.impl;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.annotation.Resource;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
-
-import org.codelibs.core.collection.LruHashMap;
-import org.codelibs.core.lang.StringUtil;
-import org.codelibs.fess.Constants;
-import org.codelibs.fess.helper.SearchLogHelper;
-import org.codelibs.fess.helper.UserInfoHelper;
-import org.codelibs.fess.mylasta.direction.FessConfig;
-import org.codelibs.fess.util.ComponentUtil;
-import org.lastaflute.web.util.LaRequestUtil;
-import org.lastaflute.web.util.LaResponseUtil;
-
-public class CookieUserInfoHelperImpl implements UserInfoHelper {
-
-    @Resource
-    protected SearchLogHelper searchLogHelper;
-
-    public int resultDocIdsCacheSize = 20;
-
-    public String cookieName = "fsid";
-
-    public String cookieDomain;
-
-    public int cookieMaxAge = 30 * 24 * 60 * 60;// 1 month
-
-    public String cookiePath;
-
-    public Boolean cookieSecure;
-
-    /* (non-Javadoc)
-     * @see org.codelibs.fess.helper.impl.UserInfoHelper#getUserCode()
-     */
-    @Override
-    public String getUserCode() {
-        final HttpServletRequest request = LaRequestUtil.getRequest();
-
-        String userCode = (String) request.getAttribute(Constants.USER_CODE);
-
-        if (StringUtil.isBlank(userCode)) {
-            userCode = getUserCodeFromCookie(request);
-        }
-
-        if (!request.isRequestedSessionIdValid()) {
-            return null;
-        }
-
-        if (StringUtil.isBlank(userCode)) {
-            userCode = getId();
-        }
-
-        if (StringUtil.isNotBlank(userCode)) {
-            updateUserSessionId(userCode);
-        }
-        return userCode;
-    }
-
-    protected String getId() {
-        return UUID.randomUUID().toString().replace("-", StringUtil.EMPTY);
-    }
-
-    protected void updateUserSessionId(final String userCode) {
-        searchLogHelper.updateUserInfo(userCode);
-
-        final HttpServletRequest request = LaRequestUtil.getRequest();
-        request.setAttribute(Constants.USER_CODE, userCode);
-
-        final Cookie cookie = new Cookie(cookieName, userCode);
-        cookie.setMaxAge(cookieMaxAge);
-        if (StringUtil.isNotBlank(cookieDomain)) {
-            cookie.setDomain(cookieDomain);
-        }
-        if (StringUtil.isNotBlank(cookiePath)) {
-            cookie.setPath(cookiePath);
-        }
-        if (cookieSecure != null) {
-            cookie.setSecure(cookieSecure);
-        }
-        LaResponseUtil.getResponse().addCookie(cookie);
-    }
-
-    protected String getUserCodeFromCookie(final HttpServletRequest request) {
-        final Cookie[] cookies = request.getCookies();
-        if (cookies != null) {
-            for (final Cookie cookie : cookies) {
-                if (cookieName.equals(cookie.getName())) {
-                    return cookie.getValue();
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void storeQueryId(final String queryId, final List<Map<String, Object>> documentItems) {
-        final HttpSession session = LaRequestUtil.getRequest().getSession(false);
-        if (session != null) {
-            final FessConfig fessConfig = ComponentUtil.getFessConfig();
-
-            final List<String> docIdList = new ArrayList<String>();
-            for (final Map<String, Object> map : documentItems) {
-                final Object docId = map.get(fessConfig.getIndexFieldDocId());
-                if (docId != null && docId.toString().length() > 0) {
-                    docIdList.add(docId.toString());
-                }
-            }
-
-            if (!docIdList.isEmpty()) {
-                final Map<String, String[]> resultDocIdsCache = getResultDocIdsCache(session);
-                resultDocIdsCache.put(queryId, docIdList.toArray(new String[docIdList.size()]));
-            }
-        }
-    }
-
-    @Override
-    public String[] getResultDocIds(final String queryId) {
-        final HttpSession session = LaRequestUtil.getRequest().getSession(false);
-        if (session != null) {
-            final Map<String, String[]> resultUrlCache = getResultDocIdsCache(session);
-            final String[] urls = resultUrlCache.get(queryId);
-            if (urls != null) {
-                return urls;
-            }
-        }
-        return StringUtil.EMPTY_STRINGS;
-    }
-
-    private Map<String, String[]> getResultDocIdsCache(final HttpSession session) {
-        @SuppressWarnings("unchecked")
-        Map<String, String[]> resultDocIdsCache = (Map<String, String[]>) session.getAttribute(Constants.RESULT_DOC_ID_CACHE);
-        if (resultDocIdsCache == null) {
-            resultDocIdsCache = new LruHashMap<String, String[]>(resultDocIdsCacheSize);
-            session.setAttribute(Constants.RESULT_DOC_ID_CACHE, resultDocIdsCache);
-        }
-        return resultDocIdsCache;
-    }
-}

+ 0 - 231
src/main/java/org/codelibs/fess/helper/impl/RoleQueryHelperImpl.java

@@ -1,231 +0,0 @@
-/*
- * Copyright 2012-2016 CodeLibs Project and the Others.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language
- * governing permissions and limitations under the License.
- */
-package org.codelibs.fess.helper.impl;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.annotation.PostConstruct;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-
-import org.codelibs.core.crypto.CachedCipher;
-import org.codelibs.core.lang.StringUtil;
-import org.codelibs.fess.helper.RoleQueryHelper;
-import org.codelibs.fess.mylasta.action.FessUserBean;
-import org.codelibs.fess.util.ComponentUtil;
-import org.codelibs.fess.util.StreamUtil;
-import org.lastaflute.web.servlet.request.RequestManager;
-import org.lastaflute.web.util.LaRequestUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This class returns a list of a role from a request parameter,
- * a request header and a cookie. The format of the default value
- * is "[\d]+\nrole1,role2,role3", which you can encrypt.
- *
- * @author shinsuke
- *
- */
-public class RoleQueryHelperImpl implements RoleQueryHelper, Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    private static final Logger logger = LoggerFactory.getLogger(RoleQueryHelperImpl.class);
-
-    public CachedCipher cipher;
-
-    public String valueSeparator = "\n";
-
-    public String roleSeparator = ",";
-
-    public String parameterKey;
-
-    public boolean encryptedParameterValue = true;
-
-    public String headerKey;
-
-    public boolean encryptedHeaderValue = true;
-
-    public String cookieKey;
-
-    public boolean encryptedCookieValue = true;
-
-    protected Map<String, String> cookieNameMap;
-
-    private final List<String> defaultRoleList = new ArrayList<>();
-
-    @PostConstruct
-    public void init() {
-        StreamUtil.of(ComponentUtil.getFessConfig().getSearchDefaultRoles().split(",")).filter(name -> StringUtil.isNotBlank(name))
-                .forEach(name -> {
-                    defaultRoleList.add(name);
-                });
-    }
-
-    /* (non-Javadoc)
-     * @see org.codelibs.fess.helper.impl.RoleQueryHelper#build()
-     */
-    @Override
-    public Set<String> build() {
-        final Set<String> roleList = new HashSet<>();
-        final HttpServletRequest request = LaRequestUtil.getOptionalRequest().orElse(null);
-
-        // request parameter
-        if (request != null && StringUtil.isNotBlank(parameterKey)) {
-            roleList.addAll(buildByParameter(request));
-        }
-
-        // request header
-        if (request != null && StringUtil.isNotBlank(headerKey)) {
-            roleList.addAll(buildByHeader(request));
-        }
-
-        // cookie
-        if (request != null && StringUtil.isNotBlank(cookieKey)) {
-            roleList.addAll(buildByCookie(request));
-        }
-
-        // cookie mapping
-        if (cookieNameMap != null) {
-            roleList.addAll(buildByCookieNameMapping(request));
-        }
-
-        final RequestManager requestManager = ComponentUtil.getRequestManager();
-        requestManager.findUserBean(FessUserBean.class).ifPresent(
-                fessUserBean -> StreamUtil.of(fessUserBean.getRoles()).forEach(roleList::add));
-
-        if (defaultRoleList != null) {
-            roleList.addAll(defaultRoleList);
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("roleList: " + roleList);
-        }
-
-        return roleList;
-    }
-
-    protected Set<String> buildByParameter(final HttpServletRequest request) {
-
-        final String parameter = request.getParameter(parameterKey);
-        if (logger.isDebugEnabled()) {
-            logger.debug(parameterKey + ":" + parameter);
-        }
-        if (StringUtil.isNotEmpty(parameter)) {
-            return decodedRoleList(parameter, encryptedParameterValue);
-        }
-
-        return Collections.emptySet();
-    }
-
-    protected Set<String> buildByHeader(final HttpServletRequest request) {
-
-        final String parameter = request.getHeader(headerKey);
-        if (logger.isDebugEnabled()) {
-            logger.debug(headerKey + ":" + parameter);
-        }
-        if (StringUtil.isNotEmpty(parameter)) {
-            return decodedRoleList(parameter, encryptedHeaderValue);
-        }
-
-        return Collections.emptySet();
-
-    }
-
-    protected Set<String> buildByCookie(final HttpServletRequest request) {
-
-        final Cookie[] cookies = request.getCookies();
-        if (cookies != null) {
-            for (final Cookie cookie : cookies) {
-                if (cookieKey.equals(cookie.getName())) {
-                    final String value = cookie.getValue();
-                    if (logger.isDebugEnabled()) {
-                        logger.debug(cookieKey + ":" + value);
-                    }
-                    if (StringUtil.isNotEmpty(value)) {
-                        return decodedRoleList(value, encryptedCookieValue);
-                    }
-                }
-            }
-        }
-
-        return Collections.emptySet();
-    }
-
-    protected Set<String> buildByCookieNameMapping(final HttpServletRequest request) {
-
-        final Set<String> roleNameSet = new HashSet<>();
-        final Cookie[] cookies = request.getCookies();
-        if (cookies != null) {
-            for (final Cookie cookie : cookies) {
-                addRoleFromCookieMapping(roleNameSet, cookie);
-            }
-        }
-
-        return roleNameSet;
-    }
-
-    protected void addRoleFromCookieMapping(final Set<String> roleNameList, final Cookie cookie) {
-        final String roleName = cookieNameMap.get(cookie.getName());
-        if (StringUtil.isNotBlank(roleName)) {
-            roleNameList.add(roleName);
-        }
-    }
-
-    protected Set<String> decodedRoleList(final String value, final boolean encrypted) {
-        String rolesStr = value;
-        if (encrypted && cipher != null) {
-            rolesStr = cipher.decryptoText(rolesStr);
-        }
-
-        final Set<String> roleSet = new HashSet<>();
-        if (valueSeparator.length() > 0) {
-            final String[] values = rolesStr.split(valueSeparator);
-            if (values.length > 1) {
-                final String[] roles = values[1].split(roleSeparator);
-                for (final String role : roles) {
-                    if (StringUtil.isNotEmpty(role)) {
-                        roleSet.add(role);
-                    }
-                }
-            }
-        } else {
-            final String[] roles = rolesStr.split(roleSeparator);
-            for (final String role : roles) {
-                if (StringUtil.isNotEmpty(role)) {
-                    roleSet.add(role);
-                }
-            }
-        }
-        return roleSet;
-    }
-
-    public void addCookieNameMapping(final String cookieName, final String roleName) {
-        if (cookieNameMap == null) {
-            cookieNameMap = new HashMap<String, String>();
-        }
-        cookieNameMap.put(cookieName, roleName);
-    }
-
-}

+ 1 - 1
src/main/java/org/codelibs/fess/job/CrawlJob.java

@@ -350,7 +350,7 @@ public class CrawlJob {
             propFile = File.createTempFile("crawler_", ".properties");
             cmdList.add(propFile.getAbsolutePath());
             try (FileOutputStream out = new FileOutputStream(propFile)) {
-                Properties prop = new Properties();
+                final Properties prop = new Properties();
                 prop.putAll(ComponentUtil.getSystemProperties());
                 prop.store(out, cmdList.toString());
             }

+ 1 - 1
src/main/java/org/codelibs/fess/job/SuggestJob.java

@@ -222,7 +222,7 @@ public class SuggestJob {
             propFile = File.createTempFile("crawler_", ".properties");
             cmdList.add(propFile.getAbsolutePath());
             try (FileOutputStream out = new FileOutputStream(propFile)) {
-                Properties prop = new Properties();
+                final Properties prop = new Properties();
                 prop.putAll(ComponentUtil.getSystemProperties());
                 prop.store(out, cmdList.toString());
             }

+ 478 - 69
src/main/java/org/codelibs/fess/ldap/LdapManager.java

@@ -18,22 +18,34 @@ package org.codelibs.fess.ldap;
 import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Locale;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import javax.naming.Context;
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
 import javax.naming.directory.Attribute;
 import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
 import javax.naming.directory.DirContext;
 import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.ModificationItem;
 import javax.naming.directory.SearchControls;
 import javax.naming.directory.SearchResult;
 
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.fess.entity.FessUser;
-import org.codelibs.fess.helper.SambaHelper;
+import org.codelibs.fess.es.user.exentity.Group;
+import org.codelibs.fess.es.user.exentity.Role;
+import org.codelibs.fess.es.user.exentity.User;
+import org.codelibs.fess.exception.LdapOperationException;
+import org.codelibs.fess.helper.SystemHelper;
 import org.codelibs.fess.mylasta.direction.FessConfig;
 import org.codelibs.fess.util.ComponentUtil;
+import org.codelibs.fess.util.StreamUtil;
 import org.dbflute.optional.OptionalEntity;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -41,37 +53,48 @@ import org.slf4j.LoggerFactory;
 public class LdapManager {
     private static final Logger logger = LoggerFactory.getLogger(LdapManager.class);
 
+    protected ThreadLocal<DirContextHolder> contextLocal = new ThreadLocal<>();
+
+    protected Hashtable<String, String> createEnvironment(final String initialContextFactory, final String securityAuthentication,
+            final String providerUrl, final String principal, final String credntials) {
+        final Hashtable<String, String> env = new Hashtable<>();
+        env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
+        env.put(Context.SECURITY_AUTHENTICATION, securityAuthentication);
+        env.put(Context.PROVIDER_URL, providerUrl);
+        env.put(Context.SECURITY_PRINCIPAL, principal);
+        env.put(Context.SECURITY_CREDENTIALS, credntials);
+        return env;
+    }
+
+    protected Hashtable<String, String> createAdminEnv() {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        return createEnvironment(fessConfig.getLdapAdminInitialContextFactory(), fessConfig.getLdapAdminSecurityAuthentication(),
+                fessConfig.getLdapAdminProviderUrl(), fessConfig.getLdapAdminSecurityPrincipal(),
+                fessConfig.getLdapAdminSecurityCredentials());
+    }
+
+    protected Hashtable<String, String> createSearchEnv(final String username, final String password) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        return createEnvironment(fessConfig.getLdapInitialContextFactory(), fessConfig.getLdapSecurityAuthentication(),
+                fessConfig.getLdapProviderUrl(), fessConfig.getLdapSecurityPrincipal(username), password);
+    }
+
     public OptionalEntity<FessUser> login(final String username, final String password) {
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
-        final String providerUrl = fessConfig.getLdapProviderUrl();
 
-        if (StringUtil.isBlank(providerUrl)) {
+        if (StringUtil.isBlank(fessConfig.getLdapProviderUrl())) {
             return OptionalEntity.empty();
         }
 
-        DirContext ctx = null;
-        try {
-            final Hashtable<String, String> env = new Hashtable<>();
-            env.put(Context.INITIAL_CONTEXT_FACTORY, fessConfig.getLdapInitialContextFactory());
-            env.put(Context.SECURITY_AUTHENTICATION, fessConfig.getLdapSecurityAuthentication());
-            env.put(Context.PROVIDER_URL, providerUrl);
-            env.put(Context.SECURITY_PRINCIPAL, fessConfig.getLdapSecurityPrincipal(username));
-            env.put(Context.SECURITY_CREDENTIALS, password);
-            ctx = new InitialDirContext(env);
+        final Hashtable<String, String> env = createSearchEnv(username, password);
+        try (DirContextHolder holder = getDirContext(() -> env)) {
+            final DirContext context = holder.get();
             if (logger.isDebugEnabled()) {
-                logger.debug("Logged in.", ctx);
+                logger.debug("Logged in.", context);
             }
             return OptionalEntity.of(createLdapUser(username, env));
-        } catch (final NamingException e) {
+        } catch (final Exception e) {
             logger.debug("Login failed.", e);
-        } finally {
-            if (ctx != null) {
-                try {
-                    ctx.close();
-                } catch (final NamingException e) {
-                    // ignore
-                }
-            }
         }
         return OptionalEntity.empty();
     }
@@ -81,72 +104,458 @@ public class LdapManager {
     }
 
     public String[] getRoles(final LdapUser ldapUser, final String bindDn, final String accountFilter) {
-        final SambaHelper sambaHelper = ComponentUtil.getSambaHelper();
+        final SystemHelper systemHelper = ComponentUtil.getSystemHelper();
         final FessConfig fessConfig = ComponentUtil.getFessConfig();
         final List<String> roleList = new ArrayList<String>();
 
-        if (fessConfig.isSmbRoleAsUser()) {
-            roleList.add(sambaHelper.getRoleByUser(ldapUser.getName()));
+        if (fessConfig.isLdapRoleSearchUserEnabled()) {
+            roleList.add(systemHelper.getSearchRoleByUser(ldapUser.getName()));
         }
 
-        DirContext ctx = null;
-        try {
-            ctx = new InitialDirContext(ldapUser.getEnvironment());
+        // LDAP: cn=%s
+        // AD: (&(objectClass=user)(sAMAccountName=%s))
+        final String filter = String.format(accountFilter, ldapUser.getName());
+        search(bindDn, filter, new String[] { fessConfig.getLdapMemberofAttribute() },
+                () -> createSearchEnv(ldapUser.getName(), ldapUser.getPassword()), result -> {
+                    processSearchRoles(result, (entryDn, name) -> {
+                        final boolean isRole = entryDn.toLowerCase(Locale.ROOT).indexOf("ou=role") != -1;
+                        if (isRole) {
+                            if (fessConfig.isLdapRoleSearchRoleEnabled()) {
+                                roleList.add(systemHelper.getSearchRoleByRole(name));
+                            }
+                        } else if (fessConfig.isLdapRoleSearchGroupEnabled()) {
+                            roleList.add(systemHelper.getSearchRoleByGroup(name));
+                        }
+                    });
+                });
 
-            // LDAP: cn=%s
-            // AD: (&(objectClass=user)(sAMAccountName=%s))
-            final String filter = String.format(accountFilter, ldapUser.getName());
-            final SearchControls controls = new SearchControls();
-            controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+        return roleList.toArray(new String[roleList.size()]);
+    }
 
-            //search
-            final NamingEnumeration<SearchResult> rslt = ctx.search(bindDn, filter, controls);
-            while (rslt.hasMoreElements()) {
-                final SearchResult srcrslt = rslt.next();
-                final Attributes attrs = srcrslt.getAttributes();
+    protected void processSearchRoles(final NamingEnumeration<SearchResult> result, final BiConsumer<String, String> consumer)
+            throws NamingException {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        while (result.hasMoreElements()) {
+            final SearchResult srcrslt = result.next();
+            final Attributes attrs = srcrslt.getAttributes();
 
-                //get group attr
-                final Attribute attr = attrs.get("memberOf");
-                if (attr == null) {
-                    continue;
-                }
+            //get group attr
+            final Attribute attr = attrs.get(fessConfig.getLdapMemberofAttribute());
+            if (attr == null) {
+                continue;
+            }
 
-                for (int i = 0; i < attr.size(); i++) {
-                    final Object attrValue = attr.get(i);
-                    if (attrValue != null) {
-                        // TODO replace with regexp
-                        String strTmp = attrValue.toString();
+            for (int i = 0; i < attr.size(); i++) {
+                final Object attrValue = attr.get(i);
+                if (attrValue != null) {
+                    final String entryDn = attrValue.toString();
+
+                    int start = 0;
+                    int end = 0;
+
+                    start = entryDn.indexOf("CN=");
+                    if (start < 0) {
+                        start = entryDn.indexOf("cn=");
+                    }
+                    if (start == -1) {
+                        continue;
+                    }
+                    start += 3;
+                    end = entryDn.indexOf(',');
 
-                        int strStart = 0;
-                        int strEnd = 0;
+                    String name;
+                    if (end == -1) {
+                        name = entryDn.substring(start);
+                    } else {
+                        name = entryDn.substring(start, end);
+                    }
 
-                        strStart = strTmp.indexOf("CN=");
-                        strStart += "CN=".length();
-                        strEnd = strTmp.indexOf(',');
+                    consumer.accept(entryDn, name);
+                }
+            }
+        }
+    }
 
-                        strTmp = strTmp.substring(strStart, strEnd);
+    public void insert(final User user) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (!fessConfig.isLdapAdminEnabled()) {
+            return;
+        }
 
-                        if (fessConfig.isSmbRoleAsGroup()) {
-                            roleList.add(sambaHelper.getRoleByDomainGroup(strTmp));
-                        } else {
-                            roleList.add(strTmp);
+        final Supplier<Hashtable<String, String>> adminEnv = () -> createAdminEnv();
+        final String userDN = fessConfig.getLdapAdminUserSecurityPrincipal(user.getName());
+        search(fessConfig.getLdapAdminUserBaseDn(),
+                fessConfig.getLdapAdminUserFilter(user.getName()),
+                new String[] { fessConfig.getLdapMemberofAttribute() },
+                adminEnv,
+                result -> {
+                    if (result.hasMore()) {
+                        if (user.getOriginalPassword() != null) {
+                            final List<ModificationItem> modifyList = new ArrayList<>();
+                            modifyReplaceEntry(modifyList, "userPassword", user.getOriginalPassword());
+                            modify(userDN, modifyList, adminEnv);
                         }
+
+                        final List<String> oldGroupList = new ArrayList<>();
+                        final List<String> oldRoleList = new ArrayList<>();
+                        final String lowerGroupDn = fessConfig.getLdapAdminGroupBaseDn().toLowerCase(Locale.ROOT);
+                        final String lowerRoleDn = fessConfig.getLdapAdminRoleBaseDn().toLowerCase(Locale.ROOT);
+                        processSearchRoles(result, (entryDn, name) -> {
+                            final String lowerEntryDn = entryDn.toLowerCase(Locale.ROOT);
+                            if (lowerEntryDn.indexOf(lowerGroupDn) != -1) {
+                                oldGroupList.add(name);
+                            } else if (lowerEntryDn.indexOf(lowerRoleDn) != -1) {
+                                oldRoleList.add(name);
+                            }
+                        });
+
+                        final List<String> newGroupList = StreamUtil.of(user.getGroupNames()).collect(Collectors.toList());
+                        StreamUtil.of(user.getGroupNames()).forEach(name -> {
+                            if (oldGroupList.contains(name)) {
+                                oldGroupList.remove(name);
+                                newGroupList.remove(name);
+                            }
+                        });
+                        oldGroupList.stream().forEach(
+                                name -> {
+                                    search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv,
+                                            subResult -> {
+                                                if (subResult.hasMore()) {
+                                                    final List<ModificationItem> modifyList = new ArrayList<>();
+                                                    modifyDeleteEntry(modifyList, "member", userDN);
+                                                    modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
+                                                }
+                                            });
+                                });
+                        newGroupList.stream().forEach(
+                                name -> {
+                                    search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv,
+                                            subResult -> {
+                                                if (!subResult.hasMore()) {
+                                                    final Group group = new Group();
+                                                    group.setName(name);
+                                                    insert(group);
+                                                }
+                                                final List<ModificationItem> modifyList = new ArrayList<>();
+                                                modifyAddEntry(modifyList, "member", userDN);
+                                                modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
+                                            });
+                                });
+
+                        final List<String> newRoleList = StreamUtil.of(user.getRoleNames()).collect(Collectors.toList());
+                        StreamUtil.of(user.getRoleNames()).forEach(name -> {
+                            if (oldRoleList.contains(name)) {
+                                oldRoleList.remove(name);
+                                newRoleList.remove(name);
+                            }
+                        });
+                        oldRoleList.stream().forEach(
+                                name -> {
+                                    search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv,
+                                            subResult -> {
+                                                if (subResult.hasMore()) {
+                                                    final List<ModificationItem> modifyList = new ArrayList<>();
+                                                    modifyDeleteEntry(modifyList, "member", userDN);
+                                                    modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
+                                                }
+                                            });
+                                });
+                        newRoleList.stream().forEach(
+                                name -> {
+                                    search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv,
+                                            subResult -> {
+                                                if (!subResult.hasMore()) {
+                                                    final Role role = new Role();
+                                                    role.setName(name);
+                                                    insert(role);
+                                                }
+                                                final List<ModificationItem> modifyList = new ArrayList<>();
+                                                modifyAddEntry(modifyList, "member", userDN);
+                                                modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
+                                            });
+                                });
+                    } else {
+                        final BasicAttributes entry = new BasicAttributes();
+                        addUserAttributes(entry, user, fessConfig);
+                        final Attribute oc = fessConfig.getLdapAdminUserObjectClassAttribute();
+                        entry.put(oc);
+                        insert(userDN, entry, adminEnv);
+
+                        StreamUtil.of(user.getGroupNames()).forEach(
+                                name -> {
+                                    search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(name), null, adminEnv,
+                                            subResult -> {
+                                                if (!subResult.hasMore()) {
+                                                    final Group group = new Group();
+                                                    group.setName(name);
+                                                    insert(group);
+                                                }
+                                                final List<ModificationItem> modifyList = new ArrayList<>();
+                                                modifyAddEntry(modifyList, "member", userDN);
+                                                modify(fessConfig.getLdapAdminGroupSecurityPrincipal(name), modifyList, adminEnv);
+                                            });
+                                });
+
+                        StreamUtil.of(user.getRoleNames()).forEach(
+                                name -> {
+                                    search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(name), null, adminEnv,
+                                            subResult -> {
+                                                if (!subResult.hasMore()) {
+                                                    final Role role = new Role();
+                                                    role.setName(name);
+                                                    insert(role);
+                                                }
+                                                final List<ModificationItem> modifyList = new ArrayList<>();
+                                                modifyAddEntry(modifyList, "member", userDN);
+                                                modify(fessConfig.getLdapAdminRoleSecurityPrincipal(name), modifyList, adminEnv);
+                                            });
+                                });
                     }
-                }
+                });
+
+    }
+
+    protected void addUserAttributes(final BasicAttributes entry, final User user, final FessConfig fessConfig) {
+        entry.put(new BasicAttribute("cn", user.getName()));
+        entry.put(new BasicAttribute("sn", user.getName()));
+        entry.put(new BasicAttribute("userPassword", user.getOriginalPassword()));
+    }
+
+    public void delete(final User user) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (!fessConfig.isLdapAdminEnabled()) {
+            return;
+        }
+
+        final Supplier<Hashtable<String, String>> adminEnv = () -> createAdminEnv();
+        search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(user.getName()), null, adminEnv, result -> {
+            if (result.hasMore()) {
+                delete(fessConfig.getLdapAdminUserSecurityPrincipal(user.getName()), adminEnv);
+            } else {
+                logger.info("{} does not exist in LDAP server.", user.getName());
             }
+        });
 
-        } catch (final Exception e) {
-            logger.warn("Failed to resolve roles: " + ldapUser.getName(), e);
-        } finally {
-            if (ctx != null) {
-                try {
-                    ctx.close();
-                } catch (final NamingException e) {
-                    // ignored
-                }
+    }
+
+    public void insert(final Role role) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (!fessConfig.isLdapAdminEnabled()) {
+            return;
+        }
+
+        final Supplier<Hashtable<String, String>> adminEnv = () -> createAdminEnv();
+        search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(role.getName()), null, adminEnv, result -> {
+            if (result.hasMore()) {
+                logger.info("{} exists in LDAP server.", role.getName());
+            } else {
+                final String entryDN = fessConfig.getLdapAdminRoleSecurityPrincipal(role.getName());
+                final BasicAttributes entry = new BasicAttributes();
+                addRoleAttributes(entry, role, fessConfig);
+                final Attribute oc = fessConfig.getLdapAdminRoleObjectClassAttribute();
+                entry.put(oc);
+                insert(entryDN, entry, adminEnv);
             }
+        });
+
+    }
+
+    protected void addRoleAttributes(final BasicAttributes entry, final Role user, final FessConfig fessConfig) {
+        // nothing
+    }
+
+    public void delete(final Role role) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (!fessConfig.isLdapAdminEnabled()) {
+            return;
         }
 
-        return roleList.toArray(new String[roleList.size()]);
+        final Supplier<Hashtable<String, String>> adminEnv = () -> createAdminEnv();
+        search(fessConfig.getLdapAdminRoleBaseDn(), fessConfig.getLdapAdminRoleFilter(role.getName()), null, adminEnv, result -> {
+            if (result.hasMore()) {
+                final String entryDN = fessConfig.getLdapAdminRoleSecurityPrincipal(role.getName());
+                delete(entryDN, adminEnv);
+            } else {
+                logger.info("{} does not exist in LDAP server.", role.getName());
+            }
+        });
+
+    }
+
+    public void insert(final Group group) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (!fessConfig.isLdapAdminEnabled()) {
+            return;
+        }
+
+        final Supplier<Hashtable<String, String>> adminEnv = () -> createAdminEnv();
+        search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(group.getName()), null, adminEnv, result -> {
+            if (result.hasMore()) {
+                logger.info("{} exists in LDAP server.", group.getName());
+            } else {
+                final String entryDN = fessConfig.getLdapAdminGroupSecurityPrincipal(group.getName());
+                final BasicAttributes entry = new BasicAttributes();
+                addGroupAttributes(entry, group.getName(), fessConfig);
+                final Attribute oc = fessConfig.getLdapAdminGroupObjectClassAttribute();
+                entry.put(oc);
+                insert(entryDN, entry, adminEnv);
+            }
+        });
+    }
+
+    protected void addGroupAttributes(final BasicAttributes entry, final String name, final FessConfig fessConfig) {
+        // nothing
     }
+
+    public void delete(final Group group) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (!fessConfig.isLdapAdminEnabled()) {
+            return;
+        }
+
+        final Supplier<Hashtable<String, String>> adminEnv = () -> createAdminEnv();
+        search(fessConfig.getLdapAdminGroupBaseDn(), fessConfig.getLdapAdminGroupFilter(group.getName()), null, adminEnv, result -> {
+            if (result.hasMore()) {
+                final String entryDN = fessConfig.getLdapAdminGroupSecurityPrincipal(group.getName());
+                delete(entryDN, adminEnv);
+            } else {
+                logger.info("{} does not exist in LDAP server.", group.getName());
+            }
+        });
+    }
+
+    public void changePassword(final String username, final String password) {
+        final FessConfig fessConfig = ComponentUtil.getFessConfig();
+        if (!fessConfig.isLdapAdminEnabled()) {
+            return;
+        }
+
+        final Supplier<Hashtable<String, String>> adminEnv = () -> createAdminEnv();
+        final String userDN = fessConfig.getLdapAdminUserSecurityPrincipal(username);
+        search(fessConfig.getLdapAdminUserBaseDn(), fessConfig.getLdapAdminUserFilter(username), null, adminEnv, result -> {
+            if (result.hasMore()) {
+                final List<ModificationItem> modifyList = new ArrayList<>();
+                modifyReplaceEntry(modifyList, "userPassword", password);
+                modify(userDN, modifyList, adminEnv);
+            } else {
+                throw new LdapOperationException("User is not found: " + username);
+            }
+        });
+
+    }
+
+    protected void insert(final String entryDN, final Attributes entry, final Supplier<Hashtable<String, String>> envSupplier) {
+        try (DirContextHolder holder = getDirContext(envSupplier)) {
+            logger.debug("Inserting {}", entryDN);
+            holder.get().createSubcontext(entryDN, entry);
+        } catch (final NamingException e) {
+            throw new LdapOperationException("Failed to add " + entryDN, e);
+        }
+    }
+
+    protected void delete(final String entryDN, final Supplier<Hashtable<String, String>> envSupplier) {
+        try (DirContextHolder holder = getDirContext(envSupplier)) {
+            logger.debug("Deleting {}", entryDN);
+            holder.get().destroySubcontext(entryDN);
+        } catch (final NamingException e) {
+            throw new LdapOperationException("Failed to delete " + entryDN, e);
+        }
+    }
+
+    protected void search(final String baseDn, final String filter, final String[] returningAttrs,
+            final Supplier<Hashtable<String, String>> envSupplier, final SearcConsumer consumer) {
+        try (DirContextHolder holder = getDirContext(envSupplier)) {
+            final SearchControls controls = new SearchControls();
+            controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+            if (returningAttrs != null) {
+                controls.setReturningAttributes(returningAttrs);
+            }
+
+            consumer.accept(holder.get().search(baseDn, filter, controls));
+        } catch (final NamingException e) {
+            throw new LdapOperationException("Failed to search " + baseDn + " with " + filter, e);
+        }
+    }
+
+    protected void modifyAddEntry(final List<ModificationItem> modifyList, final String name, final String value) {
+        final Attribute attr = new BasicAttribute(name, value);
+        final ModificationItem mod = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr);
+        modifyList.add(mod);
+    }
+
+    protected void modifyReplaceEntry(final List<ModificationItem> modifyList, final String name, final String value) {
+        final Attribute attr = new BasicAttribute(name, value);
+        final ModificationItem mod = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, attr);
+        modifyList.add(mod);
+    }
+
+    protected void modifyDeleteEntry(final List<ModificationItem> modifyList, final String name, final String value) {
+        final Attribute attr = new BasicAttribute(name, value);
+        final ModificationItem mod = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr);
+        modifyList.add(mod);
+    }
+
+    protected void modify(final String dn, final List<ModificationItem> modifyList, final Supplier<Hashtable<String, String>> envSupplier) {
+        try (DirContextHolder holder = getDirContext(envSupplier)) {
+            holder.get().modifyAttributes(dn, modifyList.toArray(new ModificationItem[modifyList.size()]));
+        } catch (final NamingException e) {
+            throw new LdapOperationException("Failed to search " + dn, e);
+        }
+    }
+
+    interface SearcConsumer {
+        void accept(NamingEnumeration<SearchResult> t) throws NamingException;
+    }
+
+    protected DirContextHolder getDirContext(final Supplier<Hashtable<String, String>> envSupplier) {
+        DirContextHolder holder = contextLocal.get();
+        if (holder == null) {
+            final Hashtable<String, String> env = envSupplier.get();
+            try {
+                holder = new DirContextHolder(new InitialDirContext(env));
+                contextLocal.set(holder);
+                return holder;
+            } catch (final NamingException e) {
+                throw new LdapOperationException("Failed to create DirContext.", e);
+            }
+        } else {
+            holder.inc();
+            return holder;
+        }
+    }
+
+    protected class DirContextHolder implements AutoCloseable {
+        private final DirContext context;
+
+        private int counter = 1;
+
+        protected DirContextHolder(final DirContext context) {
+            this.context = context;
+        }
+
+        public DirContext get() {
+            return context;
+        }
+
+        public void inc() {
+            counter++;
+        }
+
+        @Override
+        public void close() {
+            if (counter > 1) {
+                counter--;
+            } else {
+                contextLocal.remove();
+                if (context != null) {
+                    try {
+                        context.close();
+                    } catch (final NamingException e) {
+                        // ignored
+                    }
+                }
+            }
+        }
+    }
+
 }

+ 8 - 0
src/main/java/org/codelibs/fess/ldap/LdapUser.java

@@ -17,6 +17,8 @@ package org.codelibs.fess.ldap;
 
 import java.util.Hashtable;
 
+import javax.naming.Context;
+
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.fess.entity.FessUser;
 import org.codelibs.fess.util.ComponentUtil;
@@ -48,6 +50,8 @@ public class LdapUser implements FessUser {
             final String accountFilter = ComponentUtil.getFessConfig().getLdapAccountFilter();
             if (StringUtil.isNotBlank(baseDn) && StringUtil.isNotBlank(accountFilter)) {
                 roles = ComponentUtil.getLdapManager().getRoles(this, baseDn, accountFilter);
+            } else {
+                roles = StringUtil.EMPTY_STRINGS;
             }
         }
         return roles;
@@ -62,4 +66,8 @@ public class LdapUser implements FessUser {
     public Hashtable<String, String> getEnvironment() {
         return env;
     }
+
+    public String getPassword() {
+        return getEnvironment().get(Context.SECURITY_CREDENTIALS);
+    }
 }

+ 2 - 8
src/main/java/org/codelibs/fess/mylasta/action/FessHtmlPath.java

@@ -317,17 +317,11 @@ public interface FessHtmlPath {
     /** The path of the HTML: /index.jsp */
     HtmlNext path_IndexJsp = new HtmlNext("/index.jsp");
 
-    /** The path of the HTML: /login/footer.jsp */
-    HtmlNext path_Login_FooterJsp = new HtmlNext("/login/footer.jsp");
-
-    /** The path of the HTML: /login/header.jsp */
-    HtmlNext path_Login_HeaderJsp = new HtmlNext("/login/header.jsp");
-
     /** The path of the HTML: /login/index.jsp */
     HtmlNext path_Login_IndexJsp = new HtmlNext("/login/index.jsp");
 
-    /** The path of the HTML: /login/logout.jsp */
-    HtmlNext path_Login_LogoutJsp = new HtmlNext("/login/logout.jsp");
+    /** The path of the HTML: /profile/index.jsp */
+    HtmlNext path_Profile_IndexJsp = new HtmlNext("/profile/index.jsp");
 
     /** The path of the HTML: /search.jsp */
     HtmlNext path_SearchJsp = new HtmlNext("/search.jsp");

+ 48 - 0
src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java

@@ -437,6 +437,15 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Login Required */
     public static final String LABELS_LOGIN_REQUIRED = "{labels.loginRequired}";
 
+    /** The key of the message: Current Password */
+    public static final String LABELS_OLD_PASSWORD = "{labels.oldPassword}";
+
+    /** The key of the message: New Password */
+    public static final String LABELS_NEW_PASSWORD = "{labels.newPassword}";
+
+    /** The key of the message: New Password(Confirm) */
+    public static final String LABELS_CONFIRM_NEW_PASSWORD = "{labels.confirmNewPassword}";
+
     /** The key of the message: System */
     public static final String LABELS_menu_system = "{labels.menu_system}";
 
@@ -770,6 +779,30 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Logout */
     public static final String LABELS_logout_button = "{labels.logout_button}";
 
+    /** The key of the message: Change Password */
+    public static final String LABELS_PROFILE = "{labels.profile}";
+
+    /** The key of the message: Profile */
+    public static final String LABELS_profile_button = "{labels.profile_button}";
+
+    /** The key of the message: Profile */
+    public static final String LABELS_PROFILE_TITLE = "{labels.profile.title}";
+
+    /** The key of the message: Update */
+    public static final String LABELS_PROFILE_UPDATE = "{labels.profile.update}";
+
+    /** The key of the message: Back */
+    public static final String LABELS_PROFILE_BACK = "{labels.profile.back}";
+
+    /** The key of the message: Current Password */
+    public static final String LABELS_PROFILE_placeholder_old_password = "{labels.profile.placeholder_old_password}";
+
+    /** The key of the message: New Password */
+    public static final String LABELS_PROFILE_placeholder_new_password = "{labels.profile.placeholder_new_password}";
+
+    /** The key of the message: Confirm New Password */
+    public static final String LABELS_PROFILE_placeholder_confirm_new_password = "{labels.profile.placeholder_confirm_new_password}";
+
     /** The key of the message: Search */
     public static final String LABELS_TOP_SEARCH = "{labels.top.search}";
 
@@ -1307,6 +1340,12 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Error Page (BadRequest) */
     public static final String LABELS_design_file_errorBadRequest = "{labels.design_file_errorBadRequest}";
 
+    /** The key of the message: Login Page */
+    public static final String LABELS_design_file_login = "{labels.design_file_login}";
+
+    /** The key of the message: Profile Page */
+    public static final String LABELS_design_file_profile = "{labels.design_file_profile}";
+
     /** The key of the message: Edit JSP File */
     public static final String LABELS_design_title_edit_content = "{labels.design_title_edit_content}";
 
@@ -2001,6 +2040,15 @@ public class FessLabels extends ActionMessages {
     /** The key of the message: Account Filter */
     public static final String LABELS_ldap_account_filter = "{labels.ldap_account_filter}";
 
+    /** The key of the message: Login page */
+    public static final String LABELS_notification_login = "{labels.notification_login}";
+
+    /** The key of the message: Search top page */
+    public static final String LABELS_notification_search_top = "{labels.notification_search_top}";
+
+    /** The key of the message: Notification */
+    public static final String LABELS_general_menu_notification = "{labels.general_menu_notification}";
+
     /** The key of the message: Send TestMail */
     public static final String LABELS_send_testmail = "{labels.send_testmail}";
 

+ 54 - 3
src/main/java/org/codelibs/fess/mylasta/action/FessMessages.java

@@ -273,7 +273,13 @@ public class FessMessages extends FessLabels {
     public static final String ERRORS_failed_to_send_testmail = "{errors.failed_to_send_testmail}";
 
     /** The key of the message: Could not find index for backup. */
-    public static final String ERRORS_COULD_NOT_FIND_BACKUP_INDEX = "{errors.could.not.find.backup.index}";
+    public static final String ERRORS_could_not_find_backup_index = "{errors.could_not_find_backup_index}";
+
+    /** The key of the message: The current password is incorrect. */
+    public static final String ERRORS_no_user_for_changing_password = "{errors.no_user_for_changing_password}";
+
+    /** The key of the message: Failed to change your password. */
+    public static final String ERRORS_failed_to_change_password = "{errors.failed_to_change_password}";
 
     /** The key of the message: The given query has unknown condition. */
     public static final String ERRORS_invalid_query_unknown = "{errors.invalid_query_unknown}";
@@ -350,6 +356,9 @@ public class FessMessages extends FessLabels {
     /** The key of the message: Deleted job logs. */
     public static final String SUCCESS_job_log_delete_all = "{success.job_log_delete_all}";
 
+    /** The key of the message: Changed your password. */
+    public static final String SUCCESS_changed_password = "{success.changed_password}";
+
     /** The key of the message: Created data. */
     public static final String SUCCESS_crud_create_crud_table = "{success.crud_create_crud_table}";
 
@@ -1576,7 +1585,7 @@ public class FessMessages extends FessLabels {
     }
 
     /**
-     * Add the created action message for the key 'errors.could.not.find.backup.index' with parameters.
+     * Add the created action message for the key 'errors.could_not_find_backup_index' with parameters.
      * <pre>
      * message: Could not find index for backup.
      * </pre>
@@ -1585,7 +1594,35 @@ public class FessMessages extends FessLabels {
      */
     public FessMessages addErrorsCouldNotFindBackupIndex(String property) {
         assertPropertyNotNull(property);
-        add(property, new ActionMessage(ERRORS_COULD_NOT_FIND_BACKUP_INDEX));
+        add(property, new ActionMessage(ERRORS_could_not_find_backup_index));
+        return this;
+    }
+
+    /**
+     * Add the created action message for the key 'errors.no_user_for_changing_password' with parameters.
+     * <pre>
+     * message: The current password is incorrect.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsNoUserForChangingPassword(String property) {
+        assertPropertyNotNull(property);
+        add(property, new ActionMessage(ERRORS_no_user_for_changing_password));
+        return this;
+    }
+
+    /**
+     * Add the created action message for the key 'errors.failed_to_change_password' with parameters.
+     * <pre>
+     * message: Failed to change your password.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addErrorsFailedToChangePassword(String property) {
+        assertPropertyNotNull(property);
+        add(property, new ActionMessage(ERRORS_failed_to_change_password));
         return this;
     }
 
@@ -1951,6 +1988,20 @@ public class FessMessages extends FessLabels {
         return this;
     }
 
+    /**
+     * Add the created action message for the key 'success.changed_password' with parameters.
+     * <pre>
+     * message: Changed your password.
+     * </pre>
+     * @param property The property name for the message. (NotNull)
+     * @return this. (NotNull)
+     */
+    public FessMessages addSuccessChangedPassword(String property) {
+        assertPropertyNotNull(property);
+        add(property, new ActionMessage(SUCCESS_changed_password));
+        return this;
+    }
+
     /**
      * Add the created action message for the key 'success.crud_create_crud_table' with parameters.
      * <pre>

+ 427 - 60
src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java

@@ -180,7 +180,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     String CRAWLER_DOCUMENT_FILE_DEFAULT_LANG = "crawler.document.file.default.lang";
 
     /** The key of the configuration. e.g. true */
-    String CRAWLER_DOCUMENT_CACHE_ENABLE = "crawler.document.cache.enable";
+    String CRAWLER_DOCUMENT_CACHE_ENABLED = "crawler.document.cache.enabled";
 
     /** The key of the configuration. e.g. 2621440 */
     String CRAWLER_DOCUMENT_CACHE_MAX_SIZE = "crawler.document.cache.max.size";
@@ -380,18 +380,15 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. true */
     String SMB_ROLE_FROM_FILE = "smb.role.from.file";
 
-    /** The key of the configuration. e.g. true */
-    String SMB_ROLE_AS_USER = "smb.role.as.user";
-
-    /** The key of the configuration. e.g. true */
-    String SMB_ROLE_AS_GROUP = "smb.role.as.group";
-
     /** The key of the configuration. e.g. 1,2 */
     String SMB_AVAILABLE_SID_TYPES = "smb.available.sid.types";
 
     /** The key of the configuration. e.g. .fess_config,.fess_user */
     String INDEX_BACKUP_TARGETS = "index.backup.targets";
 
+    /** The key of the configuration. e.g. admin */
+    String AUTHENTICATION_ADMIN_USERS = "authentication.admin.users";
+
     /** The key of the configuration. e.g. admin */
     String AUTHENTICATION_ADMIN_ROLES = "authentication.admin.roles";
 
@@ -614,6 +611,75 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g. guest */
     String SUGGEST_ROLE_FILTERS = "suggest.role.filters";
 
+    /** The key of the configuration. e.g. false */
+    String LDAP_ADMIN_ENABLED = "ldap.admin.enabled";
+
+    /** The key of the configuration. e.g. com.sun.jndi.ldap.LdapCtxFactory */
+    String LDAP_ADMIN_INITIAL_CONTEXT_FACTORY = "ldap.admin.initial.context.factory";
+
+    /** The key of the configuration. e.g. simple */
+    String LDAP_ADMIN_SECURITY_AUTHENTICATION = "ldap.admin.security.authentication";
+
+    /** The key of the configuration. e.g. ldap://localhost:1389 */
+    String LDAP_ADMIN_PROVIDER_URL = "ldap.admin.provider.url";
+
+    /** The key of the configuration. e.g. cn=Directory Manager */
+    String LDAP_ADMIN_SECURITY_PRINCIPAL = "ldap.admin.security.principal";
+
+    /** The key of the configuration. e.g. password */
+    String LDAP_ADMIN_SECURITY_CREDENTIALS = "ldap.admin.security.credentials";
+
+    /** The key of the configuration. e.g. uid=%s */
+    String LDAP_ADMIN_USER_FILTER = "ldap.admin.user.filter";
+
+    /** The key of the configuration. e.g. ou=People,dc=fess,dc=codelibs,dc=org */
+    String LDAP_ADMIN_USER_BASE_DN = "ldap.admin.user.base.dn";
+
+    /** The key of the configuration. e.g. organizationalPerson,top,person,inetOrgPerson */
+    String LDAP_ADMIN_USER_OBJECT_CLASSES = "ldap.admin.user.object.classes";
+
+    /** The key of the configuration. e.g. cn=%s */
+    String LDAP_ADMIN_ROLE_FILTER = "ldap.admin.role.filter";
+
+    /** The key of the configuration. e.g. ou=Role,dc=fess,dc=codelibs,dc=org */
+    String LDAP_ADMIN_ROLE_BASE_DN = "ldap.admin.role.base.dn";
+
+    /** The key of the configuration. e.g. groupOfNames */
+    String LDAP_ADMIN_ROLE_OBJECT_CLASSES = "ldap.admin.role.object.classes";
+
+    /** The key of the configuration. e.g. cn=%s */
+    String LDAP_ADMIN_GROUP_FILTER = "ldap.admin.group.filter";
+
+    /** The key of the configuration. e.g. ou=Group,dc=fess,dc=codelibs,dc=org */
+    String LDAP_ADMIN_GROUP_BASE_DN = "ldap.admin.group.base.dn";
+
+    /** The key of the configuration. e.g. groupOfNames */
+    String LDAP_ADMIN_GROUP_OBJECT_CLASSES = "ldap.admin.group.object.classes";
+
+    /** The key of the configuration. e.g. true */
+    String LDAP_ADMIN_SYNC_PASSWORD = "ldap.admin.sync.password";
+
+    /** The key of the configuration. e.g. memberOf */
+    String LDAP_MEMBEROF_ATTRIBUTE = "ldap.memberof.attribute";
+
+    /** The key of the configuration. e.g. true */
+    String LDAP_ROLE_SEARCH_USER_ENABLED = "ldap.role.search.user.enabled";
+
+    /** The key of the configuration. e.g. true */
+    String LDAP_ROLE_SEARCH_GROUP_ENABLED = "ldap.role.search.group.enabled";
+
+    /** The key of the configuration. e.g. true */
+    String LDAP_ROLE_SEARCH_ROLE_ENABLED = "ldap.role.search.role.enabled";
+
+    /** The key of the configuration. e.g. 1 */
+    String LDAP_ROLE_SEARCH_USER_PREFIX = "ldap.role.search.user.prefix";
+
+    /** The key of the configuration. e.g. 2 */
+    String LDAP_ROLE_SEARCH_GROUP_PREFIX = "ldap.role.search.group.prefix";
+
+    /** The key of the configuration. e.g. R */
+    String LDAP_ROLE_SEARCH_ROLE_PREFIX = "ldap.role.search.role.prefix";
+
     /**
      * Get the value of property as {@link String}.
      * @param propertyKey The key of the property. (NotNull)
@@ -1084,20 +1150,20 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     Integer getCrawlerDocumentFileDefaultLangAsInteger();
 
     /**
-     * Get the value for the key 'crawler.document.cache.enable'. <br>
+     * Get the value for the key 'crawler.document.cache.enabled'. <br>
      * The value is, e.g. true <br>
      * comment: cache
      * @return The value of found property. (NotNull: if not found, exception but basically no way)
      */
-    String getCrawlerDocumentCacheEnable();
+    String getCrawlerDocumentCacheEnabled();
 
     /**
-     * Is the property for the key 'crawler.document.cache.enable' true? <br>
+     * Is the property for the key 'crawler.document.cache.enabled' true? <br>
      * The value is, e.g. true <br>
      * comment: cache
      * @return The determination, true or false. (if not found, exception but basically no way)
      */
-    boolean isCrawlerDocumentCacheEnable();
+    boolean isCrawlerDocumentCacheEnabled();
 
     /**
      * Get the value for the key 'crawler.document.cache.max.size'. <br>
@@ -1639,34 +1705,6 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
      */
     boolean isSmbRoleFromFile();
 
-    /**
-     * Get the value for the key 'smb.role.as.user'. <br>
-     * The value is, e.g. true <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSmbRoleAsUser();
-
-    /**
-     * Is the property for the key 'smb.role.as.user' true? <br>
-     * The value is, e.g. true <br>
-     * @return The determination, true or false. (if not found, exception but basically no way)
-     */
-    boolean isSmbRoleAsUser();
-
-    /**
-     * Get the value for the key 'smb.role.as.group'. <br>
-     * The value is, e.g. true <br>
-     * @return The value of found property. (NotNull: if not found, exception but basically no way)
-     */
-    String getSmbRoleAsGroup();
-
-    /**
-     * Is the property for the key 'smb.role.as.group' true? <br>
-     * The value is, e.g. true <br>
-     * @return The determination, true or false. (if not found, exception but basically no way)
-     */
-    boolean isSmbRoleAsGroup();
-
     /**
      * Get the value for the key 'smb.available.sid.types'. <br>
      * The value is, e.g. 1,2 <br>
@@ -1691,11 +1729,18 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     String getIndexBackupTargets();
 
     /**
-     * Get the value for the key 'authentication.admin.roles'. <br>
+     * Get the value for the key 'authentication.admin.users'. <br>
      * The value is, e.g. admin <br>
      * comment: ------
      * @return The value of found property. (NotNull: if not found, exception but basically no way)
      */
+    String getAuthenticationAdminUsers();
+
+    /**
+     * Get the value for the key 'authentication.admin.roles'. <br>
+     * The value is, e.g. admin <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
     String getAuthenticationAdminRoles();
 
     /**
@@ -2454,6 +2499,220 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
      */
     String getSuggestRoleFilters();
 
+    /**
+     * Get the value for the key 'ldap.admin.enabled'. <br>
+     * The value is, e.g. false <br>
+     * comment: ------
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminEnabled();
+
+    /**
+     * Is the property for the key 'ldap.admin.enabled' true? <br>
+     * The value is, e.g. false <br>
+     * comment: ------
+     * @return The determination, true or false. (if not found, exception but basically no way)
+     */
+    boolean isLdapAdminEnabled();
+
+    /**
+     * Get the value for the key 'ldap.admin.initial.context.factory'. <br>
+     * The value is, e.g. com.sun.jndi.ldap.LdapCtxFactory <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminInitialContextFactory();
+
+    /**
+     * Get the value for the key 'ldap.admin.security.authentication'. <br>
+     * The value is, e.g. simple <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminSecurityAuthentication();
+
+    /**
+     * Get the value for the key 'ldap.admin.provider.url'. <br>
+     * The value is, e.g. ldap://localhost:1389 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminProviderUrl();
+
+    /**
+     * Get the value for the key 'ldap.admin.security.principal'. <br>
+     * The value is, e.g. cn=Directory Manager <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminSecurityPrincipal();
+
+    /**
+     * Get the value for the key 'ldap.admin.security.credentials'. <br>
+     * The value is, e.g. password <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminSecurityCredentials();
+
+    /**
+     * Get the value for the key 'ldap.admin.user.filter'. <br>
+     * The value is, e.g. uid=%s <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminUserFilter();
+
+    /**
+     * Get the value for the key 'ldap.admin.user.base.dn'. <br>
+     * The value is, e.g. ou=People,dc=fess,dc=codelibs,dc=org <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminUserBaseDn();
+
+    /**
+     * Get the value for the key 'ldap.admin.user.object.classes'. <br>
+     * The value is, e.g. organizationalPerson,top,person,inetOrgPerson <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminUserObjectClasses();
+
+    /**
+     * Get the value for the key 'ldap.admin.role.filter'. <br>
+     * The value is, e.g. cn=%s <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminRoleFilter();
+
+    /**
+     * Get the value for the key 'ldap.admin.role.base.dn'. <br>
+     * The value is, e.g. ou=Role,dc=fess,dc=codelibs,dc=org <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminRoleBaseDn();
+
+    /**
+     * Get the value for the key 'ldap.admin.role.object.classes'. <br>
+     * The value is, e.g. groupOfNames <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminRoleObjectClasses();
+
+    /**
+     * Get the value for the key 'ldap.admin.group.filter'. <br>
+     * The value is, e.g. cn=%s <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminGroupFilter();
+
+    /**
+     * Get the value for the key 'ldap.admin.group.base.dn'. <br>
+     * The value is, e.g. ou=Group,dc=fess,dc=codelibs,dc=org <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminGroupBaseDn();
+
+    /**
+     * Get the value for the key 'ldap.admin.group.object.classes'. <br>
+     * The value is, e.g. groupOfNames <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminGroupObjectClasses();
+
+    /**
+     * Get the value for the key 'ldap.admin.sync.password'. <br>
+     * The value is, e.g. true <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapAdminSyncPassword();
+
+    /**
+     * Is the property for the key 'ldap.admin.sync.password' true? <br>
+     * The value is, e.g. true <br>
+     * @return The determination, true or false. (if not found, exception but basically no way)
+     */
+    boolean isLdapAdminSyncPassword();
+
+    /**
+     * Get the value for the key 'ldap.memberof.attribute'. <br>
+     * The value is, e.g. memberOf <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapMemberofAttribute();
+
+    /**
+     * Get the value for the key 'ldap.role.search.user.enabled'. <br>
+     * The value is, e.g. true <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapRoleSearchUserEnabled();
+
+    /**
+     * Is the property for the key 'ldap.role.search.user.enabled' true? <br>
+     * The value is, e.g. true <br>
+     * @return The determination, true or false. (if not found, exception but basically no way)
+     */
+    boolean isLdapRoleSearchUserEnabled();
+
+    /**
+     * Get the value for the key 'ldap.role.search.group.enabled'. <br>
+     * The value is, e.g. true <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapRoleSearchGroupEnabled();
+
+    /**
+     * Is the property for the key 'ldap.role.search.group.enabled' true? <br>
+     * The value is, e.g. true <br>
+     * @return The determination, true or false. (if not found, exception but basically no way)
+     */
+    boolean isLdapRoleSearchGroupEnabled();
+
+    /**
+     * Get the value for the key 'ldap.role.search.role.enabled'. <br>
+     * The value is, e.g. true <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapRoleSearchRoleEnabled();
+
+    /**
+     * Is the property for the key 'ldap.role.search.role.enabled' true? <br>
+     * The value is, e.g. true <br>
+     * @return The determination, true or false. (if not found, exception but basically no way)
+     */
+    boolean isLdapRoleSearchRoleEnabled();
+
+    /**
+     * Get the value for the key 'ldap.role.search.user.prefix'. <br>
+     * The value is, e.g. 1 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapRoleSearchUserPrefix();
+
+    /**
+     * Get the value for the key 'ldap.role.search.user.prefix' as {@link Integer}. <br>
+     * The value is, e.g. 1 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     * @throws NumberFormatException When the property is not integer.
+     */
+    Integer getLdapRoleSearchUserPrefixAsInteger();
+
+    /**
+     * Get the value for the key 'ldap.role.search.group.prefix'. <br>
+     * The value is, e.g. 2 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapRoleSearchGroupPrefix();
+
+    /**
+     * Get the value for the key 'ldap.role.search.group.prefix' as {@link Integer}. <br>
+     * The value is, e.g. 2 <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     * @throws NumberFormatException When the property is not integer.
+     */
+    Integer getLdapRoleSearchGroupPrefixAsInteger();
+
+    /**
+     * Get the value for the key 'ldap.role.search.role.prefix'. <br>
+     * The value is, e.g. R <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getLdapRoleSearchRolePrefix();
+
     /**
      * The simple implementation for configuration.
      * @author FreeGen
@@ -2699,12 +2958,12 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return getAsInteger(FessConfig.CRAWLER_DOCUMENT_FILE_DEFAULT_LANG);
         }
 
-        public String getCrawlerDocumentCacheEnable() {
-            return get(FessConfig.CRAWLER_DOCUMENT_CACHE_ENABLE);
+        public String getCrawlerDocumentCacheEnabled() {
+            return get(FessConfig.CRAWLER_DOCUMENT_CACHE_ENABLED);
         }
 
-        public boolean isCrawlerDocumentCacheEnable() {
-            return is(FessConfig.CRAWLER_DOCUMENT_CACHE_ENABLE);
+        public boolean isCrawlerDocumentCacheEnabled() {
+            return is(FessConfig.CRAWLER_DOCUMENT_CACHE_ENABLED);
         }
 
         public String getCrawlerDocumentCacheMaxSize() {
@@ -2975,22 +3234,6 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return is(FessConfig.SMB_ROLE_FROM_FILE);
         }
 
-        public String getSmbRoleAsUser() {
-            return get(FessConfig.SMB_ROLE_AS_USER);
-        }
-
-        public boolean isSmbRoleAsUser() {
-            return is(FessConfig.SMB_ROLE_AS_USER);
-        }
-
-        public String getSmbRoleAsGroup() {
-            return get(FessConfig.SMB_ROLE_AS_GROUP);
-        }
-
-        public boolean isSmbRoleAsGroup() {
-            return is(FessConfig.SMB_ROLE_AS_GROUP);
-        }
-
         public String getSmbAvailableSidTypes() {
             return get(FessConfig.SMB_AVAILABLE_SID_TYPES);
         }
@@ -3003,6 +3246,10 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return get(FessConfig.INDEX_BACKUP_TARGETS);
         }
 
+        public String getAuthenticationAdminUsers() {
+            return get(FessConfig.AUTHENTICATION_ADMIN_USERS);
+        }
+
         public String getAuthenticationAdminRoles() {
             return get(FessConfig.AUTHENTICATION_ADMIN_ROLES);
         }
@@ -3410,5 +3657,125 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
         public String getSuggestRoleFilters() {
             return get(FessConfig.SUGGEST_ROLE_FILTERS);
         }
+
+        public String getLdapAdminEnabled() {
+            return get(FessConfig.LDAP_ADMIN_ENABLED);
+        }
+
+        public boolean isLdapAdminEnabled() {
+            return is(FessConfig.LDAP_ADMIN_ENABLED);
+        }
+
+        public String getLdapAdminInitialContextFactory() {
+            return get(FessConfig.LDAP_ADMIN_INITIAL_CONTEXT_FACTORY);
+        }
+
+        public String getLdapAdminSecurityAuthentication() {
+            return get(FessConfig.LDAP_ADMIN_SECURITY_AUTHENTICATION);
+        }
+
+        public String getLdapAdminProviderUrl() {
+            return get(FessConfig.LDAP_ADMIN_PROVIDER_URL);
+        }
+
+        public String getLdapAdminSecurityPrincipal() {
+            return get(FessConfig.LDAP_ADMIN_SECURITY_PRINCIPAL);
+        }
+
+        public String getLdapAdminSecurityCredentials() {
+            return get(FessConfig.LDAP_ADMIN_SECURITY_CREDENTIALS);
+        }
+
+        public String getLdapAdminUserFilter() {
+            return get(FessConfig.LDAP_ADMIN_USER_FILTER);
+        }
+
+        public String getLdapAdminUserBaseDn() {
+            return get(FessConfig.LDAP_ADMIN_USER_BASE_DN);
+        }
+
+        public String getLdapAdminUserObjectClasses() {
+            return get(FessConfig.LDAP_ADMIN_USER_OBJECT_CLASSES);
+        }
+
+        public String getLdapAdminRoleFilter() {
+            return get(FessConfig.LDAP_ADMIN_ROLE_FILTER);
+        }
+
+        public String getLdapAdminRoleBaseDn() {
+            return get(FessConfig.LDAP_ADMIN_ROLE_BASE_DN);
+        }
+
+        public String getLdapAdminRoleObjectClasses() {
+            return get(FessConfig.LDAP_ADMIN_ROLE_OBJECT_CLASSES);
+        }
+
+        public String getLdapAdminGroupFilter() {
+            return get(FessConfig.LDAP_ADMIN_GROUP_FILTER);
+        }
+
+        public String getLdapAdminGroupBaseDn() {
+            return get(FessConfig.LDAP_ADMIN_GROUP_BASE_DN);
+        }
+
+        public String getLdapAdminGroupObjectClasses() {
+            return get(FessConfig.LDAP_ADMIN_GROUP_OBJECT_CLASSES);
+        }
+
+        public String getLdapAdminSyncPassword() {
+            return get(FessConfig.LDAP_ADMIN_SYNC_PASSWORD);
+        }
+
+        public boolean isLdapAdminSyncPassword() {
+            return is(FessConfig.LDAP_ADMIN_SYNC_PASSWORD);
+        }
+
+        public String getLdapMemberofAttribute() {
+            return get(FessConfig.LDAP_MEMBEROF_ATTRIBUTE);
+        }
+
+        public String getLdapRoleSearchUserEnabled() {
+            return get(FessConfig.LDAP_ROLE_SEARCH_USER_ENABLED);
+        }
+
+        public boolean isLdapRoleSearchUserEnabled() {
+            return is(FessConfig.LDAP_ROLE_SEARCH_USER_ENABLED);
+        }
+
+        public String getLdapRoleSearchGroupEnabled() {
+            return get(FessConfig.LDAP_ROLE_SEARCH_GROUP_ENABLED);
+        }
+
+        public boolean isLdapRoleSearchGroupEnabled() {
+            return is(FessConfig.LDAP_ROLE_SEARCH_GROUP_ENABLED);
+        }
+
+        public String getLdapRoleSearchRoleEnabled() {
+            return get(FessConfig.LDAP_ROLE_SEARCH_ROLE_ENABLED);
+        }
+
+        public boolean isLdapRoleSearchRoleEnabled() {
+            return is(FessConfig.LDAP_ROLE_SEARCH_ROLE_ENABLED);
+        }
+
+        public String getLdapRoleSearchUserPrefix() {
+            return get(FessConfig.LDAP_ROLE_SEARCH_USER_PREFIX);
+        }
+
+        public Integer getLdapRoleSearchUserPrefixAsInteger() {
+            return getAsInteger(FessConfig.LDAP_ROLE_SEARCH_USER_PREFIX);
+        }
+
+        public String getLdapRoleSearchGroupPrefix() {
+            return get(FessConfig.LDAP_ROLE_SEARCH_GROUP_PREFIX);
+        }
+
+        public Integer getLdapRoleSearchGroupPrefixAsInteger() {
+            return getAsInteger(FessConfig.LDAP_ROLE_SEARCH_GROUP_PREFIX);
+        }
+
+        public String getLdapRoleSearchRolePrefix() {
+            return get(FessConfig.LDAP_ROLE_SEARCH_ROLE_PREFIX);
+        }
     }
 }

+ 97 - 13
src/main/java/org/codelibs/fess/mylasta/direction/FessProp.java

@@ -21,6 +21,9 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
+import javax.naming.directory.Attribute;
+import javax.naming.directory.BasicAttribute;
+
 import org.codelibs.core.exception.ClassNotFoundRuntimeException;
 import org.codelibs.core.lang.StringUtil;
 import org.codelibs.core.misc.Pair;
@@ -227,7 +230,7 @@ public interface FessProp {
 
     String getJobSystemJobIds();
 
-    public default boolean isSystemJobId(String id) {
+    public default boolean isSystemJobId(final String id) {
         if (StringUtil.isBlank(getJobSystemJobIds())) {
             return false;
         }
@@ -236,7 +239,7 @@ public interface FessProp {
 
     String getSmbAvailableSidTypes();
 
-    public default boolean isAvailableSmbSidType(int sidType) {
+    public default boolean isAvailableSmbSidType(final int sidType) {
         if (StringUtil.isBlank(getSmbAvailableSidTypes())) {
             return false;
         }
@@ -254,7 +257,7 @@ public interface FessProp {
 
     String getOnlineHelpSupportedLangs();
 
-    public default boolean isOnlineHelpSupportedLang(String lang) {
+    public default boolean isOnlineHelpSupportedLang(final String lang) {
         if (StringUtil.isBlank(getOnlineHelpSupportedLangs())) {
             return false;
         }
@@ -288,7 +291,7 @@ public interface FessProp {
 
     String getJobTemplateTitleData();
 
-    public default String getJobTemplateTitle(String type) {
+    public default String getJobTemplateTitle(final String type) {
         if (Constants.WEB_CRAWLER_TYPE.equals(type)) {
             return getJobTemplateTitleWeb();
         } else if (Constants.FILE_CRAWLER_TYPE.equals(type)) {
@@ -304,9 +307,9 @@ public interface FessProp {
     public default Class<? extends LaJob> getSchedulerJobClassAsClass() {
         try {
             @SuppressWarnings("unchecked")
-            Class<? extends LaJob> clazz = (Class<? extends LaJob>) Class.forName(getSchedulerJobClass());
+            final Class<? extends LaJob> clazz = (Class<? extends LaJob>) Class.forName(getSchedulerJobClass());
             return clazz;
-        } catch (ClassNotFoundException e) {
+        } catch (final ClassNotFoundException e) {
             throw new ClassNotFoundRuntimeException(e);
         }
     }
@@ -319,7 +322,7 @@ public interface FessProp {
 
     String getCrawlerMetadataContentExcludes();
 
-    public default boolean isCrawlerMetadataContentIncluded(String name) {
+    public default boolean isCrawlerMetadataContentIncluded(final String name) {
         Pattern[] patterns = (Pattern[]) propMap.get(CRAWLER_METADATA_CONTENT_EXCLUDES);
         if (patterns == null) {
             patterns =
@@ -332,14 +335,14 @@ public interface FessProp {
 
     String getCrawlerMetadataNameMapping();
 
-    public default Pair<String, String> getCrawlerMetadataNameMapping(String name) {
+    public default Pair<String, String> getCrawlerMetadataNameMapping(final String name) {
         @SuppressWarnings("unchecked")
         Map<String, Pair<String, String>> params = (Map<String, Pair<String, String>>) propMap.get(CRAWLER_METADATA_NAME_MAPPING);
         if (params == null) {
             params = StreamUtil.of(getCrawlerMetadataNameMapping().split("\n")).filter(v -> StringUtil.isNotBlank(v)).map(v -> {
-                String[] values = v.split("=");
+                final String[] values = v.split("=");
                 if (values.length == 2) {
-                    String[] subValues = values[1].split(":");
+                    final String[] subValues = values[1].split(":");
                     if (subValues.length == 2) {
                         return new Tuple3<String, String, String>(values[0], subValues[0], subValues[1]);
                     } else {
@@ -379,7 +382,7 @@ public interface FessProp {
 
     String getQueryLanguageMapping();
 
-    public default String getQueryLanguage(Locale locale) {
+    public default String getQueryLanguage(final Locale locale) {
         if (locale == null) {
             return null;
         }
@@ -387,7 +390,7 @@ public interface FessProp {
         Map<String, String> params = (Map<String, String>) propMap.get(QUERY_LANGUAGE_MAPPING);
         if (params == null) {
             params = StreamUtil.of(getQueryLanguageMapping().split("\n")).filter(v -> StringUtil.isNotBlank(v)).map(v -> {
-                String[] values = v.split("=");
+                final String[] values = v.split("=");
                 if (values.length == 2) {
                     return new Pair<String, String>(values[0], values[1]);
                 }
@@ -414,9 +417,90 @@ public interface FessProp {
 
     String getSupportedUploadedFiles();
 
-    public default boolean isSupportedUploadedFile(String name) {
+    public default boolean isSupportedUploadedFile(final String name) {
         return StreamUtil.of(getSuggestPopularWordExcludes().split(",")).filter(s -> StringUtil.isNotBlank(s))
                 .anyMatch(s -> s.equals(name));
     }
 
+    String getLdapAdminUserObjectClasses();
+
+    public default Attribute getLdapAdminUserObjectClassAttribute() {
+        final Attribute oc = new BasicAttribute("objectClass");
+        StreamUtil.of(getLdapAdminUserObjectClasses().split(",")).filter(s -> StringUtil.isNotBlank(s)).forEach(s -> oc.add(s.trim()));
+        return oc;
+    }
+
+    String getLdapAdminUserFilter();
+
+    public default String getLdapAdminUserFilter(final String name) {
+        return String.format(getLdapAdminUserFilter(), name);
+    }
+
+    String getLdapAdminUserBaseDn();
+
+    public default String getLdapAdminUserSecurityPrincipal(final String name) {
+        final StringBuilder buf = new StringBuilder(100);
+        buf.append(String.format(getLdapAdminUserFilter(), name));
+        if (StringUtil.isNotBlank(getLdapAdminUserBaseDn())) {
+            buf.append(',').append(getLdapAdminUserBaseDn());
+        }
+        return buf.toString();
+    }
+
+    String getLdapAdminRoleObjectClasses();
+
+    public default Attribute getLdapAdminRoleObjectClassAttribute() {
+        final Attribute oc = new BasicAttribute("objectClass");
+        StreamUtil.of(getLdapAdminRoleObjectClasses().split(",")).filter(s -> StringUtil.isNotBlank(s)).forEach(s -> oc.add(s.trim()));
+        return oc;
+    }
+
+    String getLdapAdminRoleFilter();
+
+    public default String getLdapAdminRoleFilter(final String name) {
+        return String.format(getLdapAdminRoleFilter(), name);
+    }
+
+    String getLdapAdminRoleBaseDn();
+
+    public default String getLdapAdminRoleSecurityPrincipal(final String name) {
+        final StringBuilder buf = new StringBuilder(100);
+        buf.append(String.format(getLdapAdminRoleFilter(), name));
+        if (StringUtil.isNotBlank(getLdapAdminRoleBaseDn())) {
+            buf.append(',').append(getLdapAdminRoleBaseDn());
+        }
+        return buf.toString();
+    }
+
+    String getLdapAdminGroupObjectClasses();
+
+    public default Attribute getLdapAdminGroupObjectClassAttribute() {
+        final Attribute oc = new BasicAttribute("objectClass");
+        StreamUtil.of(getLdapAdminGroupObjectClasses().split(",")).filter(s -> StringUtil.isNotBlank(s)).forEach(s -> oc.add(s.trim()));
+        return oc;
+    }
+
+    String getLdapAdminGroupFilter();
+
+    public default String getLdapAdminGroupFilter(final String name) {
+        return String.format(getLdapAdminGroupFilter(), name);
+    }
+
+    String getLdapAdminGroupBaseDn();
+
+    public default String getLdapAdminGroupSecurityPrincipal(final String name) {
+        final StringBuilder buf = new StringBuilder(100);
+        buf.append(String.format(getLdapAdminGroupFilter(), name));
+        if (StringUtil.isNotBlank(getLdapAdminGroupBaseDn())) {
+            buf.append(',').append(getLdapAdminGroupBaseDn());
+        }
+        return buf.toString();
+    }
+
+    String getAuthenticationAdminUsers();
+
+    public default boolean isAdminUser(final String username) {
+        return StreamUtil.of(getAuthenticationAdminUsers().split(",")).anyMatch(s -> s.equals(username));
+    }
+
 }

+ 6 - 2
src/main/java/org/codelibs/fess/taglib/FessFunctions.java

@@ -78,11 +78,15 @@ public class FessFunctions {
     }
 
     public static Date parseDate(final String value) {
+        return parseDate(value, Constants.ISO_DATETIME_FORMAT);
+    }
+
+    public static Date parseDate(final String value, final String format) {
         if (value == null) {
             return null;
         }
         try {
-            final SimpleDateFormat sdf = new SimpleDateFormat(Constants.ISO_DATETIME_FORMAT);
+            final SimpleDateFormat sdf = new SimpleDateFormat(format);
             sdf.setTimeZone(Constants.TIMEZONE_UTC);
             return sdf.parse(value);
         } catch (final ParseException e) {
@@ -124,7 +128,7 @@ public class FessFunctions {
     public static String pagingQuery(final String query) {
         final HttpServletRequest request = LaRequestUtil.getRequest();
         @SuppressWarnings("unchecked")
-        List<String> pagingQueryList = (List<String>) request.getAttribute(Constants.PAGING_QUERY_LIST);
+        final List<String> pagingQueryList = (List<String>) request.getAttribute(Constants.PAGING_QUERY_LIST);
         if (pagingQueryList != null) {
             final String prefix;
             if (query != null) {

+ 1 - 1
src/main/resources/app.xml

@@ -146,7 +146,7 @@
 	</component>
 	<component name="popularWordHelper" class="org.codelibs.fess.helper.PopularWordHelper">
 	</component>
-	<component name="userInfoHelper" class="org.codelibs.fess.helper.impl.CookieUserInfoHelperImpl">
+	<component name="userInfoHelper" class="org.codelibs.fess.helper.UserInfoHelper">
 	</component>
 	<component name="openSearchHelper" class="org.codelibs.fess.helper.OpenSearchHelper">
 		<property name="osddPath">"/WEB-INF/orig/open-search/osdd.xml"</property>

+ 9 - 1
src/main/resources/fess.xml

@@ -83,10 +83,18 @@
 			<arg>"cache"</arg>
 			<arg>"cache.hbs"</arg>
 		</postConstruct>
+		<postConstruct name="addDesignJspFileName">
+			<arg>"login"</arg>
+			<arg>"login/index.jsp"</arg>
+		</postConstruct>
+		<postConstruct name="addDesignJspFileName">
+			<arg>"profile"</arg>
+			<arg>"profile/index.jsp"</arg>
+		</postConstruct>
 	</component>
 	<component name="crawlingInfoHelper" class="org.codelibs.fess.helper.CrawlingInfoHelper">
 	</component>
-	<component name="roleQueryHelper" class="org.codelibs.fess.helper.impl.RoleQueryHelperImpl">
+	<component name="roleQueryHelper" class="org.codelibs.fess.helper.RoleQueryHelper">
 		<!-- ex. parameter: fessRoles=123%0aadmin -->
 		<!-- 
 		<property name="parameterKey">"fessRoles"</property>

+ 36 - 7
src/main/resources/fess_config.properties

@@ -99,7 +99,7 @@ crawler.document.file.append.body.content=true
 crawler.document.file.default.lang=
 
 # cache
-crawler.document.cache.enable=true
+crawler.document.cache.enabled=true
 crawler.document.cache.max.size=2621440
 crawler.document.cache.supported.mimetypes=text/html
 #,text/plain,application/xml,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation
@@ -212,8 +212,6 @@ query.boost.content.lang=1.3
 
 # acl
 smb.role.from.file=true
-smb.role.as.user=true
-smb.role.as.group=true
 smb.available.sid.types=1,2
 
 # backup
@@ -225,6 +223,7 @@ index.backup.targets=.fess_config,.fess_user
 # ----------------------------------------------------------
 #                                                 Permission
 #                                                     ------
+authentication.admin.users=admin
 authentication.admin.roles=admin
 
 search.default.roles=guest
@@ -271,21 +270,21 @@ paging.search.page.size=20
 paging.search.page.max.size=100
 
 # ----------------------------------------------------------
-#                                                      Mail
+#                                                       Mail
 #                                                     ------
 # From
 mail.from.name = Administrator
 mail.from.address = root@localhost
 
 # ----------------------------------------------------------
-#                                                 Scheduler
+#                                                  Scheduler
 #                                                     ------
 scheduler.target.name=
 scheduler.job.class=org.codelibs.fess.app.job.ScriptExecutorJob
 scheduler.concurrent.exec.mode=QUIT
 
 # ----------------------------------------------------------
-#                                                OnlineHelp
+#                                                 OnlineHelp
 #                                                     ------
 online.help.base.link=http://fess.codelibs.org/{lang}/{version}/admin/
 online.help.name.failureurl=failureurl
@@ -324,7 +323,7 @@ online.help.name.backup=backup
 online.help.supported.langs=
 
 # ----------------------------------------------------------
-#                                                   Suggest
+#                                                    Suggest
 #                                                     ------
 
 suggest.popular.word.seed=0
@@ -346,3 +345,33 @@ suggest.role.filters=\
 guest
 
 
+# ----------------------------------------------------------
+#                                                      LDAP
+#                                                     ------
+
+ldap.admin.enabled=false
+ldap.admin.initial.context.factory=com.sun.jndi.ldap.LdapCtxFactory
+ldap.admin.security.authentication=simple
+ldap.admin.provider.url=ldap\://localhost\:1389
+ldap.admin.security.principal=cn\=Directory Manager
+ldap.admin.security.credentials=password
+ldap.admin.user.filter=uid\=%s
+ldap.admin.user.base.dn=ou\=People,dc\=fess,dc\=codelibs,dc\=org
+ldap.admin.user.object.classes=organizationalPerson,top,person,inetOrgPerson
+ldap.admin.role.filter=cn\=%s
+ldap.admin.role.base.dn=ou\=Role,dc\=fess,dc\=codelibs,dc\=org
+ldap.admin.role.object.classes=groupOfNames
+ldap.admin.group.filter=cn\=%s
+ldap.admin.group.base.dn=ou\=Group,dc\=fess,dc\=codelibs,dc\=org
+ldap.admin.group.object.classes=groupOfNames
+ldap.admin.sync.password=true
+
+ldap.memberof.attribute=memberOf
+#ldap.memberof.attribute=isMemberOf
+
+ldap.role.search.user.enabled=true
+ldap.role.search.group.enabled=true
+ldap.role.search.role.enabled=true
+ldap.role.search.user.prefix=1
+ldap.role.search.group.prefix=2
+ldap.role.search.role.prefix=R

+ 18 - 0
src/main/resources/fess_label.properties

@@ -138,6 +138,9 @@ labels.ldapSecurityPrincipal=Bind DN
 labels.ldapBaseDn=Base DN
 labels.ldapAccountFilter=Account Filter
 labels.loginRequired=Login Required
+labels.oldPassword=Current Password
+labels.newPassword=New Password
+labels.confirmNewPassword=New Password(Confirm)
 
 labels.menu_system=System
 labels.menu_wizard=Wizard
@@ -251,6 +254,14 @@ labels.logout_title=Logout
 labels.logout=Logout
 labels.do_you_want_to_logout=Do you want to logout?
 labels.logout_button=Logout
+labels.profile=Change Password
+labels.profile_button=Profile
+labels.profile.title=Profile
+labels.profile.update=Update
+labels.profile.back=Back
+labels.profile.placeholder_old_password=Current Password
+labels.profile.placeholder_new_password=New Password
+labels.profile.placeholder_confirm_new_password=Confirm New Password
 labels.top.search=Search
 labels.index_title=Fess
 labels.index_form_search_btn=Search
@@ -432,6 +443,8 @@ labels.design_file_errorNotFound=Error Page (Not Found)
 labels.design_file_errorSystem=Error Page (System Error)
 labels.design_file_errorRedirect=Error Page (Redirect)
 labels.design_file_errorBadRequest=Error Page (BadRequest)
+labels.design_file_login=Login Page
+labels.design_file_profile=Profile Page
 labels.design_title_edit_content=Edit JSP File
 labels.design_button_update=Update
 labels.design_button_back=Back
@@ -660,10 +673,15 @@ labels.general_menu_crawler=Crawler
 labels.general_menu_logging=Logging
 labels.general_menu_suggest=Suggest
 labels.general_menu_ldap=LDAP
+labels.general_menu_notification=Notification
 labels.ldap_provider_url=LDAP URL
 labels.ldap_security_principal=Bind DN
 labels.ldap_base_dn=Base DN
 labels.ldap_account_filter=Account Filter
+labels.ldap_account_filter=Account Filter
+labels.notification_login=Login page
+labels.notification_search_top=Search top page
+labels.general_menu_notification=Notification
 labels.send_testmail=Send TestMail
 labels.backup_configuration=Back Up
 labels.backup_name=Name

+ 16 - 0
src/main/resources/fess_label_en.properties

@@ -138,6 +138,9 @@ labels.ldapSecurityPrincipal=Bind DN
 labels.ldapBaseDn=Base DN
 labels.ldapAccountFilter=Account Filter
 labels.loginRequired=Login Required
+labels.oldPassword=Current Password
+labels.newPassword=New Password
+labels.confirmNewPassword=New Password(Confirm)
 
 labels.menu_system=System
 labels.menu_wizard=Wizard
@@ -251,6 +254,14 @@ labels.logout_title=Logout
 labels.logout=Logout
 labels.do_you_want_to_logout=Do you want to logout?
 labels.logout_button=Logout
+labels.profile=Change Password
+labels.profile_button=Profile
+labels.profile.title=Profile
+labels.profile.update=Update
+labels.profile.back=Back
+labels.profile.placeholder_old_password=Current Password
+labels.profile.placeholder_new_password=New Password
+labels.profile.placeholder_confirm_new_password=Confirm New Password
 labels.top.search=Search
 labels.index_title=Fess
 labels.index_form_search_btn=Search
@@ -432,6 +443,8 @@ labels.design_file_errorNotFound=Error Page (Not Found)
 labels.design_file_errorSystem=Error Page (System Error)
 labels.design_file_errorRedirect=Error Page (Redirect)
 labels.design_file_errorBadRequest=Error Page (BadRequest)
+labels.design_file_login=Login Page
+labels.design_file_profile=Profile Page
 labels.design_title_edit_content=Edit JSP File
 labels.design_button_update=Update
 labels.design_button_back=Back
@@ -660,10 +673,13 @@ labels.general_menu_crawler=Crawler
 labels.general_menu_logging=Logging
 labels.general_menu_suggest=Suggest
 labels.general_menu_ldap=LDAP
+labels.general_menu_notification=Notification
 labels.ldap_provider_url=LDAP URL
 labels.ldap_security_principal=Bind DN
 labels.ldap_base_dn=Base DN
 labels.ldap_account_filter=Account Filter
+labels.notification_login=Login page
+labels.notification_search_top=Search top page
 labels.send_testmail=Send TestMail
 labels.backup_configuration=Back Up
 labels.backup_name=Name

+ 19 - 2
src/main/resources/fess_label_ja.properties

@@ -133,6 +133,10 @@ labels.term = \u691c\u7d22\u8a9e
 labels.searchParams = \u691c\u7d22\u30d1\u30e9\u30e1\u30fc\u30bf
 labels.fields = \u30d5\u30a3\u30fc\u30eb\u30c9
 labels.ex_q = \u62e1\u5f35\u30af\u30a8\u30ea\u30fc
+labels.oldPassword=\u73fe\u5728\u306e\u30d1\u30b9\u30ef\u30fc\u30c9
+labels.newPassword=\u65b0\u3057\u3044\u30d1\u30b9\u30ef\u30fc\u30c9
+labels.confirmNewPassword=\u65b0\u3057\u3044\u30d1\u30b9\u30ef\u30fc\u30c9(\u78ba\u8a8d)
+
 labels.menu_system = \u30b7\u30b9\u30c6\u30e0
 labels.menu_wizard = \u30a6\u30a3\u30b6\u30fc\u30c9
 labels.menu_crawl_config = \u5168\u822c
@@ -245,6 +249,14 @@ labels.logout_title = \u30ed\u30b0\u30a2\u30a6\u30c8
 labels.logout = \u30ed\u30b0\u30a2\u30a6\u30c8
 labels.do_you_want_to_logout = \u30ed\u30b0\u30a2\u30a6\u30c8\u3057\u307e\u3059\u304b\uff1f
 labels.logout_button = \u30ed\u30b0\u30a2\u30a6\u30c8
+labels.profile = \u30d1\u30b9\u30ef\u30fc\u30c9\u5909\u66f4
+labels.profile_button = \u8a2d\u5b9a
+labels.profile.title= \u8a2d\u5b9a
+labels.profile.update= \u66f4\u65b0
+labels.profile.back= \u623b\u308b
+labels.profile.placeholder_old_password= \u73fe\u5728\u306e\u30d1\u30b9\u30ef\u30fc\u30c9
+labels.profile.placeholder_new_password= \u65b0\u3057\u3044\u30d1\u30b9\u30ef\u30fc\u30c9
+labels.profile.placeholder_confirm_new_password= \u65b0\u3057\u3044\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u78ba\u8a8d
 labels.top.search = \u691c\u7d22
 labels.index_title = Fess
 labels.index_form_search_btn = \u691c\u7d22
@@ -426,6 +438,8 @@ labels.design_file_errorNotFound = \u30a8\u30e9\u30fc\u30da\u30fc\u30b8 (\u898b\
 labels.design_file_errorSystem = \u30a8\u30e9\u30fc\u30da\u30fc\u30b8 (\u30b7\u30b9\u30c6\u30e0\u30a8\u30e9\u30fc) 
 labels.design_file_errorRedirect = \u30a8\u30e9\u30fc\u30da\u30fc\u30b8 (\u30ea\u30c0\u30a4\u30ec\u30af\u30c8) 
 labels.design_file_errorBadRequest = \u30a8\u30e9\u30fc\u30da\u30fc\u30b8 (BadRequest) 
+labels.design_file_login=\u30ed\u30b0\u30a4\u30f3\u30da\u30fc\u30b8
+labels.design_file_profile=\u8a2d\u5b9a\u30da\u30fc\u30b8
 labels.design_title_edit_content = \u30da\u30fc\u30b8\u306e\u7de8\u96c6\u30d5\u30a1\u30a4\u30eb\u306e\u8868\u793a
 labels.design_button_update = \u66f4\u65b0
 labels.design_button_back = \u623b\u308b
@@ -647,15 +661,18 @@ labels.general_menu_system = \u30b7\u30b9\u30c6\u30e0
 labels.general_menu_crawler = \u30af\u30ed\u30fc\u30e9
 labels.general_menu_logging = \u30ed\u30ae\u30f3\u30b0
 labels.general_menu_suggest = \u30b5\u30b8\u30a7\u30b9\u30c8
-labels.send_testmail=\u30c6\u30b9\u30c8\u30e1\u30fc\u30eb\u306e\u9001\u4fe1
+labels.general_menu_ldap=LDAP
+labels.general_menu_notification=\u304a\u77e5\u3089\u305b\u8868\u793a
 labels.ldapProviderUrl=LDAP URL
 labels.ldapSecurityPrincipal=Bind DN
 labels.ldapBaseDn=Base DN
-labels.general_menu_ldap=LDAP
 labels.ldap_provider_url=LDAP URL
 labels.ldap_security_principal=Bind DN
 labels.ldap_base_dn=Base DN
 labels.ldapAccountFilter=\u30a2\u30ab\u30a6\u30f3\u30c8\u30d5\u30a3\u30eb\u30bf
 labels.ldap_account_filter=\u30a2\u30ab\u30a6\u30f3\u30c8\u30d5\u30a3\u30eb\u30bf
+labels.notification_login=\u30ed\u30b0\u30a4\u30f3\u30da\u30fc\u30b8
+labels.notification_search_top=\u691c\u7d22\u30c8\u30c3\u30d7\u30da\u30fc\u30b8
+labels.send_testmail=\u30c6\u30b9\u30c8\u30e1\u30fc\u30eb\u306e\u9001\u4fe1
 labels.backup_configuration=\u30d0\u30c3\u30af\u30a2\u30c3\u30d7
 labels.backup_name=\u540d\u524d

+ 4 - 1
src/main/resources/fess_message.properties

@@ -112,7 +112,9 @@ errors.invalid_confirm_password=Confirm Password does not match.
 errors.cannot_delete_doc_because_of_running=Crawler is running. The document cannot be deleted.
 errors.failed_to_delete_doc_in_admin=Failed to delete document.
 errors.failed_to_send_testmail=Failed to send the test mail.
-errors.could.not.find.backup.index=Could not find index for backup.
+errors.could_not_find_backup_index=Could not find index for backup.
+errors.no_user_for_changing_password=The current password is incorrect.
+errors.failed_to_change_password=Failed to change your password.
 
 errors.invalid_query_unknown=The given query has unknown condition.
 errors.invalid_query_parse_error=The given query is invalid.
@@ -141,6 +143,7 @@ success.upload_elevate_word=Uploaded Additional Word file.
 success.upload_bad_word=Uploaded Bad Word file.
 success.send_testmail=Sent the test mail.
 success.job_log_delete_all=Deleted job logs.
+success.changed_password=Changed your password.
 
 success.crud_create_crud_table=Created data.
 success.crud_update_crud_table=Updated data.

+ 4 - 1
src/main/resources/fess_message_en.properties

@@ -112,7 +112,9 @@ errors.invalid_confirm_password=Confirm Password does not match.
 errors.cannot_delete_doc_because_of_running=Crawler is running. The document cannot be deleted.
 errors.failed_to_delete_doc_in_admin=Failed to delete document.
 errors.failed_to_send_testmail=Failed to send the test mail.
-errors.could.not.find.backup.index=Could not find index for backup.
+errors.could_not_find_backup_index=Could not find index for backup.
+errors.no_user_for_changing_password=The current password is incorrect.
+errors.failed_to_change_password=Failed to change your password.
 
 errors.invalid_query_unknown=The given query has unknown condition.
 errors.invalid_query_parse_error=The given query is invalid.
@@ -141,6 +143,7 @@ success.upload_elevate_word=Uploaded Additional Word file.
 success.upload_bad_word=Uploaded Bad Word file.
 success.send_testmail=Sent the test mail.
 success.job_log_delete_all=Deleted job logs.
+success.changed_password=Changed your password.
 
 success.crud_create_crud_table=Created data.
 success.crud_update_crud_table=Updated data.

+ 4 - 1
src/main/resources/fess_message_ja.properties

@@ -116,7 +116,9 @@ errors.invalid_query_unsupported_sort_order = \u6307\u5b9a\u3055\u308c\u305f\u30
 errors.crud_invalid_mode = \u30e2\u30fc\u30c9\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002({0} \u3067\u306a\u304f\u3001{1} \u3067\u3059)
 errors.crud_failed_to_create_crud_table = \u65b0\u3057\u3044\u30c7\u30fc\u30bf\u306e\u4f5c\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 errors.crud_could_not_find_crud_table = \u30c7\u30fc\u30bf {0} \u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002
-errors.could.not.find.backup.index=\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u7528\u306e\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002
+errors.could_not_find_backup_index=\u30d0\u30c3\u30af\u30a2\u30c3\u30d7\u7528\u306e\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002
+errors.no_user_for_changing_password=\u73fe\u5728\u306e\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002
+errors.failed_to_change_password=\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u5909\u66f4\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002
 
 success.update_crawler_params = \u30d1\u30e9\u30e1\u30fc\u30bf\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f\u3002
 success.delete_doc_from_index = \u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u304b\u3089\u6587\u66f8\u3092\u524a\u9664\u3059\u308b\u30d7\u30ed\u30bb\u30b9\u3092\u958b\u59cb\u3057\u307e\u3057\u305f\u3002
@@ -135,6 +137,7 @@ success.upload_elevate_word = \u8ffd\u52a0\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u
 success.upload_bad_word = \u9664\u5916\u30ef\u30fc\u30c9\u30d5\u30a1\u30a4\u30eb\u3092\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f\u3002
 success.send_testmail=\u30c6\u30b9\u30c8\u30e1\u30fc\u30eb\u3092\u9001\u4fe1\u3057\u307e\u3057\u305f\u3002
 success.job_log_delete_all=\u30b8\u30e7\u30d6\u30ed\u30b0\u3092\u524a\u9664\u3057\u307e\u3057\u305f\u3002
+success.changed_password=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5909\u66f4\u3057\u307e\u3057\u305f\u3002
 
 success.crud_create_crud_table = \u30c7\u30fc\u30bf\u3092\u4f5c\u6210\u3057\u307e\u3057\u305f\u3002
 success.crud_update_crud_table = \u30c7\u30fc\u30bf\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f\u3002

+ 15 - 15
src/main/resources/mail/crawler.dfmail

@@ -5,29 +5,29 @@
 subject: [FESS] Crawler completed: /*pmb.hostname*/
 >>>
 --- Server Info ---
-Host Name: /*IF pmb.hostname != null*//*pmb.hostname*//*END*//*IF pmb.hostname == null*/Unknown/*END*/
+Host Name: /*pmb.hostname:orElse('Unknown')*/
 
 --- Web/FileSystem Crawler ---
-Start Time: /*pmb.webFsCrawlStartTime*/
-End Time:   /*pmb.webFsCrawlEndTime*/
-Exec Time:  /*pmb.webFsCrawlExecTime*/ ms
+Start Time: /*pmb.webFsCrawlStartTime:orElse('-')*/
+End Time:   /*pmb.webFsCrawlEndTime:orElse('-')*/
+Exec Time:  /*pmb.webFsCrawlExecTime:orElse('-')*/ ms
 
 --- Web/FileSystem Indexer ---
-Exec Time:  /*pmb.webFsIndexExecTime*/ ms
-Num of Doc: /*pmb.webFsIndexSize*/ docs
+Exec Time:  /*pmb.webFsIndexExecTime:orElse('-')*/ ms
+Num of Doc: /*pmb.webFsIndexSize:orElse('-')*/ docs
 
 --- Data Store Crawler ---
-Start Time: /*pmb.dataCrawlStartTime*/
-End Time:   /*pmb.dataCrawlEndTime*/
-Exec Time:  /*pmb.dataCrawlExecTime*/ ms
+Start Time: /*pmb.dataCrawlStartTime:orElse('-')*/
+End Time:   /*pmb.dataCrawlEndTime:orElse('-')*/
+Exec Time:  /*pmb.dataCrawlExecTime:orElse('-')*/ ms
 
 --- Data Store Indexer ---
-Exec Time:  /*pmb.dataIndexExecTime*/ ms
-Num of Doc: /*pmb.dataFsIndexSize*/ docs
+Exec Time:  /*pmb.dataIndexExecTime:orElse('-')*/ ms
+Num of Doc: /*pmb.dataFsIndexSize:orElse('-')*/ docs
 
 --- Total ---
-Start Time: /*pmb.crawlerStartTime*/
-End Time:   /*pmb.crawlerEndTime*/
-Exec Time:  /*pmb.crawlerExecTime*/ ms
-Status:     /*pmb.status*/
+Start Time: /*pmb.crawlerStartTime:orElse('-')*/
+End Time:   /*pmb.crawlerEndTime:orElse('-')*/
+Exec Time:  /*pmb.crawlerExecTime:orElse('-')*/ ms
+Status:     /*pmb.status:orElse('-')*/
 

+ 3 - 2
src/main/resources/mail/es_status.dfmail

@@ -1,10 +1,11 @@
 /*
  [Crawler Notification]
 */
-subject: [FESS] Status Change: /*pmb.hostname*/
+subject: [FESS] Status Change: /*pmb.hostname:orElse('Unknown')*/
 >>>
 --- Server Info ---
-Host Name: /*IF pmb.hostname != null*//*pmb.hostname*//*END*//*IF pmb.hostname == null*/Unknown/*END*/
+Host Name: /*pmb.hostname:orElse('Unknown')*/
+
 Elasticsearch: /*pmb.server*/
 
 --- Status ---

+ 2 - 2
src/main/resources/mail/testmail.dfmail

@@ -1,7 +1,7 @@
 /*
  [Test Mail]
 */
-subject: [FESS] Test Mail: /*pmb.hostname*/
+subject: [FESS] Test Mail: /*pmb.hostname:orElse('Unknown')*/
 >>>
-This is a test mail from /*pmb.hostname*/
+This is a test mail from /*pmb.hostname:orElse('Unknown')*/
 

+ 12 - 0
src/main/webapp/WEB-INF/fe.tld

@@ -59,6 +59,18 @@
     </example>
   </function>
 
+  <function>
+    <description>
+      Returns Date from a given value.
+    </description>
+    <name>parseDateFormat</name>
+    <function-class>org.codelibs.fess.taglib.FessFunctions</function-class>
+    <function-signature>java.util.Date parseDate(java.lang.String, java.lang.String)</function-signature>
+    <example>
+      ${fe:parseDate(doc.tstamp, "yyyy-MM-dd HH:mm:ss")}
+    </example>
+  </function>
+
   <function>
     <description>
       Returns formatted number from a given value.

+ 3 - 0
src/main/webapp/WEB-INF/orig/view/header.jsp

@@ -15,6 +15,9 @@ ${fe:facetForm()}${fe:geoForm()}
 							<i class="fa fa-user"></i>${username}
 						</a>
 						<div class="dropdown-menu" aria-labelledby="userMenu">
+							<la:link href="/profile" styleClass="dropdown-item">
+								<la:message key="labels.profile" />
+							</la:link>
 							<la:link href="/logout" styleClass="dropdown-item">
 								<la:message key="labels.logout" />
 							</la:link>

+ 4 - 0
src/main/webapp/WEB-INF/orig/view/index.jsp

@@ -29,6 +29,9 @@
 								aria-expanded="false"> <i class="fa fa-user"></i>${username}
 							</a>
 							<div class="dropdown-menu" aria-labelledby="userMenu">
+								<la:link href="/profile" styleClass="dropdown-item">
+									<la:message key="labels.profile" />
+								</la:link>
 								<la:link href="/logout" styleClass="dropdown-item">
 									<la:message key="labels.logout" />
 								</la:link>
@@ -51,6 +54,7 @@
 					<img src="${f:url('/images/logo.png')}"
 						alt="<la:message key="labels.index_title" />" />
 				</h1>
+				<div class="notification">${notification}</div>
 				<div>
 					<la:info id="msg" message="true">
 						<div class="alert-message info">${msg}</div>

+ 102 - 0
src/main/webapp/WEB-INF/orig/view/login/index.jsp

@@ -0,0 +1,102 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>
+<!DOCTYPE html>
+<html>
+<head profile="http://a9.com/-/spec/opensearch/1.1/">
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta http-equiv="x-ua-compatible" content="ie=edge">
+<title><la:message key="labels.login.title" /></title>
+<link href="${f:url('/css/style-base.css')}" rel="stylesheet"
+	type="text/css" />
+<link href="${f:url('/css/style.css')}" rel="stylesheet" type="text/css" />
+<link href="${f:url('/css/admin/style.css')}" rel="stylesheet"
+	type="text/css" />
+<link href="${f:url('/css/admin/font-awesome.min.css')}"
+	rel="stylesheet" type="text/css" />
+<link href="${f:url('/css/admin/AdminLTE.min.css')}" rel="stylesheet"
+	type="text/css" />
+<link href="${f:url('/css/admin/skins/skin-blue.min.css')}"
+	rel="stylesheet" type="text/css" />
+<!--[if lt IE 9]>
+<script src="${f:url('/css/admin/html5shiv.min.js')}"></script>
+<script src="${f:url('/css/admin/respond.min.js')}"></script>
+<![endif]-->
+</head>
+<body class="hold-transition login-page">
+	<div class="login-box">
+		<div class="login-logo">
+			<la:link href="/">
+				<img src="${f:url('/images/logo-top.png')}"
+					alt="<la:message key="labels.header_brand_name" />" />
+			</la:link>
+		</div>
+		<!-- /.login-logo -->
+		<div class="notification">${notification}</div>
+		<div class="login-box-body">
+			<p class="login-box-msg">
+				<la:message key="labels.login" />
+			</p>
+			<%-- Message --%>
+			<div>
+				<la:info id="msg" message="false">
+					<div class="alert alert-info">${msg}</div>
+				</la:info>
+				<la:errors />
+			</div>
+			<la:form styleId="login" method="post">
+				<div class="form-group has-feedback">
+					<div class="input-group">
+						<span class="input-group-addon"><i class="fa fa-user fa-fw"></i></span>
+						<c:set var="ph_username">
+							<la:message key="labels.login.placeholder_username" />
+						</c:set>
+						<la:text property="username" styleId="username"
+							class="form-control" placeholder="${ph_username}" />
+					</div>
+				</div>
+				<div class="form-group has-feedback">
+					<div class="input-group">
+						<span class="input-group-addon"><i class="fa fa-lock fa-fw"></i></span>
+						<c:set var="ph_password">
+							<la:message key="labels.login.placeholder_password" />
+						</c:set>
+						<la:password property="password" class="form-control"
+							placeholder="${ph_password}" />
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-3"></div>
+					<!-- /.col -->
+					<div class="col-xs-6">
+						<button type="submit" name="login"
+							class="btn btn-primary btn-block btn-flat"
+							value="<la:message key="labels.login"/>">
+							<i class="fa fa-sign-in"></i>
+							<la:message key="labels.login" />
+						</button>
+					</div>
+					<!-- /.col -->
+					<div class="col-xs-3"></div>
+					<!-- /.col -->
+				</div>
+			</la:form>
+		</div>
+		<!-- /.login-box-body -->
+	</div>
+	<!-- /.login-box -->
+
+	<footer class="footer bd-footer text-muted" role="contentinfo">
+		<div class="container center">
+			<p class="text-muted">
+				<la:message key="labels.footer.copyright" />
+			</p>
+		</div>
+	</footer>
+
+	<input type="hidden" id="contextPath" value="${contextPath}" />
+	<script type="text/javascript"
+		src="${f:url('/js/jquery-2.1.4.min.js')}"></script>
+	<script type="text/javascript" src="${f:url('/js/bootstrap.js')}"></script>
+	<script type="text/javascript" src="${f:url('/js/admin/admin.js')}"></script>
+</body>
+</html>

+ 116 - 0
src/main/webapp/WEB-INF/orig/view/profile/index.jsp

@@ -0,0 +1,116 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>
+<!DOCTYPE html>
+<html>
+<head profile="http://a9.com/-/spec/opensearch/1.1/">
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta http-equiv="x-ua-compatible" content="ie=edge">
+<title><la:message key="labels.profile.title" /></title>
+<link href="${f:url('/css/style-base.css')}" rel="stylesheet"
+	type="text/css" />
+<link href="${f:url('/css/style.css')}" rel="stylesheet" type="text/css" />
+<link href="${f:url('/css/admin/style.css')}" rel="stylesheet"
+	type="text/css" />
+<link href="${f:url('/css/admin/font-awesome.min.css')}"
+	rel="stylesheet" type="text/css" />
+<link href="${f:url('/css/admin/AdminLTE.min.css')}" rel="stylesheet"
+	type="text/css" />
+<link href="${f:url('/css/admin/skins/skin-blue.min.css')}"
+	rel="stylesheet" type="text/css" />
+<!--[if lt IE 9]>
+<script src="${f:url('/css/admin/html5shiv.min.js')}"></script>
+<script src="${f:url('/css/admin/respond.min.js')}"></script>
+<![endif]-->
+</head>
+<body class="hold-transition login-page">
+	<div class="login-box">
+		<div class="login-logo">
+			<la:link href="/">
+				<img src="${f:url('/images/logo-top.png')}"
+					alt="<la:message key="labels.header_brand_name" />" />
+			</la:link>
+		</div>
+		<!-- /.login-logo -->
+		<div class="login-box-body">
+			<p class="login-box-msg">
+				<la:message key="labels.profile" />
+			</p>
+			<%-- Message --%>
+			<div>
+				<la:info id="msg" message="false">
+					<div class="alert alert-info">${msg}</div>
+				</la:info>
+				<la:errors />
+			</div>
+			<la:form styleId="login" method="post">
+				<div class="form-group has-feedback">
+					<div class="input-group">
+						<span class="input-group-addon"><i class="fa fa-lock fa-fw"></i></span>
+						<c:set var="ph_old_password">
+							<la:message key="labels.profile.placeholder_old_password" />
+						</c:set>
+						<la:password property="oldPassword" class="form-control"
+							placeholder="${ph_old_password}" />
+					</div>
+				</div>
+				<div class="form-group has-feedback">
+					<div class="input-group">
+						<span class="input-group-addon"><i class="fa fa-lock fa-fw"></i></span>
+						<c:set var="ph_new_password">
+							<la:message key="labels.profile.placeholder_new_password" />
+						</c:set>
+						<la:password property="newPassword" class="form-control"
+							     placeholder="${ph_new_password}" />
+					</div>
+				</div>
+				<div class="form-group has-feedback">
+					<div class="input-group">
+						<span class="input-group-addon"><i class="fa fa-lock fa-fw"></i></span>
+						<c:set var="ph_confirm_password">
+							<la:message key="labels.profile.placeholder_confirm_new_password" />
+						</c:set>
+						<la:password property="confirmNewPassword" class="form-control"
+							     placeholder="${ph_confirm_password}" />
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-2"></div>
+					<!-- /.col -->
+					<div class="col-xs-8">
+						<la:link href="/"
+							styleClass="btn btn-default">
+							<i class="fa fa-arrow-circle-left"></i>
+							<la:message key="labels.profile.back" />
+						</la:link>
+						<button type="submit" name="changePassword"
+							class="btn btn-warning"
+							value="<la:message key="labels.profile.update"/>">
+							<i class="fa fa-pencil"></i>
+							<la:message key="labels.profile.update" />
+						</button>
+					</div>
+					<!-- /.col -->
+					<div class="col-xs-2"></div>
+					<!-- /.col -->
+				</div>
+			</la:form>
+		</div>
+		<!-- /.login-box-body -->
+	</div>
+	<!-- /.login-box -->
+
+	<footer class="footer bd-footer text-muted" role="contentinfo">
+		<div class="container center">
+			<p class="text-muted">
+				<la:message key="labels.footer.copyright" />
+			</p>
+		</div>
+	</footer>
+
+	<input type="hidden" id="contextPath" value="${contextPath}" />
+	<script type="text/javascript"
+		src="${f:url('/js/jquery-2.1.4.min.js')}"></script>
+	<script type="text/javascript" src="${f:url('/js/bootstrap.js')}"></script>
+	<script type="text/javascript" src="${f:url('/js/admin/admin.js')}"></script>
+</body>
+</html>

+ 22 - 0
src/main/webapp/WEB-INF/view/admin/general/admin_general.jsp

@@ -329,6 +329,28 @@
 												styleClass="form-control" />
 										</div>
 									</div>
+									<%-- Nortification --%>
+									<h4><la:message key="labels.general_menu_notification" /></h4>
+									<div class="form-group">
+										<label for="notificationLogin"
+											class="col-sm-3 control-label"><la:message
+												key="labels.notification_login" /></label>
+										<div class="col-sm-9">
+											<la:errors property="notificationLogin" />
+											<la:textarea property="notificationLogin"
+												styleClass="form-control" />
+										</div>
+									</div>
+									<div class="form-group">
+										<label for="notificationSearchTop"
+											class="col-sm-3 control-label"><la:message
+												key="labels.notification_search_top" /></label>
+										<div class="col-sm-9">
+											<la:errors property="notificationSearchTop" />
+											<la:textarea property="notificationSearchTop"
+												styleClass="form-control" />
+										</div>
+									</div>
 								</div>
 								<!-- /.box-body -->
 								<div class="box-footer">

+ 43 - 1
src/main/webapp/WEB-INF/view/admin/group/admin_group_details.jsp

@@ -57,7 +57,49 @@
 								</div>
 								<!-- /.box-body -->
 								<div class="box-footer">
-									<jsp:include page="/WEB-INF/view/common/admin/crud/buttons.jsp"></jsp:include>
+									<button type="submit" class="btn btn-default" name="list" value="back">
+										<i class="fa fa-arrow-circle-left"></i>
+										<la:message key="labels.crud_button_back" />
+									</button>
+									<button type="button" class="btn btn-danger" name="delete"
+										data-toggle="modal" data-target="#confirmToDelete"
+										value="<la:message key="labels.crud_button_delete" />">
+										<i class="fa fa-trash"></i>
+										<la:message key="labels.crud_button_delete" />
+									</button>
+									<div class="modal modal-danger fade" id="confirmToDelete" tabindex="-1"
+										role="dialog">
+										<div class="modal-dialog">
+											<div class="modal-content">
+												<div class="modal-header">
+													<button type="button" class="close" data-dismiss="modal"
+														aria-label="Close">
+														<span aria-hidden="true">×</span>
+													</button>
+													<h4 class="modal-title">
+														<la:message key="labels.crud_title_delete" />
+													</h4>
+												</div>
+												<div class="modal-body">
+													<p>
+														<la:message key="labels.crud_delete_confirmation" />
+													</p>
+												</div>
+												<div class="modal-footer">
+													<button type="button" class="btn btn-outline pull-left"
+														data-dismiss="modal">
+														<la:message key="labels.crud_button_cancel" />
+													</button>
+													<button type="submit" class="btn btn-outline btn-danger"
+														name="delete"
+														value="<la:message key="labels.crud_button_delete" />">
+														<i class="fa fa-trash"></i>
+														<la:message key="labels.crud_button_delete" />
+													</button>
+												</div>
+											</div>
+										</div>
+									</div>
 								</div>
 								<!-- /.box-footer -->
 							</div>

+ 43 - 1
src/main/webapp/WEB-INF/view/admin/role/admin_role_details.jsp

@@ -56,7 +56,49 @@
 								</div>
 								<!-- /.box-body -->
 								<div class="box-footer">
-									<jsp:include page="/WEB-INF/view/common/admin/crud/buttons.jsp"></jsp:include>
+									<button type="submit" class="btn btn-default" name="list" value="back">
+										<i class="fa fa-arrow-circle-left"></i>
+										<la:message key="labels.crud_button_back" />
+									</button>
+									<button type="button" class="btn btn-danger" name="delete"
+										data-toggle="modal" data-target="#confirmToDelete"
+										value="<la:message key="labels.crud_button_delete" />">
+										<i class="fa fa-trash"></i>
+										<la:message key="labels.crud_button_delete" />
+									</button>
+									<div class="modal modal-danger fade" id="confirmToDelete" tabindex="-1"
+										role="dialog">
+										<div class="modal-dialog">
+											<div class="modal-content">
+												<div class="modal-header">
+													<button type="button" class="close" data-dismiss="modal"
+														aria-label="Close">
+														<span aria-hidden="true">×</span>
+													</button>
+													<h4 class="modal-title">
+														<la:message key="labels.crud_title_delete" />
+													</h4>
+												</div>
+												<div class="modal-body">
+													<p>
+														<la:message key="labels.crud_delete_confirmation" />
+													</p>
+												</div>
+												<div class="modal-footer">
+													<button type="button" class="btn btn-outline pull-left"
+														data-dismiss="modal">
+														<la:message key="labels.crud_button_cancel" />
+													</button>
+													<button type="submit" class="btn btn-outline btn-danger"
+														name="delete"
+														value="<la:message key="labels.crud_button_delete" />">
+														<i class="fa fa-trash"></i>
+														<la:message key="labels.crud_button_delete" />
+													</button>
+												</div>
+											</div>
+										</div>
+									</div>
 									<la:link styleClass="btn btn-success" href="/admin/roletype/createnew/${f:u(name)}">
 										<i class="fa fa-plus-circle"></i>
 										<la:message key="labels.role_button_create_crawler_role" />

+ 3 - 0
src/main/webapp/WEB-INF/view/header.jsp

@@ -15,6 +15,9 @@ ${fe:facetForm()}${fe:geoForm()}
 							<i class="fa fa-user"></i>${username}
 						</a>
 						<div class="dropdown-menu" aria-labelledby="userMenu">
+							<la:link href="/profile" styleClass="dropdown-item">
+								<la:message key="labels.profile" />
+							</la:link>
 							<la:link href="/logout" styleClass="dropdown-item">
 								<la:message key="labels.logout" />
 							</la:link>

+ 4 - 0
src/main/webapp/WEB-INF/view/index.jsp

@@ -29,6 +29,9 @@
 								aria-expanded="false"> <i class="fa fa-user"></i>${username}
 							</a>
 							<div class="dropdown-menu" aria-labelledby="userMenu">
+								<la:link href="/profile" styleClass="dropdown-item">
+									<la:message key="labels.profile" />
+								</la:link>
 								<la:link href="/logout" styleClass="dropdown-item">
 									<la:message key="labels.logout" />
 								</la:link>
@@ -51,6 +54,7 @@
 					<img src="${f:url('/images/logo.png')}"
 						alt="<la:message key="labels.index_title" />" />
 				</h1>
+				<div class="notification">${notification}</div>
 				<div>
 					<la:info id="msg" message="true">
 						<div class="alert-message info">${msg}</div>

+ 0 - 8
src/main/webapp/WEB-INF/view/login/footer.jsp

@@ -1,8 +0,0 @@
-<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>
-<footer class="footer bd-footer text-muted" role="contentinfo">
-	<div class="container center">
-		<p class="text-muted">
-			<la:message key="labels.footer.copyright" />
-		</p>
-	</div>
-</footer>

+ 0 - 6
src/main/webapp/WEB-INF/view/login/header.jsp

@@ -1,6 +0,0 @@
-<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>
-<nav class="navbar navbar-dark bg-inverse navbar-static-top pos-f-t">
-	<la:link styleClass="navbar-brand" href="/">
-		<img src="${f:url('/images/logo-head.png')}" alt="<la:message key="labels.header_brand_name" />" />
-	</la:link>
-</nav>

+ 8 - 1
src/main/webapp/WEB-INF/view/login/index.jsp

@@ -31,6 +31,7 @@
 			</la:link>
 		</div>
 		<!-- /.login-logo -->
+		<div class="notification">${notification}</div>
 		<div class="login-box-body">
 			<p class="login-box-msg">
 				<la:message key="labels.login" />
@@ -84,7 +85,13 @@
 	</div>
 	<!-- /.login-box -->
 
-	<jsp:include page="footer.jsp" />
+	<footer class="footer bd-footer text-muted" role="contentinfo">
+		<div class="container center">
+			<p class="text-muted">
+				<la:message key="labels.footer.copyright" />
+			</p>
+		</div>
+	</footer>
 
 	<input type="hidden" id="contextPath" value="${contextPath}" />
 	<script type="text/javascript"

+ 0 - 37
src/main/webapp/WEB-INF/view/login/logout.jsp

@@ -1,37 +0,0 @@
-<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-<meta http-equiv="content-style-type" content="text/css" />
-<meta http-equiv="content-script-type" content="text/javascript" />
-<title><la:message key="labels.logout_title" /></title>
-<link href="${f:url('/css/admin/style-base.css')}" rel="stylesheet"
-	type="text/css" />
-<link href="${f:url('/css/admin/style.css')}" rel="stylesheet"
-	type="text/css" />
-</head>
-<body>
-	<jsp:include page="header.jsp" />
-	<div class="container">
-		<div class="content">
-			<div class="center row">
-				<div class="col-md-12">
-					<h2>
-						<la:message key="labels.logout" />
-					</h2>
-					<div class="message">
-						<la:message key="labels.do_you_want_to_logout" />
-					</div>
-					<div class="action">
-						<la:form>
-						<input type="submit" name="logout"
-							value="<la:message key="labels.logout_button"/>" />
-						</la:form>
-					</div>
-				</div>
-			</div>
-		</div>
-		<jsp:include page="footer.jsp" />
-	</div>
-</body>
-</html>

+ 116 - 0
src/main/webapp/WEB-INF/view/profile/index.jsp

@@ -0,0 +1,116 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>
+<!DOCTYPE html>
+<html>
+<head profile="http://a9.com/-/spec/opensearch/1.1/">
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta http-equiv="x-ua-compatible" content="ie=edge">
+<title><la:message key="labels.profile.title" /></title>
+<link href="${f:url('/css/style-base.css')}" rel="stylesheet"
+	type="text/css" />
+<link href="${f:url('/css/style.css')}" rel="stylesheet" type="text/css" />
+<link href="${f:url('/css/admin/style.css')}" rel="stylesheet"
+	type="text/css" />
+<link href="${f:url('/css/admin/font-awesome.min.css')}"
+	rel="stylesheet" type="text/css" />
+<link href="${f:url('/css/admin/AdminLTE.min.css')}" rel="stylesheet"
+	type="text/css" />
+<link href="${f:url('/css/admin/skins/skin-blue.min.css')}"
+	rel="stylesheet" type="text/css" />
+<!--[if lt IE 9]>
+<script src="${f:url('/css/admin/html5shiv.min.js')}"></script>
+<script src="${f:url('/css/admin/respond.min.js')}"></script>
+<![endif]-->
+</head>
+<body class="hold-transition login-page">
+	<div class="login-box">
+		<div class="login-logo">
+			<la:link href="/">
+				<img src="${f:url('/images/logo-top.png')}"
+					alt="<la:message key="labels.header_brand_name" />" />
+			</la:link>
+		</div>
+		<!-- /.login-logo -->
+		<div class="login-box-body">
+			<p class="login-box-msg">
+				<la:message key="labels.profile" />
+			</p>
+			<%-- Message --%>
+			<div>
+				<la:info id="msg" message="false">
+					<div class="alert alert-info">${msg}</div>
+				</la:info>
+				<la:errors />
+			</div>
+			<la:form styleId="login" method="post">
+				<div class="form-group has-feedback">
+					<div class="input-group">
+						<span class="input-group-addon"><i class="fa fa-lock fa-fw"></i></span>
+						<c:set var="ph_old_password">
+							<la:message key="labels.profile.placeholder_old_password" />
+						</c:set>
+						<la:password property="oldPassword" class="form-control"
+							placeholder="${ph_old_password}" />
+					</div>
+				</div>
+				<div class="form-group has-feedback">
+					<div class="input-group">
+						<span class="input-group-addon"><i class="fa fa-lock fa-fw"></i></span>
+						<c:set var="ph_new_password">
+							<la:message key="labels.profile.placeholder_new_password" />
+						</c:set>
+						<la:password property="newPassword" class="form-control"
+							     placeholder="${ph_new_password}" />
+					</div>
+				</div>
+				<div class="form-group has-feedback">
+					<div class="input-group">
+						<span class="input-group-addon"><i class="fa fa-lock fa-fw"></i></span>
+						<c:set var="ph_confirm_password">
+							<la:message key="labels.profile.placeholder_confirm_new_password" />
+						</c:set>
+						<la:password property="confirmNewPassword" class="form-control"
+							     placeholder="${ph_confirm_password}" />
+					</div>
+				</div>
+				<div class="row">
+					<div class="col-xs-2"></div>
+					<!-- /.col -->
+					<div class="col-xs-8">
+						<la:link href="/"
+							styleClass="btn btn-default">
+							<i class="fa fa-arrow-circle-left"></i>
+							<la:message key="labels.profile.back" />
+						</la:link>
+						<button type="submit" name="changePassword"
+							class="btn btn-warning"
+							value="<la:message key="labels.profile.update"/>">
+							<i class="fa fa-pencil"></i>
+							<la:message key="labels.profile.update" />
+						</button>
+					</div>
+					<!-- /.col -->
+					<div class="col-xs-2"></div>
+					<!-- /.col -->
+				</div>
+			</la:form>
+		</div>
+		<!-- /.login-box-body -->
+	</div>
+	<!-- /.login-box -->
+
+	<footer class="footer bd-footer text-muted" role="contentinfo">
+		<div class="container center">
+			<p class="text-muted">
+				<la:message key="labels.footer.copyright" />
+			</p>
+		</div>
+	</footer>
+
+	<input type="hidden" id="contextPath" value="${contextPath}" />
+	<script type="text/javascript"
+		src="${f:url('/js/jquery-2.1.4.min.js')}"></script>
+	<script type="text/javascript" src="${f:url('/js/bootstrap.js')}"></script>
+	<script type="text/javascript" src="${f:url('/js/admin/admin.js')}"></script>
+</body>
+</html>

+ 4 - 0
src/main/webapp/css/admin/style.css

@@ -48,6 +48,10 @@
 	font-size: 13px;
 }
 
+.notification {
+	text-align: center;
+}
+
 .login-box i, section.content i {
 	margin-right: 0.2em;
 }

+ 9 - 9
src/test/java/org/codelibs/fess/helper/impl/RoleQueryHelperImplTest.java → src/test/java/org/codelibs/fess/helper/RoleQueryHelperTest.java

@@ -13,7 +13,7 @@
  * either express or implied. See the License for the specific language
  * governing permissions and limitations under the License.
  */
-package org.codelibs.fess.helper.impl;
+package org.codelibs.fess.helper;
 
 import java.util.Set;
 
@@ -23,20 +23,20 @@ import org.codelibs.core.crypto.CachedCipher;
 import org.codelibs.core.exception.IllegalBlockSizeRuntimeException;
 import org.codelibs.fess.unit.UnitFessTestCase;
 
-public class RoleQueryHelperImplTest extends UnitFessTestCase {
-    public RoleQueryHelperImpl roleQueryHelperImpl;
+public class RoleQueryHelperTest extends UnitFessTestCase {
+    public RoleQueryHelper roleQueryHelperImpl;
     public CachedCipher cipher;
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        roleQueryHelperImpl = new RoleQueryHelperImpl();
+        roleQueryHelperImpl = new RoleQueryHelper();
         cipher = new CachedCipher();
         cipher.setKey("1234567890123456");
     }
 
     public void test_buildByParameter() {
-        final RoleQueryHelperImpl roleQueryHelperImpl = new RoleQueryHelperImpl();
+        final RoleQueryHelper roleQueryHelperImpl = new RoleQueryHelper();
 
         Set<String> roleSet;
 
@@ -92,7 +92,7 @@ public class RoleQueryHelperImplTest extends UnitFessTestCase {
     }
 
     public void test_buildByHeader() {
-        final RoleQueryHelperImpl roleQueryHelperImpl = new RoleQueryHelperImpl();
+        final RoleQueryHelper roleQueryHelperImpl = new RoleQueryHelper();
 
         Set<String> roleSet;
 
@@ -152,7 +152,7 @@ public class RoleQueryHelperImplTest extends UnitFessTestCase {
     }
 
     public void test_buildByCookie() {
-        final RoleQueryHelperImpl roleQueryHelperImpl = new RoleQueryHelperImpl();
+        final RoleQueryHelper roleQueryHelperImpl = new RoleQueryHelper();
 
         Set<String> roleSet;
         Cookie cookie;
@@ -224,7 +224,7 @@ public class RoleQueryHelperImplTest extends UnitFessTestCase {
 
     public void test_decodedRoleList() {
 
-        final RoleQueryHelperImpl roleQueryHelperImpl = new RoleQueryHelperImpl();
+        final RoleQueryHelper roleQueryHelperImpl = new RoleQueryHelper();
 
         Set<String> roleSet;
         boolean encrypted;
@@ -289,7 +289,7 @@ public class RoleQueryHelperImplTest extends UnitFessTestCase {
 
     public void test_decodedRoleList_withCipher() {
 
-        final RoleQueryHelperImpl roleQueryHelperImpl = new RoleQueryHelperImpl();
+        final RoleQueryHelper roleQueryHelperImpl = new RoleQueryHelper();
         roleQueryHelperImpl.cipher = cipher;
 
         Set<String> roleSet;

+ 92 - 0
src/test/java/org/codelibs/fess/util/DocumentUtilTest.java

@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012-2016 CodeLibs Project and the Others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.codelibs.fess.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.codelibs.fess.unit.UnitFessTestCase;
+
+public class DocumentUtilTest extends UnitFessTestCase {
+
+    public void test_string() {
+        Map<String, Object> doc = new HashMap<>();
+
+        String expected = "1";
+        doc.put("key1", expected);
+        assertEquals(expected, DocumentUtil.getValue(doc, "key1", String.class));
+        assertEquals(Integer.parseInt(expected), DocumentUtil.getValue(doc, "key1", Integer.class).intValue());
+        assertEquals(Long.parseLong(expected), DocumentUtil.getValue(doc, "key1", Long.class).longValue());
+        assertEquals(Float.parseFloat(expected), DocumentUtil.getValue(doc, "key1", Float.class).floatValue());
+        assertEquals(Double.parseDouble(expected), DocumentUtil.getValue(doc, "key1", Double.class).doubleValue());
+
+        assertNull(DocumentUtil.getValue(doc, "key2", String.class));
+    }
+
+    public void test_integer() {
+        Map<String, Object> doc = new HashMap<>();
+
+        int expected3 = 999;
+        doc.put("key3", expected3);
+        assertEquals(expected3, DocumentUtil.getValue(doc, "key3", Integer.class));
+
+        doc.put("key9", new ArrayList<Integer>(Arrays.asList(777, 888, 999)));
+        assertEquals(777, DocumentUtil.getValue(doc, "key9", Integer.class));
+    }
+
+    public void test_date() {
+        Map<String, Object> doc = new HashMap<>();
+
+        Date expected4 = new Date();
+        doc.put("key4", expected4);
+        assertEquals(expected4, DocumentUtil.getValue(doc, "key4", Date.class));
+    }
+
+    public void test_long() {
+        Map<String, Object> doc = new HashMap<>();
+
+        long expected5 = 999999999999999999L;
+        doc.put("key5", expected5);
+        assertEquals(expected5, DocumentUtil.getValue(doc, "key5", Long.class).longValue());
+    }
+
+    public void test_double() {
+        Map<String, Object> doc = new HashMap<>();
+
+        double expected6 = 999.999;
+        doc.put("key6", expected6);
+        assertEquals(expected6, DocumentUtil.getValue(doc, "key6", Double.class));
+    }
+
+    public void test_float() {
+        Map<String, Object> doc = new HashMap<>();
+
+        float expected7 = 999.999f;
+        doc.put("key7", expected7);
+        assertEquals(expected7, DocumentUtil.getValue(doc, "key7", Float.class));
+    }
+
+    public void test_boolean() {
+        Map<String, Object> doc = new HashMap<>();
+
+        boolean expected8 = true;
+        doc.put("key8", expected8);
+        assertEquals(expected8, DocumentUtil.getValue(doc, "key8", Boolean.class).booleanValue());
+    }
+}