[haiku-depot-web] [haiku-depot-web-app] 4 new revisions pushed by haiku.li...@xxxxxxxxx on 2014-09-16 08:51 GMT

  • From: haiku-depot-web-app@xxxxxxxxxxxxxx
  • To: haiku-depot-web@xxxxxxxxxxxxx
  • Date: Tue, 16 Sep 2014 08:51:30 +0000

master moved from 358946777afb to 8ef5143b7e4d

4 new revisions:

Revision: 8a6c6b5f5e68
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Sep  9 11:49:40 2014 UTC
Log: initial support for read / write synchronization from / to LDAP for us...
https://code.google.com/p/haiku-depot-web-app/source/detail?r=8a6c6b5f5e68

Revision: 52c9f839987d
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Fri Sep 12 08:53:03 2014 UTC
Log: check for source and indicate when source is available when viewing a ...
https://code.google.com/p/haiku-depot-web-app/source/detail?r=52c9f839987d

Revision: 8ab5cf122712
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Mon Sep 15 10:09:53 2014 UTC
Log:      preparations for next version
https://code.google.com/p/haiku-depot-web-app/source/detail?r=8ab5cf122712

Revision: 8ef5143b7e4d
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Mon Sep 15 10:51:49 2014 UTC
Log:      resolve minor issue in viewing pkg version in jsp pages
https://code.google.com/p/haiku-depot-web-app/source/detail?r=8ef5143b7e4d

==============================================================================
Revision: 8a6c6b5f5e68
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Sep  9 11:49:40 2014 UTC
Log: initial support for read / write synchronization from / to LDAP for users

https://code.google.com/p/haiku-depot-web-app/source/detail?r=8a6c6b5f5e68

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/SynchronizeUsersRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/SynchronizeUsersResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/LdapUserUpdateListener.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/ldap/LdapConnectionPoolFactory.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/ldap/LdapConnectionPoolHolder.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/LdapException.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/LdapSynchronizeUsersService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/LocalLdapSynchronizeUsersService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/model/LdapPerson.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/model/LdapSynchronizeUsersJob.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/security/AuthenticationServiceIT.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/user/NoopLdapSynchronizeUsersService.java
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java
 /haikudepotserver-parent/pom.xml
 /haikudepotserver-rpm/src/main/etc/config__config.properties
 /haikudepotserver-webapp/pom.xml
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/UserRatingDerivationTriggerListener.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/UserOrchestrationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/LocalUserRatingDerivationService.java
 /haikudepotserver-webapp/src/main/resources/spring/application-context.xml
 /haikudepotserver-webapp/src/main/resources/spring/persistence.xml
/haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperations.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperationscontroller.js
 /haikudepotserver-webapp/src/test/resources/spring/test.xml

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/SynchronizeUsersRequest.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class SynchronizeUsersRequest {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/SynchronizeUsersResult.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.user;
+
+public class SynchronizeUsersResult {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/LdapUserUpdateListener.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.support.cayenne;
+
+import com.google.common.base.Preconditions;
+import org.apache.cayenne.LifecycleListener;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.reflect.LifecycleCallbackRegistry;
+import org.haikuos.haikudepotserver.dataobjects.User;
+import org.haikuos.haikudepotserver.user.LdapException;
+import org.haikuos.haikudepotserver.user.UserOrchestrationService;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+
+/**
+ * <p>This will attempt to listen to changes to a user and then after those changes have been
+ * persisted, to look them up and to relay those into an LDAP server.</p>
+ */
+
+public class LdapUserUpdateListener implements LifecycleListener {
+
+    @Resource
+    ServerRuntime serverRuntime;
+
+    @Resource
+    UserOrchestrationService userOrchestrationService;
+
+    @PostConstruct
+    public void init() {
+ LifecycleCallbackRegistry callbackRegistry = serverRuntime.getDataDomain().getEntityResolver().getCallbackRegistry();
+        callbackRegistry.addListener(User.class, this);
+    }
+
+    private void triggerUpdateUser(Object entity) {
+        Preconditions.checkNotNull(entity);
+        ObjectContext context = serverRuntime.getContext();
+ User user = User.getByObjectId(context, ((User) entity).getObjectId());
+
+        try {
+            userOrchestrationService.ldapUpdateUser(context, user);
+        }
+        catch(LdapException le) {
+ throw new RuntimeException("unable to ldap update user; " + user.toString(), le);
+        }
+    }
+
+    @Override
+    public void postAdd(Object entity) {
+    }
+
+    @Override
+    public void prePersist(Object entity) {
+    }
+
+    @Override
+    public void postPersist(Object entity) {
+        triggerUpdateUser(entity);
+    }
+
+    @Override
+    public void preRemove(Object entity) {
+    }
+
+    @Override
+    public void postRemove(Object entity) {
+    }
+
+    @Override
+    public void preUpdate(Object entity) {
+    }
+
+    @Override
+    public void postUpdate(Object entity) {
+        triggerUpdateUser(entity);
+    }
+
+    @Override
+    public void postLoad(Object entity) {
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/ldap/LdapConnectionPoolFactory.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.support.ldap;
+
+import com.google.common.base.Strings;
+import org.apache.directory.ldap.client.api.LdapConnectionConfig;
+import org.apache.directory.ldap.client.api.LdapConnectionPool;
+import org.apache.directory.ldap.client.api.PoolableLdapConnectionFactory;
+import org.springframework.beans.factory.FactoryBean;
+
+/**
+ * <p>This factory will create a connection pool for LDAP connections. It will produce a holder object that + * either returns an {@link org.apache.directory.ldap.client.api.LdapConnectionPool} or will return null
+ * depending on the configuration of the LDAP server.</p>
+ */
+
+public class LdapConnectionPoolFactory implements FactoryBean<LdapConnectionPoolHolder> {
+
+    private String host;
+    private Integer port;
+    private String userDn;
+    private String password;
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public void setPort(Integer port) {
+        this.port = port;
+    }
+
+    public void setUserDn(String userDn) {
+        this.userDn = userDn;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    private boolean isConfigured() {
+        return !Strings.isNullOrEmpty(host);
+    }
+
+    @Override
+    public LdapConnectionPoolHolder getObject() throws Exception {
+
+        if(isConfigured()) {
+
+            LdapConnectionConfig config = new LdapConnectionConfig();
+            config.setLdapHost(host);
+            config.setLdapPort(null == port ? 398 : port.intValue());
+            config.setName(userDn);
+            config.setCredentials(password);
+
+ PoolableLdapConnectionFactory factory = new PoolableLdapConnectionFactory(config);
+            LdapConnectionPool pool = new LdapConnectionPool( factory );
+            pool.setTestOnBorrow( true );
+
+            return new LdapConnectionPoolHolder(pool);
+        }
+
+        return new LdapConnectionPoolHolder();
+    }
+
+    @Override
+    public Class<?> getObjectType() {
+        return LdapConnectionPoolHolder.class;
+    }
+
+    @Override
+    public boolean isSingleton() {
+        return true;
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/ldap/LdapConnectionPoolHolder.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.support.ldap;
+
+import org.apache.directory.ldap.client.api.LdapConnectionPool;
+
+/**
+ * <p>This 'holder' can either hold an {@link org.apache.directory.ldap.client.api.LdapConnectionPool} or it
+ * can hold null.</p>
+ */
+
+public class LdapConnectionPoolHolder {
+
+    private LdapConnectionPool connectionPool;
+
+    public LdapConnectionPoolHolder() {
+    }
+
+    public LdapConnectionPoolHolder(LdapConnectionPool connectionPool) {
+        this.connectionPool = connectionPool;
+    }
+
+    public LdapConnectionPool get() {
+        return connectionPool;
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/LdapException.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.user;
+
+public class LdapException extends Exception {
+
+    public LdapException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/LdapSynchronizeUsersService.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.user;
+
+import org.haikuos.haikudepotserver.user.model.LdapSynchronizeUsersJob;
+
+/**
+ * <p>Implementations of this service are able to synchronize user data into an LDAP directory.</p>
+ */
+
+public interface LdapSynchronizeUsersService {
+
+    public void submit(final LdapSynchronizeUsersJob job);
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/LocalLdapSynchronizeUsersService.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.user;
+
+import com.google.common.base.Preconditions;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.haikuos.haikudepotserver.support.AbstractLocalBackgroundProcessingService;
+import org.haikuos.haikudepotserver.user.model.LdapSynchronizeUsersJob;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Resource;
+
+/**
+ * <p>This background service will asynchronously go through the users and will relay the data for the users
+ * into an LDAP server.</p>
+ */
+
+public class LocalLdapSynchronizeUsersService
+        extends AbstractLocalBackgroundProcessingService
+        implements LdapSynchronizeUsersService {
+
+ protected static Logger LOGGER = LoggerFactory.getLogger(LocalLdapSynchronizeUsersService.class);
+
+    @Resource
+    ServerRuntime serverRuntime;
+
+    @Resource
+    UserOrchestrationService userOrchestrationService;
+
+    @Override
+    public void submit(LdapSynchronizeUsersJob job) {
+        Preconditions.checkNotNull(job);
+ Preconditions.checkState(null != executor, "the service is not running, but a job is being submitted");
+        executor.submit(new LdapUpdateUsersJobRunnable(this, job));
+        LOGGER.info("did submit job to update users in ldap directory");
+    }
+
+    protected void run(LdapSynchronizeUsersJob job) {
+        Preconditions.checkNotNull(job);
+
+        try {
+ userOrchestrationService.ldapSynchronizeAllUsers(serverRuntime.getContext()); + userOrchestrationService.ldapRemoveNonExistentUsers(serverRuntime.getContext());
+        }
+        catch(LdapException le) {
+            LOGGER.error("unable to ldap synchronize users", le);
+        }
+    }
+
+    public static class LdapUpdateUsersJobRunnable implements Runnable {
+
+        private LdapSynchronizeUsersJob job;
+
+        private LocalLdapSynchronizeUsersService service;
+
+        public LdapUpdateUsersJobRunnable(
+                LocalLdapSynchronizeUsersService service,
+                LdapSynchronizeUsersJob job) {
+            Preconditions.checkNotNull(service);
+            Preconditions.checkNotNull(job);
+            this.service = service;
+            this.job = job;
+        }
+
+        public LdapSynchronizeUsersJob getJob() {
+            return job;
+        }
+
+        @Override
+        public void run() {
+            service.run(job);
+        }
+
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/model/LdapPerson.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.user.model;
+
+import org.apache.directory.api.ldap.model.name.Dn;
+
+/**
+ * <p>Provides an easily used model object to represent a person as stored in the LDAP directory.</p>
+ */
+
+public class LdapPerson {
+
+    /**
+ * <p>This is the distinguished name that uniquely identifies the user.</p>
+     */
+
+    private Dn dn;
+
+    private String cn;
+    private String mail;
+    private String sn;
+    private String uid;
+    private String userPassword;
+
+    public Dn getDn() {
+        return dn;
+    }
+
+    public void setDn(Dn dn) {
+        this.dn = dn;
+    }
+
+    public String getCn() {
+        return cn;
+    }
+
+    public void setCn(String cn) {
+        this.cn = cn;
+    }
+
+    public String getMail() {
+        return mail;
+    }
+
+    public void setMail(String mail) {
+        this.mail = mail;
+    }
+
+    public String getSn() {
+        return sn;
+    }
+
+    public void setSn(String sn) {
+        this.sn = sn;
+    }
+
+    public String getUid() {
+        return uid;
+    }
+
+    public void setUid(String uid) {
+        this.uid = uid;
+    }
+
+    public String getUserPassword() {
+        return userPassword;
+    }
+
+    public void setUserPassword(String userPassword) {
+        this.userPassword = userPassword;
+    }
+
+    @Override
+    public String toString() {
+        return "ldap-person; " + (null!=getDn() ? getDn() : "???");
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/model/LdapSynchronizeUsersJob.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.user.model;
+
+/**
+ * <p>This object models a job that can be submitted to synchronize the user data into an LDAP directory.</p>
+ */
+
+public class LdapSynchronizeUsersJob {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/security/AuthenticationServiceIT.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.security;
+
+import org.fest.assertions.Assertions;
+import org.haikuos.haikudepotserver.AbstractIntegrationTest;
+import org.haikuos.haikudepotserver.dataobjects.User;
+import org.junit.Test;
+
+import javax.annotation.Resource;
+
+public class AuthenticationServiceIT extends AbstractIntegrationTest {
+
+    @Resource
+    AuthenticationService authenticationService;
+
+    @Test
+    public void testHashPassword() {
+        User user = new User();
+        user.setPasswordSalt("cad3422ea02761f8");
+ String passwordHash = authenticationService.hashPassword(user,"p4mphl3t"); + Assertions.assertThat(passwordHash).isEqualTo("b9c4717bc5c6d16f2be9e967ab0c752f8ac2084f95781989f39cf8736e2edeef");
+    }
+
+    @Test
+    public void testHashPassword_2() {
+        User user = new User();
+        user.setPasswordSalt("66a9b264bf730ac2");
+ String passwordHash = authenticationService.hashPassword(user,"Pa55word0"); + Assertions.assertThat(passwordHash).isEqualTo("d439da8f2ec8c7aa3d0c9c2a1dd7cd6dcbf8b4435f9e288cc1a6f7b77d47361e");
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/user/NoopLdapSynchronizeUsersService.java Tue Sep 9 11:49:40 2014 UTC
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.user;
+
+import org.haikuos.haikudepotserver.user.model.LdapSynchronizeUsersJob;
+
+/**
+ * <P>No-operation implementation of this service for testing purposes.</P>
+ */
+
+public class NoopLdapSynchronizeUsersService implements LdapSynchronizeUsersService {
+
+    @Override
+    public void submit(LdapSynchronizeUsersJob job) {
+    }
+
+}
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java Tue Jul 8 09:00:54 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/UserApi.java Tue Sep 9 11:49:40 2014 UTC
@@ -18,6 +18,12 @@
 @JsonRpcService("/api/v1/user")
 public interface UserApi {

+    /**
+ * <P>This method will synchronize user data with external systems; such as LDAP servers.</P>
+     */
+
+ SynchronizeUsersResult synchronizeUsers(SynchronizeUsersRequest synchronizeUsersRequest);
+
     /**
* <p>This method will update the user based on the data in the request. Only the data which is included
      * in the filter will be updated.</p>
=======================================
--- /haikudepotserver-parent/pom.xml    Thu Aug 14 11:03:33 2014 UTC
+++ /haikudepotserver-parent/pom.xml    Tue Sep  9 11:49:40 2014 UTC
@@ -139,7 +139,7 @@
                 <version>1.0</version>
             </dependency>

-            <!-- DATABASE / PERSISTENCE -->
+            <!-- DATABASE / PERSISTENCE / DIRECTORY -->
             <dependency>
                 <groupId>com.googlecode.flyway</groupId>
                 <artifactId>flyway-core</artifactId>
@@ -150,6 +150,11 @@
                 <artifactId>cayenne-server</artifactId>
                 <version>3.1RC1</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.directory.api</groupId>
+                <artifactId>api-all</artifactId>
+                <version>1.0.0-M24</version>
+            </dependency>

             <!-- APPLICATION SERVER -->
             <dependency>
=======================================
--- /haikudepotserver-rpm/src/main/etc/config__config.properties Thu Aug 14 11:03:33 2014 UTC +++ /haikudepotserver-rpm/src/main/etc/config__config.properties Tue Sep 9 11:49:40 2014 UTC
@@ -86,3 +86,19 @@
 #smtp.auth=false
 #smtp.starttls=false
 email.from=noreply@xxxxxxxxxxxx
+
+# -------------------------------------------
+# LDAP related
+# If the LDAP setting are commented-out then the system will operate as if
+# there were no LDAP server and will not attempt to synchronize user data
+# to the LDAP directory server.
+
+#ldap.host=localhost
+#ldap.port=10389
+#ldap.user.dn=cn=haikudepotserver,ou=services,dc=haiku-os,dc=org
+#ldap.password=Pa55word2
+
+# This setting provides the location in the LDAP directory where the users
+# of the system are stored.
+
+#ldap.people.dn=ou=people,dc=haiku-os,dc=org
=======================================
--- /haikudepotserver-webapp/pom.xml    Thu Aug 14 11:03:33 2014 UTC
+++ /haikudepotserver-webapp/pom.xml    Tue Sep  9 11:49:40 2014 UTC
@@ -93,7 +93,7 @@
             <artifactId>rome</artifactId>
         </dependency>

-        <!-- DATABASE / PERSISTENCE -->
+        <!-- DATABASE / PERSISTENCE / DIRECTORY -->
         <dependency>
             <groupId>com.googlecode.flyway</groupId>
             <artifactId>flyway-core</artifactId>
@@ -102,6 +102,10 @@
             <groupId>org.apache.cayenne</groupId>
             <artifactId>cayenne-server</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.directory.api</groupId>
+            <artifactId>api-all</artifactId>
+        </dependency>

         <!-- APPLICATION SERVER -->
         <dependency>
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Sun Aug 3 11:11:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Tue Sep 9 11:49:40 2014 UTC
@@ -25,7 +25,9 @@
 import org.haikuos.haikudepotserver.security.AuthenticationService;
 import org.haikuos.haikudepotserver.security.AuthorizationService;
 import org.haikuos.haikudepotserver.security.model.Permission;
+import org.haikuos.haikudepotserver.user.LdapSynchronizeUsersService;
 import org.haikuos.haikudepotserver.user.UserOrchestrationService;
+import org.haikuos.haikudepotserver.user.model.LdapSynchronizeUsersJob;
 import org.haikuos.haikudepotserver.user.model.UserSearchSpecification;
 import org.haikuos.haikudepotserver.userrating.UserRatingDerivationService;
import org.haikuos.haikudepotserver.userrating.UserRatingOrchestrationService;
@@ -66,6 +68,28 @@

     @Resource
     PasswordResetOrchestrationService passwordResetOrchestrationService;
+
+    @Resource
+    LdapSynchronizeUsersService ldapUpdateUsersService;
+
+    @Override
+ public SynchronizeUsersResult synchronizeUsers(SynchronizeUsersRequest synchronizeUsersRequest) {
+       Preconditions.checkNotNull(synchronizeUsersRequest);
+
+        final ObjectContext context = serverRuntime.getContext();
+
+        if(!authorizationService.check(
+                context,
+                tryObtainAuthenticatedUser(context).orNull(),
+                null,
+                Permission.USER_SYNCHRONIZE)) {
+            throw new AuthorizationFailureException();
+        }
+
+        ldapUpdateUsersService.submit(new LdapSynchronizeUsersJob());
+
+        return new SynchronizeUsersResult();
+    }

     @Override
public UpdateUserResult updateUser(UpdateUserRequest updateUserRequest) throws ObjectNotFoundException {
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java Sat Jul 26 04:02:29 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/User.java Tue Sep 9 11:49:40 2014 UTC
@@ -12,6 +12,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.hash.Hashing;
+import com.google.common.io.BaseEncoding;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.exp.ExpressionFactory;
@@ -31,9 +32,11 @@

 public class User extends _User implements CreateAndModifyTimestamped {

+    public final static String NICKNAME_ROOT = "root";
+
public final static Pattern NICKNAME_PATTERN = Pattern.compile("^[a-z0-9]{4,16}$"); public final static Pattern PASSWORDHASH_PATTERN = Pattern.compile("^[a-f0-9]{64}$"); - public final static Pattern PASSWORDSALT_PATTERN = Pattern.compile("^[a-f0-9]{64}$"); + public final static Pattern PASSWORDSALT_PATTERN = Pattern.compile("^[a-f0-9]{10,32}$");

public static List<User> findByEmail(ObjectContext context, String email) {
         Preconditions.checkNotNull(context);
@@ -157,13 +160,46 @@
                 }
         ));
     }
+
+    /**
+ * <p>The LDAP entry for a person can have a 'userPassword' attribute. This has the format + * {SSHA-256}&lt;base64data&gt;. The base 64 data, decoded to bytes contains 20 bytes of
+     * hash and the rest is salt.</p>
+     *
+ * <p>This method will convert the information stored in this user over into a format suitable
+     * for storage in this format for LDAP.</p>
+     *
+     * <p>SSHA stands for salted SHA hash.</p>
+     *
+     * <p>RFC-2307</p>
+     */
+
+    public String toLdapUserPasswordAttributeValue() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{SSHA-256}");
+
+ byte[] hash = BaseEncoding.base16().decode(getPasswordHash().toUpperCase());
+
+        if(32 != hash.length) {
+ throw new IllegalStateException("the password hash should be 20 bytes long");
+        }
+
+ byte[] salt = BaseEncoding.base16().decode(getPasswordSalt().toUpperCase());
+
+        byte[] hashAndSalt = new byte[hash.length + salt.length];
+        System.arraycopy(hash, 0, hashAndSalt, 0, hash.length);
+        System.arraycopy(salt, 0, hashAndSalt, hash.length, salt.length);
+
+        return "{SSHA-256}" + BaseEncoding.base64().encode(hashAndSalt);
+    }

     /**
      * <p>This method will configure a random salt value.</p>
      */

     public void setPasswordSalt() {
- setPasswordSalt(Hashing.sha256().hashUnencodedChars(UUID.randomUUID().toString()).toString()); + String randomHash = Hashing.sha256().hashUnencodedChars(UUID.randomUUID().toString()).toString(); + setPasswordSalt(randomHash.substring(0,16)); // LDAP server doesn't seem to like very long salts
     }

     public Boolean getDerivedCanManageUsers() {
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationService.java Sun Aug 3 11:11:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthenticationService.java Tue Sep 9 11:49:40 2014 UTC
@@ -11,7 +11,7 @@
 import com.google.common.base.Strings;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
-import com.google.common.hash.Hashing;
+import com.google.common.io.BaseEncoding;
 import com.nimbusds.jose.*;
 import com.nimbusds.jose.crypto.MACSigner;
 import com.nimbusds.jose.crypto.MACVerifier;
@@ -31,6 +31,7 @@

 import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
+import java.security.MessageDigest;
 import java.text.ParseException;
 import java.util.List;
 import java.util.UUID;
@@ -188,7 +189,20 @@
      */

     public String hashPassword(User user, String passwordClear) {
- return Hashing.sha256().hashUnencodedChars(user.getPasswordSalt() + passwordClear).toString(); + byte[] saltBytes = BaseEncoding.base16().decode(user.getPasswordSalt().toUpperCase());
+
+        MessageDigest sha;
+        try {
+            sha = MessageDigest.getInstance("SHA-256");
+            sha.update(passwordClear.getBytes(Charsets.UTF_8));
+        }
+        catch (java.security.NoSuchAlgorithmException e) {
+ throw new IllegalStateException("no SHA-256 crypt algorithm available",e);
+        }
+
+        sha.update(saltBytes);
+
+        return BaseEncoding.base16().encode(sha.digest()).toLowerCase();
     }

     private int countMatches(String s, CharToBooleanFunction fn) {
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Tue Aug 19 11:01:57 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Tue Sep 9 11:49:40 2014 UTC
@@ -192,6 +192,7 @@
                         null!=authenticatedUser
&& (authenticatedUser.getIsRoot() || authenticatedUser.equals(target));

+            case USER_SYNCHRONIZE:
             case USER_LIST:
return null!=authenticatedUser && authenticatedUser.getIsRoot();

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Tue Aug 19 11:01:57 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Tue Sep 9 11:49:40 2014 UTC
@@ -18,6 +18,7 @@
     USER_EDIT(TargetType.USER),
     USER_CHANGEPASSWORD(TargetType.USER),
     USER_LIST(null),
+    USER_SYNCHRONIZE(null),

     AUTHORIZATION_CONFIGURE(null),

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/UserRatingDerivationTriggerListener.java Mon Jun 23 10:01:05 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/cayenne/UserRatingDerivationTriggerListener.java Tue Sep 9 11:49:40 2014 UTC
@@ -76,4 +76,5 @@
     @Override
     public void postLoad(Object entity) {
     }
+
 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/UserOrchestrationService.java Sun Aug 3 11:11:48 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/user/UserOrchestrationService.java Tue Sep 9 11:49:40 2014 UTC
@@ -5,19 +5,31 @@

 package org.haikuos.haikudepotserver.user;

-import com.google.common.base.Joiner;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
+import com.google.common.base.*;
 import com.google.common.collect.Lists;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.query.EJBQLQuery;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.directory.api.ldap.model.cursor.CursorException;
+import org.apache.directory.api.ldap.model.cursor.EntryCursor;
+import org.apache.directory.api.ldap.model.entry.*;
+import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.api.ldap.model.message.ModifyRequest;
+import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
+import org.apache.directory.api.ldap.model.message.SearchScope;
+import org.apache.directory.ldap.client.api.LdapConnection;
 import org.haikuos.haikudepotserver.dataobjects.User;
 import org.haikuos.haikudepotserver.support.cayenne.LikeHelper;
+import org.haikuos.haikudepotserver.support.ldap.LdapConnectionPoolHolder;
+import org.haikuos.haikudepotserver.user.model.LdapPerson;
 import org.haikuos.haikudepotserver.user.model.UserSearchSpecification;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;

+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
 import java.util.List;

 /**
@@ -28,9 +40,337 @@
 public class UserOrchestrationService {

protected static Logger LOGGER = LoggerFactory.getLogger(UserOrchestrationService.class);
+
+    /**
+ * <p>Users are processed in batches to avoid excessive memory or IO consumption at once. This constant
+     * determines the size of the batches.</p>
+     */
+
+    private final static int BATCH_SIZE_USER = 20;
+
+ private final static String LDAP_ATTRIBUTE_KEY_CN = "cn"; // common name
+    private final static String LDAP_ATTRIBUTE_KEY_SN = "sn"; // surname
+    private final static String LDAP_ATTRIBUTE_KEY_MAIL = "mail"; // mail
+    private final static String LDAP_ATTRIBUTE_KEY_UID = "uid"; // uid
+ private final static String LDAP_ATTRIBUTE_KEY_USERPASSWORD = "userPassword"; // contains a hash + private final static String LDAP_ATTRIBUTE_KEY_OBJECTCLASS = "ObjectClass";
+
+ private final static String LDAP_ATTRIBUTE_VALUE_OBJECTCLASS_INETORGPERSON = "inetOrgPerson";
+
+    @Resource
+    LdapConnectionPoolHolder ldapConnectionPoolHolder;
+
+    @Value("${ldap.people.dn:}")
+    String ldapPeopleDn;
+
+    @PostConstruct
+    public void init() {
+        if(null!=ldapConnectionPoolHolder.get()) {
+            if(Strings.isNullOrEmpty(ldapPeopleDn)) {
+ throw new IllegalStateException("the ldap people DN must be configured if LDAP is to be used to back users; data");
+            }
+        }
+    }

     // ------------------------------
-    // SEARCH
+    // LDAP
+
+    /**
+ * <p>LDAP may or may not be configured. This method will return true if the system is
+     * configured for interaction with LDAP.</p>
+     */
+
+    public boolean isLdapConfigured() {
+        return null!=ldapConnectionPoolHolder.get();
+    }
+
+    private LdapPerson createPerson(Entry entry) throws LdapException {
+
+        Attribute mailA = entry.get(LDAP_ATTRIBUTE_KEY_MAIL);
+        Attribute uidA = entry.get(LDAP_ATTRIBUTE_KEY_UID);
+ Attribute userPasswordA = entry.get(LDAP_ATTRIBUTE_KEY_USERPASSWORD);
+
+        LdapPerson ldapPerson = new LdapPerson();
+        ldapPerson.setDn(entry.getDn());
+        ldapPerson.setMail(null == mailA ? null : mailA.getString());
+        ldapPerson.setCn(entry.get(LDAP_ATTRIBUTE_KEY_CN).getString());
+        ldapPerson.setSn(entry.get(LDAP_ATTRIBUTE_KEY_SN).getString());
+        ldapPerson.setUid(null == uidA ? null : uidA.getString());
+ ldapPerson.setUserPassword(null == userPasswordA ? null : new String(userPasswordA.getBytes(), Charsets.US_ASCII));
+
+        return ldapPerson;
+    }
+
+ private Optional<LdapPerson> ldapFindPerson(LdapConnection ldapConnection, final String nickname) throws org.haikuos.haikudepotserver.user.LdapException {
+        Preconditions.checkNotNull(ldapConnection);
+        Preconditions.checkNotNull(nickname);
+ Preconditions.checkState(User.NICKNAME_PATTERN.matcher(nickname).matches(),"the nickname is illegal");
+
+        LdapPerson ldapPerson = null;
+
+        try {
+            EntryCursor cursor = ldapConnection.search(
+ String.format("%s=%s, %s", LDAP_ATTRIBUTE_KEY_CN, nickname, ldapPeopleDn), + String.format("(%s=%s)", LDAP_ATTRIBUTE_KEY_OBJECTCLASS, LDAP_ATTRIBUTE_VALUE_OBJECTCLASS_INETORGPERSON),
+                    SearchScope.OBJECT);
+
+            if (cursor.next()) {
+                ldapPerson = createPerson(cursor.get());
+            }
+
+            if (cursor.next()) {
+ throw new IllegalStateException("found two matches for the user; " + nickname);
+            }
+        }
+        catch(LdapException le) {
+ throw new org.haikuos.haikudepotserver.user.LdapException("an error arose finding the user in the ldap system; " + nickname, le);
+        }
+        catch(CursorException ce) {
+ throw new org.haikuos.haikudepotserver.user.LdapException("an error arose finding the user in the ldap system; " + nickname, ce);
+        }
+
+        return Optional.fromNullable(ldapPerson);
+    }
+
+    private void accumulateModification(
+            List<Modification> modifications,
+            String attributeName,
+            String existingValue,
+            String newValue) {
+ Modification modification = createModification(attributeName,existingValue,newValue);
+
+        if(null!=modification) {
+            modifications.add(modification);
+        }
+    }
+
+    private Modification createModification(
+            String attributeName,
+            String existingValue,
+            String newValue) {
+
+        Preconditions.checkState(!Strings.isNullOrEmpty(attributeName));
+
+        boolean existingN = !Strings.isNullOrEmpty(existingValue);
+        boolean newN = !Strings.isNullOrEmpty(newValue);
+
+        if(existingN != newN) {
+
+            if(existingN) {
+ return new DefaultModification(ModificationOperation.REMOVE_ATTRIBUTE, attributeName, existingValue);
+            }
+            else {
+ return new DefaultModification(ModificationOperation.ADD_ATTRIBUTE, attributeName, newValue);
+            }
+
+        }
+        else {
+            if(newN && !existingValue.equals(newValue)) {
+ return new DefaultModification(ModificationOperation.REPLACE_ATTRIBUTE, attributeName, newValue);
+            }
+            else {
+ LOGGER.trace("new and old attribute values are the same; will not need to replace them");
+            }
+        }
+
+        return null;
+    }
+
+    /**
+ * <p>This method will update the supplied user in the supplied object context with the LDAP server.</p>
+     */
+
+ public void ldapUpdateUser(ObjectContext context, User user) throws org.haikuos.haikudepotserver.user.LdapException {
+        Preconditions.checkNotNull(context);
+        Preconditions.checkNotNull(user);
+
+        if(user.getNickname().equals(User.NICKNAME_ROOT)) {
+            LOGGER.info("will not update the root user to ldap");
+        }
+        else {
+            if (isLdapConfigured()) {
+
+                LdapConnection ldapConnection = null;
+
+                try {
+ ldapConnection = ldapConnectionPoolHolder.get().getConnection(); + Optional<LdapPerson> ldapPersonOptional = ldapFindPerson(ldapConnection, user.getNickname());
+
+                    if (!user.getActive()) {
+
+                        if (ldapPersonOptional.isPresent()) {
+ ldapConnection.delete(ldapPersonOptional.get().getDn()); + LOGGER.info("did delete ldap directory data for user; " + user.toString());
+                        } else {
+ LOGGER.debug("no need take any action for inactive user {}; there is no data present in the ldap directory", user.toString());
+                        }
+
+                    } else {
+
+                        if (ldapPersonOptional.isPresent()) {
+
+ ModifyRequest modifyRequest = new ModifyRequestImpl(); + modifyRequest.setName(ldapPersonOptional.get().getDn());
+
+ List<Modification> modifications = Lists.newArrayList();
+
+ accumulateModification(modifications, LDAP_ATTRIBUTE_KEY_CN, ldapPersonOptional.get().getCn(), user.getNickname()); + accumulateModification(modifications, LDAP_ATTRIBUTE_KEY_SN, ldapPersonOptional.get().getSn(), user.getNickname()); + accumulateModification(modifications, LDAP_ATTRIBUTE_KEY_UID, ldapPersonOptional.get().getUid(), user.getNickname()); + accumulateModification(modifications, LDAP_ATTRIBUTE_KEY_USERPASSWORD, ldapPersonOptional.get().getUserPassword(), user.toLdapUserPasswordAttributeValue()); + accumulateModification(modifications, LDAP_ATTRIBUTE_KEY_MAIL, ldapPersonOptional.get().getMail(), user.getEmail());
+
+                            if (!modifications.isEmpty()) {
+                                ldapConnection.modify(
+                                        ldapPersonOptional.get().getDn(),
+ modifications.toArray(new Modification[modifications.size()])
+                                );
+ LOGGER.info("did update ldap directory entry for; {}", user.toString());
+                            }
+
+                        } else {
+
+ DefaultEntry defaultEntry = new DefaultEntry(String.format("%s=%s,%s", LDAP_ATTRIBUTE_KEY_CN, user.getNickname(), ldapPeopleDn));
+
+ defaultEntry.add(new DefaultAttribute(LDAP_ATTRIBUTE_KEY_OBJECTCLASS, LDAP_ATTRIBUTE_VALUE_OBJECTCLASS_INETORGPERSON));
+
+ defaultEntry.add(new DefaultAttribute(LDAP_ATTRIBUTE_KEY_CN, user.getNickname())); + defaultEntry.add(new DefaultAttribute(LDAP_ATTRIBUTE_KEY_SN, user.getNickname())); + defaultEntry.add(new DefaultAttribute(LDAP_ATTRIBUTE_KEY_UID, user.getNickname())); + defaultEntry.add(new DefaultAttribute(LDAP_ATTRIBUTE_KEY_USERPASSWORD, user.toLdapUserPasswordAttributeValue()));
+
+                            if (!Strings.isNullOrEmpty(user.getEmail())) {
+ defaultEntry.add(new DefaultAttribute(LDAP_ATTRIBUTE_KEY_MAIL, user.getEmail()));
+                            }
+
+                            ldapConnection.add(defaultEntry);
+ LOGGER.info("did create ldap directory entry for; {}", user.toString());
+                        }
+
+                    }
+
+                } catch (Exception le) {
+ throw new org.haikuos.haikudepotserver.user.LdapException("unable to update user to ldap directory; " + user.getNickname(), le); // TODO; better exception handling
+                } finally {
+                    if (null != ldapConnection) {
+                        try {
+ ldapConnectionPoolHolder.get().releaseConnection(ldapConnection);
+                        } catch (Exception e) {
+                            // ignore
+                        }
+                    }
+                }
+
+            } else {
+ LOGGER.debug("ldap is not configured --> did not update ldap for user {}", user.toString());
+            }
+        }
+
+    }
+
+    /**
+ * <p>This method will go through all of the users in the database and will synchronize them all with the + * LDAP directory; adding, modifying and deleting entries as necessary.</p>
+     */
+
+ public void ldapSynchronizeAllUsers(ObjectContext context) throws org.haikuos.haikudepotserver.user.LdapException {
+        Preconditions.checkNotNull(context);
+
+        if(isLdapConfigured()) {
+
+            LOGGER.info("will update all users to ldap");
+
+            SelectQuery query = new SelectQuery(User.class);
+            query.setFetchLimit(BATCH_SIZE_USER);
+            query.setFetchOffset(0);
+            Integer lastCount = null;
+            int count = 0;
+
+            while (null == lastCount || lastCount >= BATCH_SIZE_USER) {
+
+                if (null != lastCount) {
+ query.setFetchOffset(query.getFetchOffset() + BATCH_SIZE_USER);
+                }
+
+ @SuppressWarnings("unchecked") List<User> userBatch = context.performQuery(query);
+                lastCount = userBatch.size();
+
+                for (User user : userBatch) {
+                    ldapUpdateUser(context, user);
+                }
+
+                count += lastCount;
+            }
+
+            LOGGER.info("did update {} users to ldap", count);
+
+        }
+        else {
+ LOGGER.info("ldap is not configured --> will not update all users to ldap");
+        }
+    }
+
+    /**
+ * <p>This method will search the LDAP directory at the root for people and will find all of the users. If there + * exists some users who do not exist in the database then those users will be deleted from the LDAP server.</p>
+     */
+
+ // it is not clear if this will be necessary, but is probably a good idea to avoid people adding spurious data
+    // into the HDS LDAP directory.
+
+ public void ldapRemoveNonExistentUsers(ObjectContext context) throws org.haikuos.haikudepotserver.user.LdapException {
+
+        Preconditions.checkNotNull(context);
+
+        if(isLdapConfigured()) {
+
+            LdapConnection ldapConnection = null;
+
+            try {
+ ldapConnection = ldapConnectionPoolHolder.get().getConnection();
+
+                EntryCursor cursor = ldapConnection.search(
+                        ldapPeopleDn,
+ String.format("(%s=%s)", LDAP_ATTRIBUTE_KEY_OBJECTCLASS, LDAP_ATTRIBUTE_VALUE_OBJECTCLASS_INETORGPERSON),
+                        SearchScope.ONELEVEL);
+
+ // could be more efficient, but probably does not matter at this stage.
+
+                while(cursor.next()) {
+                    LdapPerson person = createPerson(cursor.get());
+ Optional<User> userOptional = User.getByNickname(context, person.getCn());
+
+ if(!userOptional.isPresent() | | !userOptional.get().getActive()) { + LOGGER.info("will delete ldap directory entry as no active user can be found for; {}", person.getCn());
+                        ldapConnection.delete(person.getDn());
+ LOGGER.info("did delete ldap directory entry for; {}", person.getCn());
+                    }
+                    else {
+ LOGGER.trace("have found active user for person {}; will not remove", person.getCn());
+                    }
+
+                }
+
+            } catch (Exception le) {
+ throw new org.haikuos.haikudepotserver.user.LdapException("unable to remove non-existent users from the ldap directory", le); // TODO; better exception handling
+            } finally {
+                if (null != ldapConnection) {
+                    try {
+ ldapConnectionPoolHolder.get().releaseConnection(ldapConnection);
+                    } catch (Exception e) {
+                        // ignore
+                    }
+                }
+            }
+        }
+        else {
+ LOGGER.info("ldap is not configured --> will not remove non-existent users from ldap directory");
+        }
+
+    }
+
+    // ------------------------------
+    // DATABASE SEARCH

     private String prepareWhereClause(
             List<Object> parameterAccumulator,
@@ -71,6 +411,7 @@
      * <p>Undertakes a search for users.</p>
      */

+    @SuppressWarnings("unchecked")
     public List<User> search(
             ObjectContext context,
             UserSearchSpecification searchSpecification) {
@@ -97,6 +438,7 @@
         query.setFetchOffset(searchSpecification.getOffset());
         query.setFetchLimit(searchSpecification.getLimit());

+        //noinspection unchecked
         return (List<User>) context.performQuery(query);
     }

@@ -126,12 +468,12 @@
             query.setParameter(i+1, parameters.get(i));
         }

-        List<Long> result = (List<Long>) context.performQuery(query);
+ @SuppressWarnings("unchecked") List<Long> result = (List<Long>) context.performQuery(query);

         switch(result.size()) {

             case 1:
-                return result.get(0).longValue();
+                return result.get(0);

             default:
throw new IllegalStateException("the result should have contained a single long result");
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/LocalUserRatingDerivationService.java Tue Aug 19 11:01:57 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/userrating/LocalUserRatingDerivationService.java Tue Sep 9 11:49:40 2014 UTC
@@ -1,3 +1,8 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
 package org.haikuos.haikudepotserver.userrating;

 import com.google.common.base.Preconditions;
@@ -26,7 +31,7 @@
         Preconditions.checkNotNull(job);
Preconditions.checkState(null!=executor, "the service is not running, but a job is being submitted");
         executor.submit(new UserRatingDerivationJobRunnable(this, job));
- LOGGER.info("have submitted job to derive user rating; {}", job.toString()); + LOGGER.info("did submit job to derive user rating; {}", job.toString());
     }

     protected void run(UserRatingDerivationJob job) {
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/application-context.xml Sun Aug 3 11:11:48 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/application-context.xml Tue Sep 9 11:49:40 2014 UTC
@@ -25,6 +25,25 @@

<bean class="org.haikuos.haikudepotserver.support.cayenne.UserRatingDerivationTriggerListener"/>

+    <!-- LDAP -->
+
+    <!--
+    Processes requests to synchronize the HDS database into LDAP
+    -->
+
+    <bean
+ class="org.haikuos.haikudepotserver.user.LocalLdapSynchronizeUsersService"
+            init-method="startAsyncAndAwaitRunning"
+            destroy-method="stopAsyncAndAwaitTerminated">
+    </bean>
+
+    <!--
+ Picks up on Cayenne ORM events around user changes so that LDAP can (optionally) be
+    updated with changes.
+    -->
+
+ <bean class="org.haikuos.haikudepotserver.support.cayenne.LdapUserUpdateListener"/>
+
     <!-- EMAIL -->

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/persistence.xml Sat Jun 7 06:02:54 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/persistence.xml Tue Sep 9 11:49:40 2014 UTC
@@ -48,4 +48,16 @@
         </constructor-arg>
     </bean>

+    <!--
+    LDAP
+    -->
+
+ <bean class="org.haikuos.haikudepotserver.support.ldap.LdapConnectionPoolFactory">
+        <property name="host" value="${ldap.host:}"/>
+        <property name="port" value="${ldap.port:}"/>
+        <property name="userDn" value="${ldap.user.dn:}"/>
+        <property name="password" value="${ldap.password:}"/>
+    </bean>
+
+
 </beans>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperations.html Tue Aug 19 11:01:57 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperations.html Tue Sep 9 11:49:40 2014 UTC
@@ -16,6 +16,14 @@
                 <em>&mdash; did initiate</em>
             </span>
         </li>
+        <li>
+            <a href="" ng-click="goSynchronizeUsers()">
+                Synchronize users (with LDAP)
+            </a>
+            <span ng-show="didSynchronizeUsersTimeout">
+                <em>&mdash; did initiate</em>
+            </span>
+        </li>
<li><a href="" ng-click="goRuntimeInformation()">Runtime information</a></li> <li><a href="" ng-click="goPaginationControlPlayground()">Pagination playground</a></li> <li><a href="" ng-click="goRaiseExceptionInLocalRuntime()">Raise test exception in javascript environment</a></li>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperationscontroller.js Tue Aug 19 11:01:57 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperationscontroller.js Tue Sep 9 11:49:40 2014 UTC
@@ -22,6 +22,9 @@
             $scope.didDeriveAndStoreUserRatingsForAllPkgs = false;
             $scope.deriveAndStoreUserRatingsForAllPkgsTimeout = undefined;

+            $scope.didSynchronizeUsers = false;
+            $scope.didSynchronizeUsersTimeout = undefined;
+
             $scope.goDeriveAndStoreUserRatingsForAllPkgs = function() {
                 jsonRpc.call(
                     constants.ENDPOINT_API_V1_USERRATING,
@@ -48,6 +51,32 @@
                 );
             };

+            $scope.goSynchronizeUsers = function() {
+                jsonRpc.call(
+                    constants.ENDPOINT_API_V1_USER,
+                    "synchronizeUsers",
+                    [{}]
+                ).then(
+                    function() {
+                        $log.info('requested synchronize users');
+                        $scope.didSynchronizeUsers = true;
+
+                        if($scope.didSynchronizeUsersTimeout) {
+ $timeout.cancel($scope.didSynchronizeUsersTimeout);
+                        }
+
+ $scope.didSynchronizeUsersTimeout = $timeout(function() {
+                            $scope.didSynchronizeUsers = false;
+                            $scope.didSynchronizeUsersTimeout = undefined;
+                        }, 3000);
+                    },
+                    function(err) {
+                        $log.error('unable to synchronize users');
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+            };
+
             /**
* <p>This is outside of the normal breadcrumb navigation system because it is a
              * developers' feature.</p>
=======================================
--- /haikudepotserver-webapp/src/test/resources/spring/test.xml Tue Jul 8 09:00:54 2014 UTC +++ /haikudepotserver-webapp/src/test/resources/spring/test.xml Tue Sep 9 11:49:40 2014 UTC
@@ -8,6 +8,7 @@

<bean class="org.haikuos.haikudepotserver.IntegrationTestSupportService"/> <bean class="org.haikuos.haikudepotserver.userrating.NoopUserRatingDerviationService"/> + <bean class="org.haikuos.haikudepotserver.user.NoopLdapSynchronizeUsersService"/> <bean id="mailSender" class="org.haikuos.haikudepotserver.CapturingMailSender"/>



==============================================================================
Revision: 52c9f839987d
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Fri Sep 12 08:53:03 2014 UTC
Log: check for source and indicate when source is available when viewing a package version.

https://code.google.com/p/haiku-depot-web-app/source/detail?r=52c9f839987d

Added:
/haikudepotserver-webapp/src/main/webapp/js/app/directive/booleanindicatordirective.js
Modified:
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java
 /haikudepotserver-webapp/src/main/resources/messages.properties
 /haikudepotserver-webapp/src/main/resources/messages_de.properties
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js

=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/booleanindicatordirective.js Fri Sep 12 08:53:03 2014 UTC
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This directive renders a small piece of text to indicate if the state
+ * bound is truthy or falsey</p>
+ */
+
+angular.module('haikudepotserver').directive('booleanIndicator',[
+    'messageSource','userState',
+    function(messageSource,userState) {
+        return {
+            restrict: 'E',
+            link: function ($scope, element, attributes) {
+
+                var stateExpression = attributes['state'];
+
+                if (!stateExpression || !stateExpression.length) {
+ throw Error('a value for the binding \'state\' was expected');
+                }
+
+                var containerE = angular.element('<span></span>');
+                element.replaceWith(containerE);
+
+                $scope.$watch(stateExpression, function (newValue) {
+
+                    messageSource.get(
+                        userState.naturalLanguageCode(),
+                            'gen.' + (!!newValue ? 'yes' : 'no')
+                    ).then(
+                        function (str) {
+                            containerE.text(str);
+                        },
+                        function () {
+                            containerE.text('???');
+                        }
+                    );
+
+                });
+            }
+        };
+    }
+]);
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Wed Sep 3 09:55:22 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Fri Sep 12 08:53:03 2014 UTC
@@ -384,7 +384,7 @@
public GetPkgResult getPkg(GetPkgRequest request) throws ObjectNotFoundException {

         Preconditions.checkNotNull(request);
-        Preconditions.checkState(!Strings.isNullOrEmpty(request.name));
+ Preconditions.checkState(!Strings.isNullOrEmpty(request.name), "request pkg name is required");
         Preconditions.checkNotNull(request.versionType);
Preconditions.checkState(!Strings.isNullOrEmpty(request.naturalLanguageCode));

=======================================
--- /haikudepotserver-webapp/src/main/resources/messages.properties Sat Aug 30 10:29:45 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Fri Sep 12 08:53:03 2014 UTC
@@ -13,6 +13,8 @@
 gen.pkg.title.plural=Packages
 gen.created.title=Created
 gen.modified.title=Modified
+gen.yes=Yes
+gen.no=No

 breadcrumb.home.title=Home
 breadcrumb.editRepository.title=Edit {0}
@@ -197,6 +199,7 @@
 listUsers.showInactiveAction.title=Show inactive users
 listUsers.createUserAction.title=Create user

+viewPkg.sourceAvailable.title=Source available
 viewPkg.prominence.title=Prominence
 viewPkg.categories.title=Categories
 viewPkg.categories.none=None
=======================================
--- /haikudepotserver-webapp/src/main/resources/messages_de.properties Sun Aug 31 10:18:17 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages_de.properties Fri Sep 12 08:53:03 2014 UTC
@@ -11,6 +11,8 @@
 gen.pkg.title.plural=Pakete
 gen.created.title=Erstellt
 gen.modified.title=Geändert
+gen.yes=Ja
+gen.no=Nein

 breadcrumb.home.title=Home
 breadcrumb.editRepository.title={0} umbenennen
@@ -193,6 +195,7 @@
 listUsers.showInactiveAction.title=Zeige inaktive Benutzer
 listUsers.createUserAction.title=Benutzer anlegen

+viewPkg.sourceAvailable.title=Quellcode verfügbar
 viewPkg.prominence.title=Empfehlungsstufen
 viewPkg.categories.title=Kategorien
 viewPkg.categories.none=keine
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Tue Aug 19 11:01:57 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Fri Sep 12 08:53:03 2014 UTC
@@ -62,6 +62,10 @@
     <div id="pkg-metadata-container">

         <dl>
+ <dt><message key="viewPkg.sourceAvailable.title"></message></dt>
+            <dd>
+ <boolean-indicator state="!!pkg.versions[0].sourcePkg"></boolean-indicator>
+            </dd>
             <dt><message key="viewPkg.categories.title"></message></dt>
             <dd>
                 <span ng-show="!pkgCategories">...</span>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Tue Aug 19 11:01:57 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Fri Sep 12 08:53:03 2014 UTC
@@ -97,6 +97,7 @@
                         refetchPkgCategories();
                         refetchUserRatings();
                         refetchProminence();
+                        refetchIsSourcePkgAvailable();
                     },
                     function() {
                         errorHandling.navigateToError(); // already logged
@@ -136,6 +137,71 @@
                 );

             }
+
+            /**
+ * <p>If there is a source package for this viewed package then this function
+             * will go off and get it.</p>
+             */
+
+            function refetchIsSourcePkgAvailable() {
+
+                if(!$scope.pkg) {
+ throw Error('there is not pkg available from which it would be possible to ascertain if a source pkg exists.');
+                }
+
+                var pv = $scope.pkg.versions[0];
+
+                jsonRpc.call(
+                    constants.ENDPOINT_API_V1_PKG,
+                    'getPkg',
+                    [{
+                        name : $scope.pkg.name + "_source",
+                        versionType : 'SPECIFIC',
+                        incrementViewCounter : false,
+                        architectureCode : 'source',
+ naturalLanguageCode: constants.NATURALLANGUAGECODE_ENGLISH,
+                        major: pv.major,
+                        minor : pv.minor,
+                        micro : pv.micro,
+                        preRelease : pv.preRelease,
+                        revision : pv.revision
+                    }]
+                ).then(
+                    function(result) {
+                        $log.info('source exists for pkg');
+                        pv.sourcePkg = result;
+                    },
+                    function(err) {
+
+                        switch(err.code) {
+
+                            case jsonRpc.errorCodes.OBJECTNOTFOUND:
+
+                                switch(err.data.entityName) {
+
+                                    case 'Pkg':
+                                    case 'PkgVersion':
+ $log.info('there is no source package found');
+                                        pv.sourcePkg = null;
+                                        break;
+
+                                    default:
+ $log.error('unable to ascertain if source is available for the package'); + errorHandling.handleJsonRpcError(err);
+                                        break;
+                                }
+                                break;
+
+                            default:
+ $log.error('unable to ascertain if source is available for the package');
+                                errorHandling.handleJsonRpcError(err);
+                                break;
+
+                        }
+                    }
+                );
+
+            }

             function refetchPkgIconMetaData() {


==============================================================================
Revision: 8ab5cf122712
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Mon Sep 15 10:09:53 2014 UTC
Log:      preparations for next version

https://code.google.com/p/haiku-depot-web-app/source/detail?r=8ab5cf122712

Added:
/haikudepotserver-webapp/src/main/resources/db/haikudepot/migration/V1.18__Password_hash_changes.sql
Modified:
 /haikudepotserver-docs/src/main/latex/docs/part-config.tex
 /haikudepotserver-rpm/src/main/etc/config__config.properties
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js

=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/resources/db/haikudepot/migration/V1.18__Password_hash_changes.sql Mon Sep 15 10:09:53 2014 UTC
@@ -0,0 +1,56 @@
+-- ------------------------------------------------------
+-- PASSWORD HASHES ARE CHANGING
+-- ------------------------------------------------------
+-- It is necessary to reset the root user's password back
+-- to the original value and it is also necessary to
+-- remove those users who don't have email addresses as
+-- it will not be possible for them to reset their
+-- passwords.
+-- ------------------------------------------------------
+
+UPDATE haikudepot.user SET
+ password_hash='b9c4717bc5c6d16f2be9e967ab0c752f8ac2084f95781989f39cf8736e2edeef',
+  password_salt='cad3422ea02761f8'
+WHERE
+  nickname='root';
+
+-- the following query will get a list of effected parties;
+-- SELECT
+--   nickname,
+-- (SELECT true FROM haikudepot.user_password_reset_token WHERE user_id=u.id LIMIT 1) AS prt, +-- (SELECT true FROM haikudepot.permission_user_pkg WHERE user_id=u.id LIMIT 1) AS pup, +-- (SELECT true FROM haikudepot.user_rating WHERE user_id=u.id LIMIT 1) AS ur
+-- FROM
+--   haikudepot.user u
+-- WHERE
+--   u.email IS NULL
+--   AND u.nickname <> 'root';
+
+DELETE FROM
+  haikudepot.permission_user_pkg
+WHERE
+  user_id IN (
+    SELECT id FROM haikudepot.user u
+    WHERE u.email IS NULL AND u.nickname <> 'root'
+  );
+
+DELETE FROM
+  haikudepot.user_password_reset_token
+WHERE
+  user_id IN (
+    SELECT id FROM haikudepot.user u
+    WHERE u.email IS NULL AND u.nickname <> 'root'
+  );
+
+DELETE FROM
+  haikudepot.user_rating
+WHERE
+  user_id IN (
+    SELECT id FROM haikudepot.user u
+    WHERE u.email IS NULL AND u.nickname <> 'root'
+  );
+
+DELETE FROM haikudepot.user
+WHERE email IS NULL AND nickname <> 'root'
+
+
=======================================
--- /haikudepotserver-docs/src/main/latex/docs/part-config.tex Thu Aug 14 11:03:33 2014 UTC +++ /haikudepotserver-docs/src/main/latex/docs/part-config.tex Mon Sep 15 10:09:53 2014 UTC
@@ -127,3 +127,13 @@
 \subsubsection{\tt email.from}

This is the email address from which emails outbound from the system will be sent. Typically this might be a ``no-reply'' email address such as ``noreply@xxxxxxxxxxxx''.
+
+\subsection{LDAP}
+
+\fcolorbox{red}{white}{\parbox{\textwidth}{\color{red}The LDAP integration is not intended to be used at this point in time.}}
+
+\subsubsection{\tt ldap.host}
+\subsubsection{\tt ldap.port}
+\subsubsection{\tt ldap.user.dn}
+\subsubsection{\tt ldap.password}
+\subsubsection{\tt ldap.people.dn}
=======================================
--- /haikudepotserver-rpm/src/main/etc/config__config.properties Tue Sep 9 11:49:40 2014 UTC +++ /haikudepotserver-rpm/src/main/etc/config__config.properties Mon Sep 15 10:09:53 2014 UTC
@@ -88,6 +88,7 @@
 email.from=noreply@xxxxxxxxxxxx

 # -------------------------------------------
+# == NOT READY TO BE USED ==
 # LDAP related
 # If the LDAP setting are commented-out then the system will operate as if
 # there were no LDAP server and will not attempt to synchronize user data
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Fri Sep 12 08:53:03 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Mon Sep 15 10:09:53 2014 UTC
@@ -62,8 +62,8 @@
     <div id="pkg-metadata-container">

         <dl>
- <dt><message key="viewPkg.sourceAvailable.title"></message></dt>
-            <dd>
+ <dt ng-show="undefined!=viewPkg.sourceAvailable"><message key="viewPkg.sourceAvailable.title"></message></dt>
+            <dd ng-show="undefined!=viewPkg.sourceAvailable">
<boolean-indicator state="!!pkg.versions[0].sourcePkg"></boolean-indicator>
             </dd>
             <dt><message key="viewPkg.categories.title"></message></dt>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Fri Sep 12 08:53:03 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Mon Sep 15 10:09:53 2014 UTC
@@ -145,61 +145,67 @@

             function refetchIsSourcePkgAvailable() {

-                if(!$scope.pkg) {
- throw Error('there is not pkg available from which it would be possible to ascertain if a source pkg exists.');
-                }
+// [apl 15.sep.2014]
+// disabling this for now because it creates exception logs on the server-side if +// there is no source package (often). Maybe this can be dealt with later on, but
+// I don't want to delay release because of this.

-                var pv = $scope.pkg.versions[0];

-                jsonRpc.call(
-                    constants.ENDPOINT_API_V1_PKG,
-                    'getPkg',
-                    [{
-                        name : $scope.pkg.name + "_source",
-                        versionType : 'SPECIFIC',
-                        incrementViewCounter : false,
-                        architectureCode : 'source',
- naturalLanguageCode: constants.NATURALLANGUAGECODE_ENGLISH,
-                        major: pv.major,
-                        minor : pv.minor,
-                        micro : pv.micro,
-                        preRelease : pv.preRelease,
-                        revision : pv.revision
-                    }]
-                ).then(
-                    function(result) {
-                        $log.info('source exists for pkg');
-                        pv.sourcePkg = result;
-                    },
-                    function(err) {
-
-                        switch(err.code) {
-
-                            case jsonRpc.errorCodes.OBJECTNOTFOUND:
-
-                                switch(err.data.entityName) {
-
-                                    case 'Pkg':
-                                    case 'PkgVersion':
- $log.info('there is no source package found');
-                                        pv.sourcePkg = null;
-                                        break;
-
-                                    default:
- $log.error('unable to ascertain if source is available for the package'); - errorHandling.handleJsonRpcError(err);
-                                        break;
-                                }
-                                break;
-
-                            default:
- $log.error('unable to ascertain if source is available for the package');
-                                errorHandling.handleJsonRpcError(err);
-                                break;
-
-                        }
-                    }
-                );
+//                if(!$scope.pkg) {
+// throw Error('there is not pkg available from which it would be possible to ascertain if a source pkg exists.');
+//                }
+//
+//                var pv = $scope.pkg.versions[0];
+//
+//                jsonRpc.call(
+//                    constants.ENDPOINT_API_V1_PKG,
+//                    'getPkg',
+//                    [{
+//                        name : $scope.pkg.name + "_source",
+//                        versionType : 'SPECIFIC',
+//                        incrementViewCounter : false,
+//                        architectureCode : 'source',
+// naturalLanguageCode: constants.NATURALLANGUAGECODE_ENGLISH,
+//                        major: pv.major,
+//                        minor : pv.minor,
+//                        micro : pv.micro,
+//                        preRelease : pv.preRelease,
+//                        revision : pv.revision
+//                    }]
+//                ).then(
+//                    function(result) {
+//                        $log.info('source exists for pkg');
+//                        pv.sourcePkg = result;
+//                    },
+//                    function(err) {
+//
+//                        switch(err.code) {
+//
+//                            case jsonRpc.errorCodes.OBJECTNOTFOUND:
+//
+//                                switch(err.data.entityName) {
+//
+//                                    case 'Pkg':
+//                                    case 'PkgVersion':
+// $log.info('there is no source package found');
+//                                        pv.sourcePkg = null;
+//                                        break;
+//
+//                                    default:
+// $log.error('unable to ascertain if source is available for the package'); +// errorHandling.handleJsonRpcError(err);
+//                                        break;
+//                                }
+//                                break;
+//
+//                            default:
+// $log.error('unable to ascertain if source is available for the package');
+//                                errorHandling.handleJsonRpcError(err);
+//                                break;
+//
+//                        }
+//                    }
+//                );

             }


==============================================================================
Revision: 8ef5143b7e4d
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Mon Sep 15 10:51:49 2014 UTC
Log:      resolve minor issue in viewing pkg version in jsp pages

https://code.google.com/p/haiku-depot-web-app/source/detail?r=8ef5143b7e4d

Modified:
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersion.java /haikudepotserver-webapp/src/main/webapp/WEB-INF/views/multipage/viewPkgVersion.jsp

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Fri Sep 12 08:53:03 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Mon Sep 15 10:51:49 2014 UTC
@@ -1109,7 +1109,7 @@
                     }
             );

-            LOGGER.info(
+            LOGGER.debug(
"did search and find {} pkg versions for get bulk pkg; fetch in {}ms, marshall in {}ms",
                     pkgVersions.size(),
                     postFetchMs - preFetchMs,
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersion.java Sun Aug 31 12:05:02 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersion.java Mon Sep 15 10:51:49 2014 UTC
@@ -146,7 +146,7 @@
     }

public PkgVersionLocalization getPkgVersionLocalizationOrFallback(final NaturalLanguage naturalLanguage) { - return getPkgVersionLocalizationOrFallback(naturalLanguage.getCode()); + return getPkgVersionLocalizationOrFallbackByCode(naturalLanguage.getCode());
     }

     /**
@@ -173,7 +173,8 @@
      * for english if the desired language is not available.</p>
      */

- public PkgVersionLocalization getPkgVersionLocalizationOrFallback(final String naturalLanguageCode) {
+    // have to add the 'byCode' for JSP to be able to work with it.
+ public PkgVersionLocalization getPkgVersionLocalizationOrFallbackByCode(final String naturalLanguageCode) { Optional<PkgVersionLocalization> pkgVersionLocalizationOptional = Optional.absent();

         if(!Strings.isNullOrEmpty(naturalLanguageCode)) {
=======================================
--- /haikudepotserver-webapp/src/main/webapp/WEB-INF/views/multipage/viewPkgVersion.jsp Sun Aug 31 10:18:17 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/WEB-INF/views/multipage/viewPkgVersion.jsp Mon Sep 15 10:51:49 2014 UTC
@@ -39,7 +39,7 @@
<multipage:pkgIcon pkgVersion="${data.pkgVersion}" size="32"/>
             </div>
             <div id="pkg-title-text">
- <h1><c:out value="${data.pkgVersion.getPkgVersionLocalizationOrFallback(data.currentNaturalLanguage.code).summary}"/></h1> + <h1><c:out value="${data.pkgVersion.getPkgVersionLocalizationOrFallbackByCode(data.currentNaturalLanguage.code).summary}"/></h1>
                 <div class="muted">
                     <small>
                         <c:out value="${data.pkgVersion.pkg.name}"></c:out>
@@ -68,7 +68,7 @@

         <div id="pkg-description-container">
             <p>
- <multipage:plainTextContent value="${data.pkgVersion.getPkgVersionLocalizationOrFallback(data.currentNaturalLanguage.code).description}"></multipage:plainTextContent> + <multipage:plainTextContent value="${data.pkgVersion.getPkgVersionLocalizationOrFallbackByCode(data.currentNaturalLanguage.code).description}"></multipage:plainTextContent>
             </p>
         </div>

Other related posts:

  • » [haiku-depot-web] [haiku-depot-web-app] 4 new revisions pushed by haiku.li...@xxxxxxxxx on 2014-09-16 08:51 GMT - haiku-depot-web-app