001    /*
002     * 
003     * $Revision: 15422 $ $Date: 2009-07-01 10:48:51 +0200 (Wed, 01 Jul 2009) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.common;
025    
026    import static org.mycore.common.events.MCRSessionEvent.Type.activated;
027    import static org.mycore.common.events.MCRSessionEvent.Type.passivated;
028    
029    import java.security.Principal;
030    import java.util.Arrays;
031    import java.util.Collections;
032    import java.util.Hashtable;
033    import java.util.Iterator;
034    import java.util.List;
035    import java.util.Map;
036    import java.util.Set;
037    import java.util.Map.Entry;
038    import java.util.concurrent.atomic.AtomicInteger;
039    
040    import org.apache.log4j.Logger;
041    import org.hibernate.Transaction;
042    import org.mycore.backend.hibernate.MCRHIBConnection;
043    import org.mycore.common.events.MCRSessionEvent;
044    import org.mycore.common.events.MCRSessionListener;
045    import org.mycore.frontend.servlets.MCRServletJob;
046    
047    /**
048     * Instances of this class collect information kept during a session like the
049     * currently active user, the preferred language etc.
050     * 
051     * @author Detlev Degenhardt
052     * @author Jens Kupferschmidt
053     * @author Frank L\u00fctzenkirchen
054     * 
055     * @version $Revision: 15422 $ $Date: 2008-03-17 17:12:15 +0100 (Mo, 17 Mrz
056     *          2008) $
057     */
058    public class MCRSession implements Cloneable {
059        /** A map storing arbitrary session data * */
060        private Map<Object, Object> map = new Hashtable<Object, Object>();
061    
062        @SuppressWarnings("unchecked")
063        private Map.Entry<Object, Object>[] emptyEntryArray = new Map.Entry[0];
064    
065        private List<Map.Entry<Object, Object>> mapEntries;
066    
067        private boolean mapChanged = true;
068    
069        AtomicInteger accessCount;
070    
071        AtomicInteger concurrentAccess;
072    
073        ThreadLocal<AtomicInteger> currentThreadCount = new ThreadLocal<AtomicInteger>() {
074            public AtomicInteger initialValue() {
075                return new AtomicInteger();
076            }
077        };
078    
079        /** the logger */
080        static Logger LOGGER = Logger.getLogger(MCRSession.class.getName());
081    
082        /** The user ID of the session */
083        private String userID = null;
084    
085        /** The language for this session as upper case character */
086        private String language = null;
087    
088        /** The unique ID of this session */
089        private String sessionID = null;
090    
091        private String FullName = null;
092    
093        private String CurrentDocumentID = null;
094    
095        private String ip = null;
096    
097        private long loginTime, lastAccessTime, thisAccessTime, createTime;
098    
099        private boolean dataBaseAccess;
100    
101        private ThreadLocal<Transaction> transaction = new ThreadLocal<Transaction>();
102    
103        /**
104         * The constructor of a MCRSession. As default the user ID is set to the
105         * value of the property variable named 'MCR.Users.Guestuser.UserName'.
106         */
107        MCRSession() {
108            MCRConfiguration config = MCRConfiguration.instance();
109            userID = config.getString("MCR.Users.Guestuser.UserName", "gast");
110            language = config.getString("MCR.Metadata.DefaultLang", "de");
111            dataBaseAccess = MCRConfiguration.instance().getBoolean("MCR.Persistence.Database.Enable", true);
112    
113            accessCount = new AtomicInteger();
114            concurrentAccess = new AtomicInteger();
115    
116            ip = "";
117            sessionID = buildSessionID();
118            MCRSessionMgr.addSession(this);
119    
120            LOGGER.debug("MCRSession created " + sessionID);
121            setLoginTime();
122            createTime = loginTime;
123    
124        }
125    
126        public final void setLoginTime() {
127            loginTime = System.currentTimeMillis();
128            lastAccessTime = loginTime;
129            thisAccessTime = loginTime;
130        }
131    
132        /**
133         * Constructs a unique session ID for this session, based on current time
134         * and IP address of host where the code runs.
135         */
136        private static synchronized String buildSessionID() {
137            String localip = getLocalIP();
138    
139            java.util.StringTokenizer st = new java.util.StringTokenizer(localip, ".");
140    
141            long sum = Integer.parseInt(st.nextToken());
142    
143            while (st.hasMoreTokens())
144                sum = (sum << 8) + Integer.parseInt(st.nextToken());
145    
146            String address = Long.toString(sum, 36);
147            address = "000000" + address;
148    
149            String prefix = address.substring(address.length() - 6);
150    
151            long now = System.currentTimeMillis();
152            String suffix = Long.toString(now, 36);
153    
154            return prefix + "-" + suffix;
155        }
156    
157        /**
158         * Returns the unique ID of this session
159         */
160        public String getID() {
161            return sessionID;
162        }
163    
164        /**
165         * Returns a list of all stored object keys within MCRSession.
166         * This method is not thread safe.
167         * I you need thread safe access to all stored objects use {@link MCRSession#getMapEntries()} instead.
168         * @return Returns a list of all stored object keys within MCRSession as
169         *         java.util.Ierator
170         */
171        public Iterator<Object> getObjectsKeyList() {
172            return Collections.unmodifiableSet(map.keySet()).iterator();
173        }
174    
175        /**
176         * Returns an unmodifiable list of all entries in this MCRSession
177         * This method is thread safe.
178         */
179        public List<Map.Entry<Object, Object>> getMapEntries() {
180            if (this.mapChanged) {
181                this.mapChanged = false;
182                final Set<Entry<Object, Object>> entrySet = Collections.unmodifiableMap(map).entrySet();
183                final Map.Entry<Object, Object>[] entryArray = entrySet.toArray(emptyEntryArray);
184                this.mapEntries = Collections.unmodifiableList(Arrays.asList(entryArray));
185            }
186            return this.mapEntries;
187        }
188    
189        /** returns the current user ID */
190        public final String getCurrentUserID() {
191            return userID;
192        }
193    
194        /** sets the current user ID */
195        public final void setCurrentUserID(String userID) {
196            this.userID = userID;
197        }
198    
199        /** returns the current language */
200        public final String getCurrentLanguage() {
201            return language;
202        }
203    
204        /** sets the current language */
205        public final void setCurrentLanguage(String language) {
206            this.language = language;
207        }
208    
209        /** returns the current document ID */
210        public final String getCurrentDocumentID() {
211            return CurrentDocumentID;
212        }
213    
214        /** returns the current document ID */
215        public final String getCurrentUserName() {
216            return FullName;
217        }
218    
219        /** sets the current user fullname */
220        public final void setCurrentUserName(String userName) {
221            this.FullName = userName;
222        }
223    
224        /** sets the current document ID */
225        public final void setCurrentDocumentID(String DocumentID) {
226            this.CurrentDocumentID = DocumentID;
227        }
228    
229        /** Write data to the logger for debugging purposes */
230        public final void debug() {
231            LOGGER.debug("SessionID = " + sessionID);
232            LOGGER.debug("UserID    = " + userID);
233            LOGGER.debug("IP        = " + ip);
234            LOGGER.debug("language  = " + language);
235        }
236    
237        /** Stores an object under the given key within the session * */
238        public Object put(Object key, Object value) {
239            mapChanged = true;
240            return map.put(key, value);
241        }
242    
243        /** Returns the object that was stored in the session under the given key * */
244        public Object get(Object key) {
245            return map.get(key);
246        }
247    
248        public void deleteObject(Object key) {
249            mapChanged = true;
250            map.remove(key);
251        }
252    
253        /** Get the ip value to the local IP */
254        public static final String getLocalIP() {
255            try {
256                return java.net.InetAddress.getLocalHost().getHostAddress();
257            } catch (java.net.UnknownHostException ignored) {
258                return "127.0.0.1";
259            }
260        }
261    
262        /** Get the current ip value */
263        public String getCurrentIP() {
264            return ip;
265        }
266    
267        /** Set the ip to the given IP */
268        public final void setCurrentIP(String newip) {
269            java.util.StringTokenizer st = new java.util.StringTokenizer(newip, ".");
270            if (st.countTokens() != 4)
271                return;
272            try {
273                while (st.hasMoreTokens()) {
274                    int i = Integer.parseInt(st.nextToken());
275                    if ((i < 0) || (i > 255)) {
276                        return;
277                    }
278                }
279            } catch (Exception e) {
280                LOGGER.debug("Exception while parsing new ip " + newip + " using old value.", e);
281                return;
282            }
283            this.ip = newip;
284        }
285    
286        public final long getLoginTime() {
287            return loginTime;
288        }
289    
290        public void close() {
291            // remove from session list
292            LOGGER.debug("Remove myself from MCRSession list");
293            MCRSessionMgr.removeSession(this);
294            // clear bound objects
295            LOGGER.debug("Clearing local map.");
296            map.clear();
297            mapEntries = null;
298            this.sessionID = null;
299        }
300    
301        public String toString() {
302            StringBuilder sb = new StringBuilder();
303            sb.append("MCRSession[");
304            sb.append(getID());
305            sb.append(",user:'");
306            sb.append(getCurrentUserID());
307            sb.append("',ip:");
308            sb.append(getCurrentIP());
309            sb.append("]");
310            return sb.toString();
311        }
312    
313        public long getLastAccessedTime() {
314            return lastAccessTime;
315        }
316    
317        /**
318         * Activate this session. For internal use mainly by MCRSessionMgr.
319         * 
320         * @see MCRSessionMgr#setCurrentSession(MCRSession)
321         */
322        void activate() {
323            lastAccessTime = thisAccessTime;
324            thisAccessTime = System.currentTimeMillis();
325            accessCount.incrementAndGet();
326            if (currentThreadCount.get().getAndIncrement() == 0) {
327                fireSessionEvent(activated, concurrentAccess.incrementAndGet());
328            } else {
329                try {
330                    throw new MCRException("Cannot activate a Session more than once per thread: " + currentThreadCount.get().get());
331                } catch (Exception e) {
332                    LOGGER.debug("Too many activate() calls stacktrace:", e);
333                }
334            }
335        }
336    
337        /**
338         * Passivate this session. For internal use mainly by MCRSessionMgr.
339         * 
340         * @see MCRSessionMgr#releaseCurrentSession()
341         */
342        void passivate() {
343            if (currentThreadCount.get().getAndDecrement() == 1) {
344                fireSessionEvent(passivated, concurrentAccess.decrementAndGet());
345            } else {
346                LOGGER.debug("deactivate currentThreadCount: " + currentThreadCount.get().get());
347            }
348        }
349    
350        /**
351         * Fire MCRSessionEvents.
352         * 
353         * This is a common method that fires all types of MCRSessionEvent.
354         * 
355         * Mainly for internal use of MCRSession and MCRSessionMgr.
356         * 
357         * @param type
358         *            type of event
359         * @param concurrentAccessors
360         *            number of concurrentThreads (passivateEvent gets 0 for
361         *            singleThread)
362         */
363        void fireSessionEvent(MCRSessionEvent.Type type, int concurrentAccessors) {
364            List<MCRSessionListener> listeners = MCRSessionMgr.getListeners();
365            if (listeners.size() == 0) {
366                return;
367            }
368            MCRSessionEvent event = new MCRSessionEvent(this, type, concurrentAccessors);
369            LOGGER.debug(event);
370            MCRSessionMgr.getListenersLock().readLock().lock();
371            MCRSessionListener[] list = listeners.toArray(new MCRSessionListener[listeners.size()]);
372            MCRSessionMgr.getListenersLock().readLock().unlock();
373            for (MCRSessionListener listener : list) {
374                listener.sessionEvent(event);
375            }
376        }
377    
378        public long getThisAccessTime() {
379            return thisAccessTime;
380        }
381    
382        public long getCreateTime() {
383            return createTime;
384        }
385    
386        public Principal getUserPrincipal() {
387            MCRServletJob job = (MCRServletJob) get("MCRServletJob");
388            if (job == null)
389                return null;
390            return job.getRequest().getUserPrincipal();
391        }
392    
393        public boolean isPrincipalInRole(String role) {
394            Principal p = getUserPrincipal();
395            if (p == null)
396                return false;
397            MCRServletJob job = (MCRServletJob) get("MCRServletJob");
398            if (job == null)
399                return false;
400            return job.getRequest().isUserInRole(role);
401        }
402    
403        /**
404         * starts a new database transaction.
405         */
406        public void beginTransaction() {
407            if (dataBaseAccess)
408                transaction.set(MCRHIBConnection.instance().getSession().beginTransaction());
409        }
410    
411        /**
412         * commits the database transaction.
413         * Commit is only done if {@link #isTransactionActive()} returns true.
414         */
415        public void commitTransaction() {
416            if (isTransactionActive()) {
417                transaction.get().commit();
418                beginTransaction();
419                MCRHIBConnection.instance().getSession().clear();
420                transaction.get().commit();
421                transaction.remove();
422            }
423        }
424    
425        /**
426         * forces the database transaction to roll back.
427         * Roll back is only performed if {@link #isTransactionActive()} returns true.
428         */
429        public void rollbackTransaction() {
430            if (isTransactionActive()) {
431                transaction.get().rollback();
432                MCRHIBConnection.instance().getSession().close();
433                transaction.remove();
434            }
435        }
436    
437        /**
438         * Is the transaction still alive?
439         * @return true if the transaction is still alive
440         */
441        public boolean isTransactionActive() {
442            return dataBaseAccess && transaction.get() != null && transaction.get().isActive();
443        }
444    
445    }