001/*
002        $Id: ProjectKey.java 4403 2013-01-24 07:28:21Z olle $
003
004        Copyright (C) 2006 Gregory Vincic, Olle Mansson
005        Copyright (C) 2007 Gregory Vincic
006
007        This file is part of Proteios.
008        Available at http://www.proteios.org/
009
010        Proteios is free software; you can redistribute it and/or modify it
011        under the terms of the GNU General Public License as published by
012        the Free Software Foundation; either version 2 of the License, or
013        (at your option) any later version.
014
015        Proteios is distributed in the hope that it will be useful, but
016        WITHOUT ANY WARRANTY; without even the implied warranty of
017        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
018        General Public License for more details.
019
020        You should have received a copy of the GNU General Public License
021        along with this program; if not, write to the Free Software
022        Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
023        02111-1307, USA.
024*/
025package org.proteios.core;
026
027import org.proteios.core.data.ProjectData;
028import org.proteios.core.data.ProjectKeyData;
029import org.proteios.core.data.ShareableData;
030import org.proteios.core.hibernate.TypeWrapper;
031import java.util.HashSet;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035
036/**
037        This class is used to get information about which projects
038        a {@link Shareable} item has been shared to.
039        <p>
040        A project key is immutable and the project/permission combinations 
041        cannot be changed once the key has been created.
042        This allows the core to resuse project keys for all items with the same
043        combination of project/permission values without fear that someone
044        else may change the permissions.
045        <p>
046        Project keys can be used with {@link Shareable} items
047        to share them with projects. If the logged in user has
048        set an active project (see {@link SessionControl#setActiveProject(Project)})
049        new items will automatically be shared to that project.
050        <p>
051        To create a new project key use the {@link ProjectPermissions} object and pass 
052        the information to  the {@link #getNewOrExisting(DbControl, ProjectPermissions)}
053        method.
054        <p>
055        Another option is to use the {@link MultiPermissions} object which allows
056        you to modifiy the sharing information of a whole bunch of shareable items
057        in one go. The <code>MultiPermissions</code> class takes care of all
058        the cumbersome work of creating new keys and assigning those to the items
059        for you.
060
061        @author Nicklas
062        @version 2.0
063        @see Shareable
064        @see SessionControl#setActiveProject(Project)
065        @base.modified $Date: 2013-01-24 08:28:21 +0100 (Thu, 24 Jan 2013) $
066*/
067public class ProjectKey
068        extends Key<ProjectKeyData>
069{
070        /**
071                The type of item represented by this class.
072                @see Item#PROJECTKEY
073                @see #getType()
074        */
075        public static final Item TYPE = Item.PROJECTKEY;
076        
077        /**
078                Create a new or load an existing <code>ProjectKey</code> when you
079                have a combination of project/permission values. If the database
080                contains a project key with the same combination that project key is
081                loaded and returned, otherwise a new project key is created.
082                If no existing key is found, the new key will immediately be saved
083                and committed to the database using another transaction. This ensures
084                that no two keys contains identical permissions. 
085
086                @param dc The <code>DbControl</code> which will be used for
087                        permission checking and database access
088                @param projectPermissions A <code>ProjectPermissions</code> object
089                        holding the permissions
090                @return The <code>ProjectKey</code> item
091                @throws BaseException If there is an error
092        */
093        public static ProjectKey getNewOrExisting(DbControl dc, ProjectPermissions projectPermissions)
094                throws BaseException
095        {
096                if (projectPermissions == null || projectPermissions.size() == 0)
097                {
098                        throw new InvalidDataException("projectPermissions is null or empty");
099                }
100                int id = getNewOrExistingId(null, projectPermissions.getProjectPermissions());
101                return getById(dc, id);
102        }
103
104        /**
105                Get a <code>ProjectKey</code> item when you know the ID.
106
107                @param dc The <code>DbControl</code> which will be used for
108                        permission checking and database access.
109                @param id The ID of the item to load
110                @return The <code>ProjectKey</code> item
111                @throws ItemNotFoundException If an item with the specified 
112                        ID is not found
113                @throws PermissionDeniedException If the logged in user doesn't 
114                        have {@link Permission#READ} permission to the item
115                @throws BaseException If there is another error
116        */
117        public static ProjectKey getById(DbControl dc, int id)
118                throws ItemNotFoundException, PermissionDeniedException, BaseException
119        {
120                ProjectKey pk = dc.loadItem(ProjectKey.class, id);
121                if (pk == null) throw new ItemNotFoundException("ProjectKey[id="+id+"]");
122                return pk;
123        }
124
125        /**
126                Delete all keys that are currently not used by any item. This method 
127                is intended to be executed at regular intervals by a cleanup application.
128        */
129        public static synchronized void deleteUnusedKeys()
130                throws BaseException
131        {
132                org.hibernate.Session session = null;
133                org.hibernate.Transaction tx = null;
134                try
135                {
136                        session = HibernateUtil.newSession();
137                        tx = HibernateUtil.newTransaction(session);
138                        org.hibernate.Query query = HibernateUtil.getPredefinedQuery(session, "FIND_USED_PROJECTKEY_IDS");
139                        /*
140                                SELECT DISTINCT sd.projectKey.id
141                                FROM org.proteios.core.data.ShareableData sd
142                                WHERE NOT sd.projectKey IS NULL
143                        */
144                        Set<Integer> used = new HashSet<Integer>(HibernateUtil.loadList(Integer.class, query));
145                        if (used.size() == 0) used.add(0);
146                        query = HibernateUtil.getPredefinedQuery(session, "SELECT_UNUSED_PROJECTKEYS");
147                        /*
148                                SELECT pk FROM ProjectKeyData pk 
149                                WHERE pk.id NOT IN (:used)
150                        */
151                        query.setParameterList("used", used, TypeWrapper.INTEGER.getHibernateType());
152                        List<ProjectKeyData> unused = HibernateUtil.loadList(ProjectKeyData.class, query);
153                        for (ProjectKeyData pk : unused)
154                        {
155                                HibernateUtil.deleteData(session, pk);
156                        }
157                        HibernateUtil.commit(tx);
158                }
159                catch (BaseException ex)
160                {
161                        if (tx != null) HibernateUtil.rollback(tx);
162                        throw ex;
163                }
164                finally
165                {
166                        if (session != null) HibernateUtil.close(session);
167                }
168        }
169        
170        ProjectKey(ProjectKeyData projectKeyData)
171        {
172                super(projectKeyData);
173        }
174
175        /*
176                From the Identifiable interface
177                -------------------------------------------
178        */
179        public Item getType()
180        {
181                return TYPE;
182        }
183        // -------------------------------------------
184        /*
185                From the BasicItem class
186                -------------------------------------------
187        */
188        /**
189                Checks if:
190                <ul>
191                <li>A {@link Shareable} item is using this key.
192                </ul>
193        */
194        @Override
195        public boolean isUsed()
196                throws BaseException
197        {
198                org.hibernate.Session session = getDbControl().getHibernateSession();
199                org.hibernate.Query query = HibernateUtil.getPredefinedQuery(session, "GET_SHAREABLE_ITEMS_FOR_PROJECTKEY");
200                /*
201                        SELECT s
202                        FROM org.proteios.core.data.ShareableData s
203                        WHERE s.projectKey = :projectKey
204                */
205                query.setEntity("projectKey", this.getData());
206                return HibernateUtil.loadData(ShareableData.class, query) != null;
207        }
208        /**
209                WRITE permission is always denied. USE and CREATE
210                permission is always granted.
211        */
212        @Override
213        void initPermissions(int granted, int denied)
214                throws BaseException
215        {
216                denied |= Permission.deny(Permission.WRITE);
217                granted |= Permission.grant(Permission.USE, Permission.CREATE);
218                super.initPermissions(granted, denied);
219        }
220        // -------------------------------------------
221
222        /**
223                Get the permissions for a project.
224                @param project The <code>Project</code> for which we want to get the permission
225                @return A <code>Set</code> containing the granted permissions, or an
226                        empty set if no permissions have been granted
227                @throws InvalidDataException If the project is null
228                @see Permission
229        */
230        public Set<Permission> getPermissions(Project project)
231                throws InvalidDataException
232        {
233                if (project == null) throw new InvalidUseOfNullException("project");
234                return Permission.fromInt(getData().getProjects().get(project.getData()));
235        }
236        
237        /**
238                Find the ID of an existing key with exactly the same
239                project/permission combination. If no existing key is found a new one is created.
240                @param projectPermissions A <code>Map</code> holding the project/permission combinations
241                @return The ID of the new or existing key
242        */
243        static synchronized int getNewOrExistingId(org.hibernate.Session session, Map<ProjectData,Integer> projectPermissions)
244                throws BaseException
245        {
246                org.hibernate.Transaction tx = null; 
247                if (session == null)
248                {
249                        session = HibernateUtil.newSession();
250                        tx = HibernateUtil.newTransaction(session);
251                }
252                try
253                {
254                        // 1. Find all keys with the same number of entries as we
255                        // have project/permission combinations
256                        int numPermissions = projectPermissions.size();
257                        org.hibernate.Query query = HibernateUtil.getPredefinedQuery(session, "GET_PROJECTKEY_IDS_FOR_COUNT");
258                        /*
259                                SELECT pk.keyId FROM ProjectKeys pk
260                                GROUP BY pk.keyId
261                                HAVING count(pk.projectId) = :numPermissions
262                        */
263                        query.setInteger("numPermissions", numPermissions);
264                        List<Integer> candidates = HibernateUtil.loadList(Integer.class, query);
265                        if (candidates.size() > 0)
266                        {
267                
268                                // 2. Check that the candidates have the same project/permission combinations
269                                StringBuilder sb = new StringBuilder();
270                                int i = 0;
271                                for (Map.Entry<ProjectData,Integer> entry : projectPermissions.entrySet())
272                                {
273                                        if (i > 0) sb.append(" OR ");
274                                        i++;
275                                        sb.append("(pk.projectId=").append(entry.getKey().getId()).
276                                                append(" AND pk.permission=").append(entry.getValue()).append(") ");
277                                }
278                
279                                query = HibernateUtil.createQuery(session,
280                                        "SELECT pk.keyId FROM ProjectKeys pk "+
281                                        "WHERE pk.keyId IN (:candidates) AND ("+sb.toString()+") "+
282                                        "GROUP BY pk.keyId "+
283                                        "HAVING COUNT(pk.projectId) = :numPermissions"
284                                );
285                                query.setParameterList("candidates", candidates, TypeWrapper.INTEGER.getHibernateType());
286                                query.setInteger("numPermissions", numPermissions);
287                                candidates = HibernateUtil.loadList(Integer.class, query);
288                        }
289                        if (candidates.size() == 0)
290                        {
291                                // No more candidates, create a new project key
292                                ProjectKeyData data = new ProjectKeyData();
293                                data.getProjects().putAll(projectPermissions);
294                                HibernateUtil.saveData(session, data);
295                                if (tx != null) HibernateUtil.commit(tx);
296                                return data.getId();
297                        }
298                        if (tx != null) HibernateUtil.commit(tx);
299                        return candidates.get(0);
300                }
301                catch (BaseException ex)
302                {
303                        if (tx != null) HibernateUtil.rollback(tx);
304                        throw ex;
305                }
306                finally
307                {
308                        if (tx != null && session != null) HibernateUtil.close(session);
309                }
310        }
311}
312
313
314
315