001    /****************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one   *
003     * or more contributor license agreements.  See the NOTICE file *
004     * distributed with this work for additional information        *
005     * regarding copyright ownership.  The ASF licenses this file   *
006     * to you under the Apache License, Version 2.0 (the            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  You may obtain a copy of the License at   *
009     *                                                              *
010     *   http://www.apache.org/licenses/LICENSE-2.0                 *
011     *                                                              *
012     * Unless required by applicable law or agreed to in writing,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    
020    package org.apache.james.user.jpa;
021    
022    import java.util.Collections;
023    import java.util.Iterator;
024    
025    import javax.annotation.PostConstruct;
026    import javax.persistence.EntityManager;
027    import javax.persistence.EntityManagerFactory;
028    import javax.persistence.EntityTransaction;
029    import javax.persistence.NoResultException;
030    import javax.persistence.PersistenceException;
031    import javax.persistence.PersistenceUnit;
032    
033    import org.apache.commons.configuration.ConfigurationException;
034    import org.apache.commons.configuration.HierarchicalConfiguration;
035    import org.apache.james.user.api.UsersRepositoryException;
036    import org.apache.james.user.api.model.User;
037    import org.apache.james.user.jpa.model.JPAUser;
038    import org.apache.james.user.lib.AbstractUsersRepository;
039    
040    /**
041     * JPA based UserRepository
042     */
043    public class JPAUsersRepository extends AbstractUsersRepository {
044    
045        private EntityManagerFactory entityManagerFactory;
046    
047        private String algo;
048    
049        /**
050         * Sets entity manager.
051         * 
052         * @param entityManagerFactory
053         *            the entityManager to set
054         */
055        @PersistenceUnit
056        public final void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
057            this.entityManagerFactory = entityManagerFactory;
058        }
059    
060        @PostConstruct
061        public void init() {
062            createEntityManager().close();
063        }
064    
065        /**
066         * Get the user object with the specified user name. Return null if no such
067         * user.
068         * 
069         * @param name
070         *            the name of the user to retrieve
071         * @return the user being retrieved, null if the user doesn't exist
072         * 
073         * @since James 1.2.2
074         */
075        public User getUserByName(String name) throws UsersRepositoryException {
076            EntityManager entityManager = entityManagerFactory.createEntityManager();
077    
078            try {
079                return (JPAUser) entityManager.createNamedQuery("findUserByName").setParameter("name", name).getSingleResult();
080            } catch (NoResultException e) {
081                return null;
082            } catch (PersistenceException e) {
083                getLogger().debug("Failed to find user", e);
084                throw new UsersRepositoryException("Unable to search user", e);
085            } finally {
086                entityManager.close();
087            }
088        }
089    
090        /**
091         * Returns the user name of the user matching name on an equalsIgnoreCase
092         * basis. Returns null if no match.
093         * 
094         * @param name
095         *            the name to case-correct
096         * @return the case-correct name of the user, null if the user doesn't exist
097         * @throws UsersRepositoryException
098         */
099        public String getRealName(String name) throws UsersRepositoryException {
100            User u = getUserByName(name);
101            if (u != null) {
102                u.getUserName();
103            }
104            return null;
105        }
106    
107        /**
108         * Update the repository with the specified user object. A user object with
109         * this username must already exist.
110         * 
111         * @throws UsersRepositoryException
112         */
113        public void updateUser(User user) throws UsersRepositoryException {
114            EntityManager entityManager = entityManagerFactory.createEntityManager();
115    
116            final EntityTransaction transaction = entityManager.getTransaction();
117            try {
118                if (contains(user.getUserName())) {
119                    transaction.begin();
120                    entityManager.merge(user);
121                    transaction.commit();
122                } else {
123                    getLogger().debug("User not found");
124                    throw new UsersRepositoryException("User " + user.getUserName() + " not found");
125                }
126            } catch (PersistenceException e) {
127                getLogger().debug("Failed to update user", e);
128                if (transaction.isActive()) {
129                    transaction.rollback();
130                }
131                throw new UsersRepositoryException("Failed to update user " + user.getUserName(), e);
132            } finally {
133                entityManager.close();
134            }
135        }
136    
137        /**
138         * Removes a user from the repository
139         * 
140         * @param name
141         *            the user to remove from the repository
142         * @throws UsersRepositoryException
143         */
144        public void removeUser(String name) throws UsersRepositoryException {
145            EntityManager entityManager = entityManagerFactory.createEntityManager();
146    
147            final EntityTransaction transaction = entityManager.getTransaction();
148            try {
149                transaction.begin();
150                if (entityManager.createNamedQuery("deleteUserByName").setParameter("name", name).executeUpdate() < 1) {
151                    transaction.commit();
152                    throw new UsersRepositoryException("User " + name + " does not exist");
153                } else {
154                    transaction.commit();
155                }
156            } catch (PersistenceException e) {
157                getLogger().debug("Failed to remove user", e);
158                if (transaction.isActive()) {
159                    transaction.rollback();
160                }
161                throw new UsersRepositoryException("Failed to remove user " + name, e);
162            } finally {
163                entityManager.close();
164            }
165        }
166    
167        /**
168         * Returns whether or not this user is in the repository
169         * 
170         * @param name
171         *            the name to check in the repository
172         * @return whether the user is in the repository
173         * @throws UsersRepositoryException
174         */
175        public boolean contains(String name) throws UsersRepositoryException {
176            EntityManager entityManager = entityManagerFactory.createEntityManager();
177    
178            try {
179                return ((Long) entityManager.createNamedQuery("containsUser").setParameter("name", name.toLowerCase()).getSingleResult()).longValue() > 0;
180            } catch (PersistenceException e) {
181                getLogger().debug("Failed to find user", e);
182                throw new UsersRepositoryException("Failed to find user" + name, e);
183            } finally {
184                entityManager.close();
185            }
186        }
187    
188        /**
189         * Test if user with name 'name' has password 'password'.
190         * 
191         * @param name
192         *            the name of the user to be tested
193         * @param password
194         *            the password to be tested
195         * 
196         * @return true if the test is successful, false if the user doesn't exist
197         *         or if the password is incorrect
198         * 
199         * @since James 1.2.2
200         */
201        public boolean test(String name, String password) throws UsersRepositoryException {
202            final User user = getUserByName(name);
203            final boolean result;
204            if (user == null) {
205                result = false;
206            } else {
207                result = user.verifyPassword(password);
208            }
209            return result;
210        }
211    
212        /**
213         * Returns a count of the users in the repository.
214         * 
215         * @return the number of users in the repository
216         * @throws UsersRepositoryException
217         */
218        public int countUsers() throws UsersRepositoryException {
219            EntityManager entityManager = entityManagerFactory.createEntityManager();
220    
221            try {
222                return ((Long) entityManager.createNamedQuery("countUsers").getSingleResult()).intValue();
223            } catch (PersistenceException e) {
224                getLogger().debug("Failed to find user", e);
225                throw new UsersRepositoryException("Failed to count users", e);
226            } finally {
227                entityManager.close();
228            }
229        }
230    
231        /**
232         * List users in repository.
233         * 
234         * @return Iterator over a collection of Strings, each being one user in the
235         *         repository.
236         * @throws UsersRepositoryException
237         */
238        @SuppressWarnings("unchecked")
239        public Iterator<String> list() throws UsersRepositoryException {
240            EntityManager entityManager = entityManagerFactory.createEntityManager();
241    
242            try {
243                return Collections.unmodifiableList(entityManager.createNamedQuery("listUserNames").getResultList()).iterator();
244    
245            } catch (PersistenceException e) {
246                getLogger().debug("Failed to find user", e);
247                throw new UsersRepositoryException("Failed to list users", e);
248            } finally {
249                entityManager.close();
250            }
251        }
252    
253        /**
254         * @see
255         * org.apache.james.user.lib.AbstractUsersRepository#doConfigure(org.apache.commons.configuration.HierarchicalConfiguration)
256         */
257        public void doConfigure(HierarchicalConfiguration config) throws ConfigurationException {
258            algo = config.getString("algorithm", "MD5");
259            super.doConfigure(config);
260        }
261    
262        /**
263         * Return a new {@link EntityManager} instance
264         * 
265         * @return manager
266         */
267        private EntityManager createEntityManager() {
268            return entityManagerFactory.createEntityManager();
269        }
270    
271        /**
272         * @see
273         * org.apache.james.user.lib.AbstractUsersRepository#doAddUser(java.lang.String, java.lang.String)
274         */
275        protected void doAddUser(String username, String password) throws UsersRepositoryException {
276            String lowerCasedUsername = username.toLowerCase();
277            if (contains(lowerCasedUsername)) {
278                throw new UsersRepositoryException(lowerCasedUsername + " already exists.");
279            }
280            EntityManager entityManager = entityManagerFactory.createEntityManager();
281            final EntityTransaction transaction = entityManager.getTransaction();
282            try {
283                transaction.begin();
284                JPAUser user = new JPAUser(lowerCasedUsername, password, algo);
285                entityManager.persist(user);
286                transaction.commit();
287            } catch (PersistenceException e) {
288                getLogger().debug("Failed to save user", e);
289                if (transaction.isActive()) {
290                    transaction.rollback();
291                }
292                throw new UsersRepositoryException("Failed to add user" + username, e);
293            } finally {
294                entityManager.close();
295            }
296        }
297    
298    }