Presentation is loading. Please wait.

Presentation is loading. Please wait.

תוכנה 1 בשפת Java שיעור מספר 14: "מה להנדסה ולזה?"

Similar presentations


Presentation on theme: "תוכנה 1 בשפת Java שיעור מספר 14: "מה להנדסה ולזה?""— Presentation transcript:

1 תוכנה 1 בשפת Java שיעור מספר 14: "מה להנדסה ולזה?"
שחר מעוז בית הספר למדעי המחשב אוניברסיטת תל אביב

2 על סדר היום מעבר לתכנות בשפת Java ותכנות מונחה עצמים
תוכנה אינה רק תכנות (גם בדיקות גם תיכון) תבניות תיכון (design patterns) קלאסיות בתכנות מונחה עצמים חתכי רוחב (crosscutting concerns) שכתוב מבני (refactoring) תוכנה 1 בשפת Java אוניברסיטת תל אביב

3 מבוא להנדסת תוכנה

4 מבוא להנדסת תוכנה תהליך הפיתוח של תוכנה אינו מורכב רק מתכנות ובדיקות
התהליך מתחיל לפני הפיתוח ונמשך גם אחרי שהפיתוח הסתיים הנדסת תוכנה מתיימרת להיות תחום הנדסי העוסק בכל ההיבטים של יצירת מערכות תוכנה. בחלק הזה של הקורס נדון בקצרה בשלבים שלפני ואחרי הפיתוח, במה שמשותף להם ולפיתוח ובמה ששונה הדיון יהיה תמציתי ולא ממצה; הנושא רחב מדי קיימים קורסי בחירה מתקדמים בחוג המתמקדים בשלבים השונים הדיון אינו ספציפי לתכנות מונחה עצמים תוכנה 1 בשפת Java אוניברסיטת תל אביב

5 מחזור החיים של תוכנה ניתוח דרישות (requirements analysis)
תיכון (design) מימוש (Construction, implementation or coding) שילוב (integration) בדיקות וניפוי שגיאות (Testing and debugging aka: verification) בדיקות קבלה ייצור (production) הפצה והתקנה (deployment and installation) תחזוקה ושינויים (maintenance) התייחסות מיוחדת למקרה שמערכת התוכנה היא חלק ממערכת ממוחשבת הכוללת חומרה ותוכנה. תוכנה 1 בשפת Java אוניברסיטת תל אביב

6 מודל המפל המודל המסורתי של מחזור חיים נקרא מודל מפל המים (waterfall model, Royce 1970) - כל שלב מתבצע לאחר שקודמו הסתיים (אך ניתן לחזור לשלב קודם לצורך תיקון). תוכנה 1 בשפת Java אוניברסיטת תל אביב

7 מודל ספירלה מודל הספירלה (spiral model) שהוצע מאוחר יותר (Barry Boehm, 1988) מפתח את המערכת באופן אבולוציוני. מתחילים מפיתוח מערכת מינימלית, ומבצעים את כל השלבים. לאחר סיום מעריכים את המוצר הנוכחי, מחליטים מה להוסיף, וחוזרים על כל השלבים תוכנה 1 בשפת Java אוניברסיטת תל אביב

8 מחירן של טעויות ככל שטעות מתגלה מוקדם יותר, מחיר תיקונה קטן יותר
נניח שטעינו בניתוח הדרישות ושכחנו פעולה מסוימת שהתוכנה צריכה לבצע אם נגלה את הטעות לפני המעבר לתיכון, המחיר יהיה מינימאלי, אולי עיכוב קטן בלוח הזמנים אם נגלה בזמן התיכון, נצטרך אולי לזרוק חלק מהתיכון שלא יתאים לדרישות המתוקנות אבל אם נגלה את הטעות רק בזמן בדיקות הקבלה, נצטרך אולי לזרוק חלקים גדולים מהתיכון ומהמימוש! עדיף לגלות טעויות מוקדם; לשם כך צריך לתכנן בקפדנות את תהליך הפיתוח הכולל, ולהשתדל להשתמש בשיטות שימזערו טעויות ואת הצורך לחזור אחורה לשלב קודם תוכנה 1 בשפת Java אוניברסיטת תל אביב

9 מחירן של טעויות תוכנה 1 בשפת Java אוניברסיטת תל אביב

10 מפל או ספירלה? מודל הספירלה מאפשר לראות מוצר חלקי ולהעריך אותו
אבל מפל המים משקף את הרצוי: רצוי לא לטעות. שינויים בתוכנה אינם רק תוצאה של שגיאות, שינוי הוא דבר מובנה בתהליך הפיתוח פיתוח תוכנה תוך הערכות לשינויים עתידיים הביא למודלים נוספים לתהליך הפיתוח: בשנים האחרונות עולה הפופולריות של משפחת המודלים הקלילה (agile) הנציג הבולט של המשפחה הזו הוא eXtreme Programming (תכנות קיצוני) תוכנה 1 בשפת Java אוניברסיטת תל אביב

11 תכנות קיצוני (XP) מעצבי השיטה (Kent Beck, Ward Cunningham, Ron Jeffries, 1996) ניסחו 4 ערכים: משוב (feedback) פשטות (Simplicity) תקשורת (Communication) אומץ (Courage) ערכים אלו מבוטאים ב 12 מיומנויות תוכנה מכיוון שהערכים והמיומנויות הוכחו כטובים, נלקח כל אחד מהם לקיצוניות תוכנה 1 בשפת Java אוניברסיטת תל אביב

12 אינטגרציה משוב תקשורת פשטות אומץ
Source: Beck, K. (2000). eXtreme Programming explained, Addison Wesley. תוכנה 1 בשפת Java אוניברסיטת תל אביב אחריות מטפורות בדיקות אינטגרציה משוב תקשורת פשטות אומץ שכתוב

13 תוכנה 1 בשפת Java אוניברסיטת תל אביב

14 בדיקות תוכנה

15 איך יודעים שמודול או תוכנית נכונים?
אימות: תהליך שמיועד לוודא באופן פורמאלי או לא פורמאלי נכונות של מודול או תוכנית ביחס לחוזה אימות פורמאלי אוטומאטי אינו אפשרי במקרה הכללי (לא כריע). למרות זאת קיימים כלים פורמליים שלעיתים אינם מצליחים. אימות פורמאלי ידני יקר מדי לרוב המערכות פרט אולי למערכות שחיי אדם תלויים בהן ישירות (רפואיות, מוטסות, וכולי, אבל גם שם יש פחות אימות ממה שהיה ראוי) בדיקות (testing): ביצוע סדרת הרצות של התוכנה שמיועדות למצוא פגמים, אם יש, ולהגדיל את בטחוננו בנכונותה לא מבטיח נכונות, אבל יותר טוב מכלום, ומועיל מאוד באופן מעשי להקטנת מספר הפגמים תוכנה 1 בשפת Java אוניברסיטת תל אביב

16 אל תירה בשליח כאשר המכונית לא עוברת טסט, זה כמובן מעצבן, אבל זה בדרך כלל לא כישלון של מכון הרישוי שביצע את הטסט כישלון והצלחה של בדיקה הם נפרדים לחלוטין מאלה של הקוד הנבדק! בדיקה מצליחה אם היא מגלה פגם בדיקה נכשלת אם היא לא מגלה פגם או מדווחת על פגם לא קיים אם בדיקה מדווחת על פגם נאמר שהקוד לא עבר את הבדיקה, ולא נאמר שהבדיקה נכשלה דווח על פגם הוא אירוע חיובי (לא משמח אולי, אבל חיובי) כי הוא מספק אפשרות לתיקון פגם לפני שהוא גורם עוד נזק תוכנה 1 בשפת Java אוניברסיטת תל אביב

17 שלושה סוגי בדיקות בדיקות יחידה (unit tests) בודקות מודול בודד (שרות, מחלקה אחת או מספר מחלקות קשורות) בדיקות אינטגרציה בודקות את התוכנית כולה, או קבוצה של מודולים ביחד; מתבצעת תמיד לאחר בדיקות היחידה של המודולים הבודדים (כלומר על מודולים שעברו את בדיקות היחידה שלהם) בדיקות קבלה (acceptance tests) מתבצעות על ידי הלקוח או על ידי צוות שמתפקד בתור לקוח, לא על ידי צוות הפיתוח גם לאחר כניסה לשימוש, התוכנה ממשיכה למעשה להיבדק, אבל אצל משתמשים אמיתיים; רצוי שיהיה מנגנון דיווח לתקלות ופגמים שמתגלים בשלב הזה, ורצוי לתקן את הפגמים הללו תוכנה 1 בשפת Java אוניברסיטת תל אביב

18 קופסאות שחורות וקופסאות פתוחות
על כל מודול תוכנה צריך לבצע שני סוגים של בדיקות יחידה: בדיקות קופסה שחורה (black-box tests) בודקים את הקוד מול החוזה שהוא מבטיח לקיים, והן אינן תלויות במימוש בדיקות קופסה שחורה לא תלויות במימוש ולכן אותו סט בדיקות תקף לכל המימושים של מנשק מסוים, גם העתידיים, ובפרט לשינויים ותיקונים במימוש הנוכחי בדיקות כיסוי (coverage tests או glass-box tests) דואגות שבזמן הבדיקות, כל פיסת קוד תרוץ, ובמקרים מסוימים, תרוץ יותר בכמה צורות בדיקות כיסוי צריך לעדכן כאשר מעדכנים את הקוד תוכנה 1 בשפת Java אוניברסיטת תל אביב

19 איך בודקים? בבדיקות מעורבים שני סוגי קוד: מנועים ורכיבים חלופיים
מנוע (driver) הוא קוד שמדמה לקוח של המודול הנבדק וקורא לו רכיב חלופי (stub) מחליף ספק שמשרת את המודול הנבדק למשל מחלקה A משתמשת ב-B שמשתמשת ב-C בדיקת יחידה ל-B תדמה לקוח של B ותספק מחלקה חלופית ל-C, על מנת שניתן יהיה לבדוק את B בנפרד מ-A ו-C רכיב חלופי צריך להיות פשוט ככל האפשר לפעמים הרכיב החלופי לא יכול להיות משמעותית יותר פשוט מהמודול שאותו הוא מחליף, ואז כדאי להשתמש במודול האמיתי לאחר בדיקות יסודיות שלו Driver(A) Stub(C) A B C תוכנה 1 בשפת Java אוניברסיטת תל אביב

20 בדיקות רגרסיה בכל פעם שמגלים פגם בתוכנה, בכל שלב של חיי התוכנה (גם לאחר שנכנסה לשימוש) יש להוסיף בדיקה שחושפת את הפגם, כלומר שנכשלת בגרסה עם הפגם אבל עוברת בגרסה המתוקנת לפעמים הבדיקה תתווסף לבדיקות הקופסה השחורה ולפעמים לבדיקות הכיסוי (אם הפגם קשור באופן הדוק למימוש ולא לחוזה) את סט הבדיקות השלם, כולל כל הבדיקות הללו שנוצרו בעקבות גילוי פגמים, מריצים לאחר כל שינוי במודול הרלוונטי, על מנת לוודא שהשינוי לא גרם לרגרסיה, כלומר להופעה מחודשת של פגמים ישנים סט הבדיקות מייצג, כמו התוכנה המתוקנת, ניסיון מצטבר ויש לו ערך טכני וכלכלי משמעותי תוכנה 1 בשפת Java אוניברסיטת תל אביב

21 בדיקות צריכות להיות אוטומטיות
בדיקה שדורשת התערבות של אדם היא בדיקה לא טובה, כי קשה ויקר לחזור עליה אחרי כל שינוי בתוכנה לכן, כל בדיקה בדידה צריכה להיות אוטומטית צריך מנגנון (תוכנה) שמריץ את כל הבדיקות ומדווח על כל הפגמים שהתגלו לפעמים צריך להריץ אולי רק חלק, למשל אם ביצענו שינוי קטן בתוכנה; אבל אם הבדיקות מהירות כדאי להריץ את כולן תוכנה 1 בשפת Java אוניברסיטת תל אביב

22 תמיכה בסביבת הפיתוח כלים נוחים לבדיקות יחידה קיימים לכל שפות התכנות ולכל סביבות הפיתוח (JUnit, NUnit, CPPUnit), הכלים מגדירים את המושג Test Suite ליצירת סדרת בדיקות הסביבה מספקת מידע נוח לגבי אלו בדיקות בוצעו אילו עברו ואילו נכשלו קל לראות האם נזרקו חריגות ואילו קל לנווט בקוד ישירות למקור הבעיה אדום = בעיה תוכנה 1 בשפת Java אוניברסיטת תל אביב

23 פיתוח מונחה בדיקות מתודולוגיה ששמה דגש על הבדיקות כגורם המניע את התהליך. חוזרים שוב ושוב על התהליך הבא: הוסף במהירות בדיקה. הרץ את כל הבדיקות וראה שהחדשה לא עוברת. בצע שינוי קטן בקוד. הרץ את כל הבדיקות וראה שכולם עוברות. בצע refactoring לביטול כפילות בקוד. Kent Beck, Test-Driven Development By example, Addison-Wesley תוכנה 1 בשפת Java אוניברסיטת תל אביב

24 פיתוח מונחה בדיקות הבדיקה מקדימה את הפונקציה!
הפונקציה הנכתבת היא מינימלית - מטרתה לגרום לבדיקה להצליח היבט פסיכולוגי לכל מחלקה ולכל מתודה נכתוב מחלקת בדיקה ומתודת בדיקה. לדוגמא את המתודה func של המחלקה MyClass נבדוק בעזרת המתודה testFunc של המחלקה TestMyClass תוכנה 1 בשפת Java אוניברסיטת תל אביב

25 תבניות תיכון (design patterns)

26 תבניות תיכון - מוטיבציה
בחיי היום יום אנחנו מתארים דברים תוך שימוש בתבניות חוזרות: "מכונית א' היא כמו מכונית ב', אבל יש לה 2 דלתות במקום 4" "אני רוצה ארון כמו זה, אבל עם דלתות במקום מגרות" גם בפיתוח תוכנה, אנחנו יכולים להסביר כיצד לעשות משהו ע"י התייחסות לדברים שעשינו בעבר, ובצורה כזאת להקל על התקשורת עם עמיתים. "נאחסן את המבנה בעץ בינרי, ונבצע חיפוש לרוחב" תוכנה 1 בשפת Java אוניברסיטת תל אביב

27 הגדרות מהספרות: "Design Patterns are recurring solutions to design problems you see over and over." [Alpert, Brown, Wof, 1998]. "Design Patterns constitute a set of rules describing how to accomplish certain tasks in the realm of software development." [Pree, 1994]. "A pattern address a recurring design problem that arises in specific design situations and presents a solution to it." [Buschmann et al, 1996]. "Patterns identify and specify abstractions that are above the level of single classes and instances, or of components." [Gamma et al, 1993]. תוכנה 1 בשפת Java אוניברסיטת תל אביב

28 מקורות המושג נעשה פופולרי בעקבות הספר שמכונה GoF – דוגמאות הקוד ב ++C
Design Patterns: Elements of Reusable Object-Oriented Software By Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Published by Addison Wesley Professional. Series: Addison-Wesley Professional Computing Series. ISBN: ; Published: Oct 31, 1994; Copyright 1995; Pages: 416; Edition: 1st. תוכנה 1 בשפת Java אוניברסיטת תל אביב

29 מקורות ב Java קיימים כמה מקורות לתבניות עיצוב עם דוגמאות בשפת Java כגון: The Design Patterns Java Companion James W. Cooper Thinking in Patterns with Java Bruce Eckel תבניות עיצוב רבות אומצו ע"י כותבי הספריות הסטנדרטיות של Java בעיקר (אבל לא רק) בספריות GUI תוכנה 1 בשפת Java אוניברסיטת תל אביב

30 סיווג תבניות הספר של GoF מציג 23 תבניות שמחולקות לשלוש משפחות לפי המטרה שלהן: תבניות ליצירהCreational : נוגעות לתהליך היצירה של עצמים. תבניות מבנהStructural : עוסקות בהרכבה של מחלקות ועצמים. תבניות התנהגותBehavioral : מאפיינות את הדרכים בהן מחלקות ועצמים מתקשרים ומחלקים אחריות. קלסיפיקציה נוספת מתייחסת לתחום העיסוק של תבנית – מחלקות או עצמים. בנוסף, ניתן להתייחס לקבוצות של תבניות שמופיעים בדרך כלל ביחד: כאלה שמהווים חלופות שונות לפתרון בעיות דומות ולהיפך – פתרונות דומים לבעיות שונות לתבניות חשיבות גדולה באפיון הבעיות חלק מהתבניות ראינו במהלך הקורס – בהקשר רחב או מקומי תוכנה 1 בשפת Java אוניברסיטת תל אביב

31 סיווג תבניות תוכנה 1 בשפת Java אוניברסיטת תל אביב

32 Crosscutting Concerns
חתכי רוחב בתוכנה Crosscutting Concerns

33 No Silver Bullet בתוכנה אין פתרונות קסם
גם לתכנות מונחה עצמים יש החסרונות שלו וצריך להיות ערים להם חסרון בולט קשור לניהול של חתכי רוחב (crosscutting concerns) במערכת תוכנה נניח שכתבנו תוכנה שעושה משהו במערכת התוכנה נמצא את המחלקה SomeBusinessClass עם השרות someOperation למשל המחלקה BankAccount עם השרות withdraw (רק לצורך הדוגמא – הדבר תקף כמעט לכל תוכנה אמיתית) תוכנה 1 בשפת Java אוניברסיטת תל אביב

34 The wrong way public class SomeBusinessClass extends OtherBusinessClass {   // Core data members // Override methods in the base class public void someOperation(OperationInformation info) {       // ==== Perform the core operation ====     } ... } תוכנה 1 בשפת Java אוניברסיטת תל אביב

35 The wrong way(2) But what about logging capabilities ?
public class SomeBusinessClass extends OtherBusinessClass {   // Core data members ...Log stream ; // Override methods in the base class public void someOperation(OperationInformation info){          ...log the start of operation // ==== Perform the core operation ==== ...log the complition of operation       } } תוכנה 1 בשפת Java אוניברסיטת תל אביב

36 The wrong way(3) Actually, we want it multithreaded…
public class SomeBusinessClass extends OtherBusinessClass {   // Core data members ...Log stream ; // Override methods in the base class public void someOperation(OperationInformation info) { ...lock the object – thread safety          ...log the start of operation // ==== Perform the core operation ==== ...log the complition of operation ...unlock the object       } } תוכנה 1 בשפת Java אוניברסיטת תל אביב

37 The wrong way(4) Who enforces your contract ?
public class SomeBusinessClass extends OtherBusinessClass {   // Core data members ...Log stream ; // Override methods in the base class public void someOperation(OperationInformation info) { ...ensure info satisfies contract ...lock the object – thread safety          ...log the start of operation // ==== Perform the core operation ==== ...log the complition of operation ...unlock the object       } } תוכנה 1 בשפת Java אוניברסיטת תל אביב

38 The wrong way(5) Authorization ? Authentication ?
public class SomeBusinessClass extends OtherBusinessClass {   // Core data members ...Log stream ; // Override methods in the base class public void someOperation(OperationInformation info) { ...ensure authorization ...ensure info satisfies contract ...lock the object – thread safety          ...log the start of operation // ==== Perform the core operation ==== ...log the complition of operation ...unlock the object       } } תוכנה 1 בשפת Java אוניברסיטת תל אביב

39 The wrong way(6) Persistence ? Cache consistency ?
public class SomeBusinessClass extends OtherBusinessClass {   // Core data members ...Log stream ; ...cache update_status ; // Override methods in the base class public void someOperation(OperationInformation info) { ...ensure authorization ...ensure info satisfies contract ...lock the object – thread safety ...ensure cache is up to date          ...log the start of operation // ==== Perform the core operation ==== ...log the complition of operation ...unlock the object       } public void save(PersitanceStorage ps) {...}    public void load(PersitanceStorage ps) {...} } תוכנה 1 בשפת Java אוניברסיטת תל אביב

40 מה קיבלנו? בלאגן בשתי רמות: ברמת המיקרו (השרות הבודד): Code Tangling
הוא כבר לא עושה "רק משהו אחד" - לא מודולרי ראו תרשים => ברמת המאקרו (מערכת התוכנה): Code Scattering שכפול קוד, קטעי קוד קשורים אינם מופיעים יחד ראו תרשימים גם בשקפים הבאים שבירת המודולריות נוצרת בגלל אופי הספק-לקוח של תכנות מונחה עצמים תוכנה 1 בשפת Java אוניברסיטת תל אביב

41 good modularity XML parsing
XML parsing in org.apache.tomcat red shows relevant lines of code nicely fits in one box תוכנה 1 בשפת Java אוניברסיטת תל אביב

42 good modularity URL pattern matching
URL pattern matching in org.apache.tomcat red shows relevant lines of code nicely fits in two boxes (using inheritance) תוכנה 1 בשפת Java אוניברסיטת תל אביב

43 logging is not modularized…
where is logging in org.apache.tomcat red shows lines of code that handle logging not in just one place not even in a small number of places תוכנה 1 בשפת Java אוניברסיטת תל אביב

44 StandardSessionManager StandardSessionManager
אילו רק יכולנו... /* * * ==================================================================== * modification, are permitted provided that the following conditions * Redistribution and use in source and binary forms, with or without * reserved. * Copyright (c) 1999 The Apache Software Foundation. All rights * The Apache Software License, Version 1.1 * are met: * the documentation and/or other materials provided with the * distribution. * notice, this list of conditions and the following disclaimer in * 2. Redistributions in binary form must reproduce the above copyright * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * Alternately, this acknowlegement may appear in the software * Apache Software Foundation (http://www.apache.org/)." * "This product includes software developed by the * any, must include the following acknowlegement: * 3. The end-user documentation included with the redistribution, if derived * from this software without prior written permission. For written * Foundation" must not be used to endorse or promote products * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software itself, * if and wherever such third-party acknowlegements normally appear. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * permission of the Apache Group. * nor may "Apache" appear in their names without prior written * 5. Products derived from this software may not be called "Apache" * permission, please contact * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * SUCH DAMAGE. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * information on the Apache Software Foundation, please see * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * [Additional notices, if required by prior licensing conditions] */ * <http://www.apache.org/>. package org.apache.tomcat.session; import javax.servlet.*; import java.util.*; import javax.servlet.http.*; import java.net.*; import org.apache.tomcat.core.*; import org.apache.tomcat.util.StringManager; import java.io.*; /** James Todd Jason Hunter James Duncan Davidson * Core implementation of an application level session public class ApplicationSession implements HttpSession { private Context context; private long creationTime = System.currentTimeMillis();; private ServerSession serverSession; private String id; private StringManager sm = StringManager.getManager("org.apache.tomcat.session"); private Hashtable values = new Hashtable(); this.serverSession = serverSession; this.context = context; Context context) { ApplicationSession(String id, ServerSession serverSession, private boolean valid = true; private long thisAccessTime = creationTime; this.id = id; } this.inactiveInterval *= 60; if (this.inactiveInterval != -1) { this.inactiveInterval = context.getSessionTimeOut(); return serverSession; ServerSession getServerSession() { * inactivities can be dealt with accordingly. * Called by context when request comes in so that accesses and if (valid) { // HTTP SESSION IMPLEMENTATION METHODS public String getId() { String msg = sm.getString("applicationSession.session.ise"); throw new IllegalStateException(msg); return id; } else { return creationTime; public long getCreationTime() { return new SessionContextImpl(); public HttpSessionContext getSessionContext() { public void invalidate() { // remove everything in the session serverSession.removeApplicationSession(context); removeValue(name); String name = (String)enum.nextElement(); Enumeration enum = values.keys(); while (enum.hasMoreElements()) { public boolean isNew() { valid = false; if (! valid) { return true; return false; if (thisAccessTime == creationTime) { public void setAttribute(String name, Object value) { setAttribute(name, value); public void putValue(String name, Object value) { removeValue(name); // remove any existing binding throw new IllegalArgumentException(msg); String msg = sm.getString("applicationSession.value.iae"); if (name == null) { new HttpSessionBindingEvent(this, name); ((HttpSessionBindingListener)value).valueBound(e); HttpSessionBindingEvent e = if (value != null && value instanceof HttpSessionBindingListener) { values.put(name, value); public Object getAttribute(String name) { return getAttribute(name); public Object getValue(String name) { return values.get(name); public String[] getValueNames() { names.addElement(e.nextElement()); while (e.hasMoreElements()) { Vector names = new Vector(); Enumeration e = getAttributeNames(); names.copyInto(valueNames); String[] valueNames = new String[names.size()]; public Enumeration getAttributeNames() { return valueNames; return (Enumeration)valuesClone.keys(); Hashtable valuesClone = (Hashtable)values.clone(); public void removeAttribute(String name) { removeAttribute(name); public void removeValue(String name) { Object o = values.get(name); ((HttpSessionBindingListener)o).valueUnbound(e); new HttpSessionBindingEvent(this,name); if (o instanceof HttpSessionBindingListener) { public void setMaxInactiveInterval(int interval) { values.remove(name); public int getMaxInactiveInterval() { inactiveInterval = interval; return inactiveInterval; // ApplicationSession import java.io.IOException; import java.util.Vector; import java.util.Hashtable; import java.util.Enumeration; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import org.apache.tomcat.catalina.*; import javax.servlet.http.HttpSessionContext; import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.ServletException; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionBindingEvent; * <b>IMPLEMENTATION NOTE</b>: An instance of this class represents both the * <p> * to a different JVM for distributable session support. * Standard implementation of the <b>Session</b> interface. This object is * serializable, so that it can be stored in persistent storage or transferred * However, because the class itself is not declared public, Java logic outside * internal (Session) and application level (HttpSession) view of the session. * of the <code>org.apache.tomcat.session</code> package cannot cast an Craig R. McClanahan $Revision: 1.2 $ $Date: 2000/05/15 17:54:10 $ * HttpSession view of this instance back to a Session view. // Constructors implements HttpSession, Session { final class StandardSession manager The manager with which this Session is associated * Construct a new Session associated with the specified Manager. this.manager = manager; super(); public StandardSession(Manager manager) { // Instance Variables private Hashtable attributes = new Hashtable(); * The collection of user data attributes associated with this Session. * The time this session was created, in milliseconds since midnight, private long creationTime = 0L; * January 1, 1970 GMT. private String id = null; * The session identifier of this Session. private static final String info = "StandardSession/1.0"; * Descriptive information describing this Session implementation. * The last accessed time for this Session. private Manager manager = null; * The Manager with which this Session is associated. * The maximum time interval, in seconds, between client requests before private int maxInactiveInterval = -1; * indicates that the session should never time out. * the servlet container may invalidate this session. A negative time private boolean isNew = true; * Flag indicating whether this session is new or not. private boolean isValid = false; * Flag indicating whether this session is valid or not. * The string manager for this package. private static HttpSessionContext sessionContext = null; * The HTTP session context associated with this session. private long thisAccessedTime = creationTime; * The current accessed time for this session. // Session Properties * Set the creation time for this session. This method is called by the time The new creation time * Manager when an existing Session instance is reused. this.creationTime = time; this.thisAccessedTime = time; public void setCreationTime(long time) { * Return the session identifier for this session. return (this.id); * Set the session identifier for this session. id The new session identifier ((ManagerBase) manager).remove(this); (manager instanceof ManagerBase)) if ((this.id != null) && (manager != null) && public void setId(String id) { ((ManagerBase) manager).add(this); if ((manager != null) && (manager instanceof ManagerBase)) * the corresponding version number, in the format * Return descriptive information about this Session implementation and return (this.info); public String getInfo() { * <code><description>/<version></code>. * Return the Manager within which this Session is valid. return (this.manager); public Manager getManager() { public void setManager(Manager manager) { manager The new Manager * Set the Manager within which this Session is valid. * Return the maximum time interval, in seconds, between client requests IllegalStateException if this method is called on * time indicates that the session should never time out. * before the servlet container will invalidate the session. A negative * an invalidated session return (this.maxInactiveInterval); * Set the maximum time interval, in seconds, between client requests interval The new maximum interval this.maxInactiveInterval = interval; * Return the <code>HttpSession</code> for which this object return ((HttpSession) this); public HttpSession getSession() { * is the facade. // Session Public Methods * without triggering an exception if the session has already expired. * Perform the internal processing required to invalidate this session, public void expire() { // Remove this session from our manager's active sessions String attr = (String) attrs.nextElement(); while (attrs.hasMoreElements()) { Vector results = new Vector(); Enumeration attrs = getAttributeNames(); // Unbind any objects associated with this session String name = (String) names.nextElement(); while (names.hasMoreElements()) { Enumeration names = results.elements(); results.addElement(attr); setValid(false); // Mark this session as invalid * preparation for reuse of this object. * Release all object references, and initialize instance variables, in public void recycle() { manager = null; maxInactiveInterval = -1; id = null; creationTime = 0L; attributes.clear(); // Reset the instance variables associated with this Session ((ManagerBase) manager).recycle(this); // Tell our Manager that this Session has been recycled isValid = false; isNew = true; // Session Package Methods * Return the <code>isValid</code> flag for this session. return (this.isValid); boolean isValid() { void setNew(boolean isNew) { isNew The new value for the <code>isNew</code> flag * Set the <code>isNew</code> flag for this session. this.isNew = isNew; void setValid(boolean isValid) { isValid The new value for the <code>isValid</code> flag * Set the <code>isValid</code> flag for this session. this.isValid = isValid; // HttpSession Properties IllegalStateException if this method is called on an * midnight, January 1, 1970 GMT. * Return the time when this session was created, in milliseconds since * invalidated session return (this.creationTime); * replacement. It will be removed in a future version of the As of Version 2.1, this method is deprecated and has no * Return the session context with which this session is associated. * Java Servlet API. sessionContext = new StandardSessionContext(); return (sessionContext); if (sessionContext == null) // HttpSession Public Methods * <code>null</code> if no object is bound with that name. * Return the object bound with the specified name in this session, or name Name of the attribute to be returned return (attributes.get(name)); * containing the names of the objects bound to this session. * Return an <code>Enumeration</code> of <code>String</code> objects return (attributes.keys()); name Name of the value to be returned * <code>getAttribute()</code> As of Version 2.2, this method is replaced by return (getAttribute(name)); * are no such objects, a zero-length array is returned. * Return the set of names of objects bound to this session. If there * <code>getAttributeNames()</code> names[i] = (String) results.elementAt(i); return (names); for (int i = 0; i < names.length; i++) String names[] = new String[results.size()]; * Invalidates this session and unbinds any objects bound to it. expire(); // Cause this session to expire * session, or if the client chooses not to join the session. For * Return <code>true</code> if the client does not yet know about the * request. * has disabled the use of cookies, then a session would be new on each * example, if the server used only cookie-based sessions, and the client return (this.isNew); * Bind an object to this session, using the specified name. If an object * replaced. * of the same name is already bound to this session, the object is name Name to which the object is bound, cannot be null value Object to be bound, cannot be null * <code>valueBound()</code> on the object. * After this method executes, and if the object implements * <code>HttpSessionBindingListener</code>, the container calls * <code>setAttribute()</code> * does nothing. * the session does not have an object bound with this name, this method * Remove the object bound with the specified name from this session. If name Name of the object to remove from this session. * <code>valueUnbound()</code> on the object. if (object == null) Object object = attributes.get(name); synchronized (attributes) { (new HttpSessionBindingEvent((HttpSession) this, name)); ((HttpSessionBindingListener) object).valueUnbound if (object instanceof HttpSessionBindingListener) { attributes.remove(name); // System.out.println( "Removing attribute " + name ); return; * <code>removeAttribute()</code> IllegalArgumentException if an attempt is made to add a * non-serializable object in an environment marked distributable. throw new IllegalArgumentException (sm.getString("standardSession.setAttribute.iae")); !(value instanceof Serializable)) if ((manager != null) && manager.getDistributable() && if (value instanceof HttpSessionBindingListener) ((HttpSessionBindingListener) value).valueBound attributes.put(name, value); // HttpSession Private Methods * <b>IMPLEMENTATION NOTE</b>: The reference to the owning Manager * is not restored by this method, and must be set explicitly. * object input stream. * Read a serialized version of this session object from the specified ClassNotFoundException if an unknown class is specified stream The input stream to read from // Deserialize the scalar instance variables (except Manager) creationTime = ((Long) stream.readObject()).longValue(); throws ClassNotFoundException, IOException { private void readObject(ObjectInputStream stream) IOException if an input/output error occurs isValid = ((Boolean) stream.readObject()).booleanValue(); id = (String) stream.readObject(); String name = (String) stream.readObject(); Object value = (Object) stream.readObject(); for (int i = 0; i < n; i++) { int n = ((Integer) stream.readObject()).intValue(); // Deserialize the attribute count and attribute values * object output stream. * Write a serialized version of this session object to the specified * <b>IMPLEMENTATION NOTE</b>: Any attribute that is not Serializable * will be silently ignored. If you do not want any such attributes, * explicitly. * <code>readObject()</code>, you must set the associated Manager * <b>IMPLEMENTATION NOTE</b>: The owning Manager will not be stored * in the serialized representation of this Session. After calling * Manager is set to <code>true</code>. stream The output stream to write to * be sure the <code>distributable</code> property of our associated stream.writeObject(new Integer(maxInactiveInterval)); stream.writeObject(id); stream.writeObject(new Long(creationTime)); private void writeObject(ObjectOutputStream stream) throws IOException { // Write the scalar instance variables (except Manager) stream.writeObject(new Boolean(isValid)); // Accumulate the names of serializable attributes stream.writeObject(new Boolean(isNew)); if (value instanceof Serializable) Object value = attributes.get(attr); stream.writeObject(new Integer(results.size())); // Serialize the attribute count and the attribute values stream.writeObject(attributes.get(name)); stream.writeObject(name); Enumeration getAttributeNames() | String[] getValueNames() | Object getAttribute(String) | long getCreationTime() | crosscut invalidate(StandardSession s): s & (int getMaxInactiveInterval() | static advice(StandardSession s): invalidate(s) { before { void setAttribute(String, Object)); void removeAttribute(String) | void invalidate() | boolean isNew() | + ".ise")); (s.sm.getString("standardSession." + thisJoinPoint.methodName throw new IllegalStateException if (!s.isValid()) // Private Class * when <code>HttpSession.getSessionContext()</code> is called. * This class is a dummy implementation of the <code>HttpSessionContext</code> * interface, to conform to the requirement that such an object be returned As of Java Servlet API 2.1 with no replacement. The private Vector dummy = new Vector(); final class StandardSessionContext implements HttpSessionContext { * interface will be removed in a future version of this API. * and will be removed in a future version of the API. * This method must return an empty <code>Enumeration</code> * within this context. * Return the session identifiers of all sessions defined As of Java Servlet API 2.1 with no replacement. return (dummy.elements()); public Enumeration getIds() { * This method must return null and will be removed in a id Session identifier for which to look up a session * specified session identifier. * Return the <code>HttpSession</code> associated with the public HttpSession getSession(String id) { * future version of the API. return (null); StandardSession import javax.servlet.http.Cookie; import org.w3c.dom.Node; import org.w3c.dom.NamedNodeMap; * an optional, configurable, maximum number of active sessions allowed. * no session persistence or distributable capabilities, but does support * Standard implementation of the <b>Manager</b> interface that provides * checkInterval="60" maxActiveSessions="-1" * maxInactiveInterval="-1" /> * <Manager className="org.apache.tomcat.session.StandardManager" * <code> * in the following format: * Lifecycle configuration of this component assumes an XML node * where you can adjust the following parameters, with default values * </code> * be active at once, or -1 for no limit. [-1] * <li><b>maxInactiveInterval</b> - The default maximum number of seconds of * inactivity before which the servlet container is allowed to time out * <li><b>maxActiveSessions</b> - The maximum number of sessions allowed to * thread checks for expired sessions. [60] * <li><b>checkInterval</b> - The interval (in seconds) between background * in square brackets: * <ul> * </ul> * descriptor, if any. [-1] * the default session timeout specified in the web application deployment * a session, or -1 for no limit. This value should be overridden from implements Lifecycle, Runnable { extends ManagerBase public final class StandardManager $Revision: $ $Date: 2000/05/02 21:28:30 $ * The interval (in seconds) between checks for expired sessions. private boolean configured = false; * Has this component been configured yet? private int checkInterval = 60; private static final String info = "StandardManager/1.0"; * The descriptive information about this implementation. protected int maxActiveSessions = -1; * The maximum number of active Sessions allowed, or -1 for no limit. private boolean started = false; * Has this component been started yet? private Thread thread = null; * The background thread. * The background thread completion semaphore. private boolean threadDone = false; * Name to register for the background thread. // Properties private String threadName = "StandardManager"; return (this.checkInterval); public int getCheckInterval() { * Return the check interval (in seconds) for this Manager. checkInterval The new check interval * Set the check interval (in seconds) for this Manager. public void setCheckInterval(int checkInterval) { this.checkInterval = checkInterval; * Return descriptive information about this Manager implementation and public int getMaxActiveSessions() { * no limit. * Return the maximum number of active Sessions allowed, or -1 for return (this.maxActiveSessions); * Set the maximum number of actives Sessions allowed, or -1 for this.maxActiveSessions = max; public void setMaxActiveSessions(int max) { max The new maximum number of sessions // Public Methods * method of the returned session. If a new session cannot be created * for any reason, return <code>null</code>. * id will be assigned by this method, and available via the getId() * settings specified by this Manager's properties. The session * Construct and return a new session object, based on the default if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) public Session createSession() { * instantiated for any reason IllegalStateException if a new session cannot be return (super.createSession()); (sm.getString("standardManager.createSession.ise")); // Lifecycle Methods * Configure this component, based on the specified configuration IllegalStateException if this component has already been * (<B>FIXME: What object type should this really be?) parameters Configuration parameters for this component * parameters. This method should be called immediately after the * component instance is created, and before <code>start()</code> * is called. LifecycleException if this component detects a fatal error * configured and/or started if (configured) // Validate and update our current component state throws LifecycleException { * in the configuration parameters it was given public void configure(Node parameters) if (!("Manager".equals(parameters.getNodeName()))) // Parse and process our configuration parameters if (parameters == null) (sm.getString("standardManager.alreadyConfigured")); configured = true; throw new LifecycleException NamedNodeMap attributes = parameters.getAttributes(); } catch (Throwable t) { ; // XXX - Throw exception? setCheckInterval(Integer.parseInt(node.getNodeValue())); if (node != null) { node = attributes.getNamedItem("checkInterval"); Node node = null; try { setMaxActiveSessions(Integer.parseInt(node.getNodeValue())); node = attributes.getNamedItem("maxActiveSessions"); setMaxInactiveInterval(Integer.parseInt(node.getNodeValue())); node = attributes.getNamedItem("maxInactiveInterval"); * component. This method should be called after <code>configure()</code>, * Prepare for the beginning of active use of the public methods of this * started * configured (if required for this component) IllegalStateException if this component has not yet been * and before any of the public methods of the component are utilized. public void start() throws LifecycleException { * that prevents this component from being used if (started) (sm.getString("standardManager.alreadyStarted")); (sm.getString("standardManager.notConfigured")); if (!configured) threadStart(); // Start the background reaper thread started = true; * instance of this component. * Gracefully terminate the active use of the public methods of this * component. This method should be the last one called on a given * been stopped IllegalStateException if this component has already IllegalStateException if this component has not been started if (!started) public void stop() throws LifecycleException { * that needs to be reported started = false; (sm.getString("standardManager.notStarted")); for (int i = 0; i < sessions.length; i++) { StandardSession session = (StandardSession) sessions[i]; Session sessions[] = findSessions(); // Expire all active sessions threadStop(); // Stop the background reaper thread continue; session.expire(); if (!session.isValid()) // Private Methods private void threadSleep() { * Sleep for the duration specified by the <code>checkInterval</code> * property. ; } catch (InterruptedException e) { Thread.sleep(checkInterval * 1000L); private void threadStart() { * session timeouts. * Start the background thread that will periodically check for thread.start(); thread.setDaemon(true); threadDone = false; if (thread != null) thread = new Thread(this, threadName); * Stop the background thread that is periodically checking for threadDone = true; if (thread == null) private void threadStop() { thread.join(); thread.interrupt(); thread = null; // Background Thread public void run() { * The background thread that checks for session timeouts and shutdown. processExpires(); threadSleep(); while (!threadDone) { // Loop until the termination semaphore is set StandardManager StandardSessionManager import org.apache.tomcat.util.SessionUtil; import org.apache.tomcat.core.SessionManager; import org.apache.tomcat.core.Response; import org.apache.tomcat.core.Context; import org.apache.tomcat.core.Request; * XXX - At present, use of <code>StandardManager</code> is hard coded, * that adapts to the new component-based Manager implementation. * Specialized implementation of org.apache.tomcat.core.SessionManager * and lifecycle configuration is not supported. * the core level. The Tomcat.Next "Manager" interface acts more like a * collection class, and has minimal knowledge of the detailed request * paradigm, I would suggest moving the logic implemented here back into * <b>IMPLEMENTATION NOTE</b>: Once we commit to the new Manager/Session * a Context to tell the Manager that we create what the default session * XXX - At present, there is no way (via the SessionManager interface) for * processing semantics of handling sessions. * should be. * timeout for this web application (specified in the deployment descriptor) implements SessionManager { public final class StandardSessionManager * Create a new SessionManager that adapts to the corresponding Manager manager = new StandardManager(); public StandardSessionManager() { * implementation. if (manager instanceof Lifecycle) { } catch (LifecycleException e) { throw new IllegalStateException("" + e); ((Lifecycle) manager).start(); ((Lifecycle) manager).configure(null); * The Manager implementation we are actually using. // XXX should we throw exception or just return null ?? public HttpSession findSession( Context ctx, String id ) { } catch (IOException e) { return session.getSession(); if(session!=null) Session session = manager.findSession(id); return manager.createSession().getSession(); public HttpSession createSession(Context ctx) { * Remove all sessions because our associated Context is being shut down. public void removeSessions(Context ctx) { ctx The context that is being shut down // The manager will still run after that ( i.e. keep database // connection open // contexts, we just want to remove the sessions of ctx! // XXX XXX a manager may be shared by multiple ((Lifecycle) manager).stop(); * Used by context to configure the session manager's inactivity timeout. * session manager according to this value. * descriptor (web.xml). This method lets the Context conforgure the * Context on the other hand has it's timeout set by the deployment * The SessionManager may have some default session time out, the if(-1 != minutes) { // The manager works with seconds... public void setSessionTimeOut(int minutes) { minutes The session inactivity timeout in minutes. manager.setMaxInactiveInterval(minutes * 60); ServerSessionManager import org.apache.tomcat.util.*; public class ServerSessionManager implements SessionManager { private static ServerSessionManager manager; // = new ServerSessionManager(); public static ServerSessionManager getManager() { manager = new ServerSessionManager(); static { protected int inactiveInterval = -1; private Hashtable sessions = new Hashtable(); return manager; reaper.setServerSessionManager(this); reaper.start(); reaper = Reaper.getReaper(); private Reaper reaper; private ServerSessionManager() { String sessionId = SessionIdGenerator.generateId(); session.setMaxInactiveInterval(inactiveInterval); return session.getApplicationSession( ctx, true ); if(-1 != inactiveInterval) { sessions.put(sessionId, session); ServerSession session = new ServerSession(sessionId); if(sSession==null) return null; public HttpSession findSession(Context ctx, String id) { return sSession.getApplicationSession(ctx, false); ServerSession sSession=(ServerSession)sessions.get(id); // XXX Enumeration enum = sessions.keys(); synchronized void reap() { // solution for this, but we'll determine something else later. // sync'd for safty -- no other thread should be getting something // from this while we are reaping. This isn't the most optimal session.reap(); Object key = enum.nextElement(); session.validate(); ServerSession session = (ServerSession)sessions.get(key); String id = session.getId(); synchronized void removeSession(ServerSession session) { sessions.remove(id); public void removeSessions(Context context) { session.invalidate(); appSession.invalidate(); if (appSession != null) { session.getApplicationSession(context, false); ApplicationSession appSession = inactiveInterval = (minutes * 60); SessionInterceptor package org.apache.tomcat.request; * It also marks the session as accessed. * in the Request. * Will process the request and determine the session Id, and set it // GS, separates the session id from the jvm route static final char SESSIONID_ROUTE_SEP = '.'; public class SessionInterceptor extends BaseInterceptor implements RequestInterceptor { * add new interceptors for other methods. * This implementation only handles Cookies sessions, please extend or public SessionInterceptor() { int debug=0; ContextManager cm; public void setContextManager( ContextManager cm ) { debug=i; System.out.println("Set debug to " + i); public void setDebug( int i ) { String sessionId = null; public int requestMap(Request request ) { this.cm=cm; if (cookie.getName().equals("JSESSIONID")) { sessionId = cookie.getValue(); Cookie cookie = cookies[i]; for( int i=0; i<cookies.length; i++ ) { Cookie cookies[]=request.getCookies(); // assert !=null request.setRequestedSessionIdFromCookie(true); if (sessionId!=null){ sessionId=validateSessionId(request, sessionId); sessionId=request.getRequestURI().substring(foundAt+sig.length()); request.setRequestURI(request.getRequestURI().substring(0, foundAt)); // rewrite URL, do I need to do anything more? if ((foundAt=request.getRequestURI().indexOf(sig))!=-1){ if( debug>0 ) cm.log(" XXX RURI=" + request.getRequestURI()); String sig=";jsessionid="; int foundAt=-1; return 0; request.setRequestedSessionIdFromURL(true); sessionId, or null if not valid * It will also clean up the session from load-balancing strings. /** Validate and fix the session id. If the session is not valid return null. // We may still set it and just return session invalid. // XXX what is the correct behavior if the session is invalid ? // GS, We piggyback the JVM id on top of the session cookie private String validateSessionId(Request request, String sessionId){ if(idex > 0) { sessionId = sessionId.substring(0, idex); int idex = sessionId.lastIndexOf(SESSIONID_ROUTE_SEP); if (null != sessionId) { // Separate them ... if( debug>0 ) cm.log(" Orig sessionId " + sessionId ); if (sessionId != null && sessionId.length()!=0) { Context ctx=request.getContext(); SessionManager sM = ctx.getSessionManager(); if(null != sM.findSession(ctx, sessionId)) { // cookie. We must check for validity in the current context. // context and one for the real context... or old session // GS, We are in a problem here, we may actually get // multiple Session cookies (one for the root return sessionId; request.setRequestedSessionId(sessionId); if( debug>0 ) cm.log(" Final session id " + sessionId ); return null; public int beforeBody( Request rrequest, Response response ) { if( debug>0 ) cm.log("Before Body " + reqSessionId ); if( reqSessionId==null) String reqSessionId = response.getSessionId(); if(sessionPath.length() == 0) { sessionPath = "/"; String sessionPath = rrequest.getContext().getPath(); // multiple session cookies can be used, one for each // context. // GS, set the path attribute to the cookie. This way if(null != jvmRoute) { // GS, piggyback the jvm route on the session id. reqSessionId = reqSessionId + SESSIONID_ROUTE_SEP + jvmRoute; String jvmRoute = rrequest.getJvmRoute(); if(!sessionPath.equals("/")) { cookie.setMaxAge(-1); reqSessionId); Cookie cookie = new Cookie("JSESSIONID", CookieTools.getCookieHeaderValue(cookie)); cookie.setVersion(0); response.addHeader( CookieTools.getCookieHeaderName(cookie), cookie.setVersion(1); cookie.setPath(sessionPath); { if( ctx.getDebug() > 0 ) ctx.log("Removing sessions from " + ctx ); public void contextShutdown( Context ctx ) throws TomcatException /** Notification of context shutdown ctx.getSessionManager().removeSessions(ctx); ServerSession * Core implementation of a server session public class ServerSession { private Hashtable appSessions = new Hashtable(); ServerSession(String id) { private int inactiveInterval = -1; (ApplicationSession)appSessions.get(context); boolean create) { public ApplicationSession getApplicationSession(Context context, appSessions.put(context, appSession); appSession = new ApplicationSession(id, this, context); // sync to ensure valid? if (appSession == null && create) { return appSession; // a new appSession // inactive interval -- if so, invalidate and create // make sure that we haven't gone over the end of our appSessions.remove(context); void removeApplicationSession(Context context) { void validate() synchronized void invalidate() { Enumeration enum = appSessions.keys(); (ApplicationSession)appSessions.get(key); String msg = sm.getString("serverSession.value.iae"); public Enumeration getValueNames() { return values.keys(); appSession.validate(); private long lastAccessed = creationTime; validate(); thisAccessTime = System.currentTimeMillis(); lastAccessed = thisAccessTime; void accessed() { // set last accessed to thisAccessTime as it will be left over // from the previous access int thisInterval = if (inactiveInterval != -1) { // if we have an inactive interval, check to see if we've exceeded it void validate() { if (thisInterval > inactiveInterval) { invalidate(); (int)(System.currentTimeMillis() - lastAccessed) / 1000; return lastAccessed; public long getLastAccessedTime() { // we've exceeded it // if we have an inactive interval, check to see if ServerSessionManager ssm = ssm.removeSession(this); ServerSessionManager.getManager(); private long lastAccessedTime = creationTime; * GMT. Actions that your application takes, such as getting or setting * session, as the number of milliseconds since midnight, January 1, 1970 * Return the last time the client sent a request associated with this * a value associated with the session, do not affect the access time. return (this.lastAccessedTime); this.lastAccessedTime = time; * session, even if the application does not reference it. * Update the accessed time information for this session. This method * should be called by the context when a request comes in for a particular public void access() { lastAccessedTime = 0L; this.isNew=false; this.thisAccessedTime = System.currentTimeMillis(); this.lastAccessedTime = this.thisAccessedTime; isNew = ((Boolean) stream.readObject()).booleanValue(); lastAccessedTime = ((Long) stream.readObject()).longValue(); maxInactiveInterval = ((Integer) stream.readObject()).intValue(); stream.writeObject(new Long(lastAccessedTime)); ApplicationSession apS=(ApplicationSession)findSession( ctx, id); public void accessed( Context ctx, Request req, String id ) { sM.accessed(ctx, request, sessionId ); // cache it - no need to compute it again apS.accessed(); servS.accessed(); if( apS==null) return; ServerSession servS=apS.getServerSession(); req.setSession( apS ); long timeNow = System.currentTimeMillis(); * Invalidate all sessions that have expired. private void processExpires() { int timeIdle = // Truncate, do not round up int maxInactiveInterval = session.getMaxInactiveInterval(); if (maxInactiveInterval < 0) if (timeIdle >= maxInactiveInterval) (int) ((timeNow - session.getLastAccessedTime()) / 1000L); * called for each request by a RequestInterceptor. * Mark the specified session's last accessed time. This should be HttpSession session=findSession(ctx, id); if( session == null) return; public void accessed(Context ctx, Request req, String id) { session The session to be marked // cache the HttpSession - avoid another find ((Session) session).access(); if (session instanceof Session) req.setSession( session ); /* * * ==================================================================== * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * reserved. * Copyright (c) 1999 The Apache Software Foundation. All rights * The Apache Software License, Version 1.1 * 1. Redistributions of source code must retain the above copyright * are met: * the documentation and/or other materials provided with the * distribution. * notice, this list of conditions and the following disclaimer in * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer. itself, * if and wherever such third-party acknowlegements normally appear. * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software * "This product includes software developed by the * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * Foundation" must not be used to endorse or promote products * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * 5. Products derived from this software may not be called "Apache" * permission, please contact derived * from this software without prior written permission. For written * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * This software consists of voluntary contributions made by many * SUCH DAMAGE. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * information on the Apache Software Foundation, please see * individuals on behalf of the Apache Software Foundation. For more * [Additional notices, if required by prior licensing conditions] */ * <http://www.apache.org/>. package org.apache.tomcat.session; import java.net.*; import java.util.*; import java.io.*; import org.apache.tomcat.util.StringManager; import org.apache.tomcat.core.*; * Core implementation of an application level session James Duncan Davidson /** import javax.servlet.http.*; import javax.servlet.*; James Todd Jason Hunter private String id; private Hashtable values = new Hashtable(); private StringManager sm = public class ApplicationSession implements HttpSession { StringManager.getManager("org.apache.tomcat.session"); private ServerSession serverSession; private boolean valid = true; private int inactiveInterval = -1; private long lastAccessed = creationTime; private Context context; private long creationTime = System.currentTimeMillis();; private long thisAccessTime = creationTime; this.inactiveInterval = context.getSessionTimeOut(); this.context = context; this.id = id; Context context) { ApplicationSession(String id, ServerSession serverSession, this.serverSession = serverSession; } this.inactiveInterval *= 60; if (this.inactiveInterval != -1) { return serverSession; ServerSession getServerSession() { * inactivities can be dealt with accordingly. * Called by context when request comes in so that accesses and void accessed() { thisAccessTime = System.currentTimeMillis(); lastAccessed = thisAccessTime; // set last accessed to thisAccessTime as it will be left over // from the previous access int thisInterval = if (inactiveInterval != -1) { void validate() { validate(); invalidate(); if (thisInterval > inactiveInterval) { (int)(System.currentTimeMillis() - lastAccessed) / 1000; // if we have an inactive interval, check to see if we've exceeded it if (valid) { // HTTP SESSION IMPLEMENTATION METHODS String msg = sm.getString("applicationSession.session.ise"); public String getId() { throw new IllegalStateException(msg); } else { return id; return creationTime; public long getCreationTime() { public long getLastAccessedTime() { return new SessionContextImpl(); public HttpSessionContext getSessionContext() { return lastAccessed; public void invalidate() { // remove everything in the session serverSession.removeApplicationSession(context); removeValue(name); String name = (String)enum.nextElement(); Enumeration enum = values.keys(); while (enum.hasMoreElements()) { public boolean isNew() { valid = false; if (! valid) { return true; return false; if (thisAccessTime == creationTime) { public void setAttribute(String name, Object value) { setAttribute(name, value); public void putValue(String name, Object value) { removeValue(name); // remove any existing binding throw new IllegalArgumentException(msg); String msg = sm.getString("applicationSession.value.iae"); if (name == null) { new HttpSessionBindingEvent(this, name); ((HttpSessionBindingListener)value).valueBound(e); HttpSessionBindingEvent e = if (value != null && value instanceof HttpSessionBindingListener) { values.put(name, value); public Object getAttribute(String name) { return getAttribute(name); public Object getValue(String name) { return values.get(name); public String[] getValueNames() { names.addElement(e.nextElement()); while (e.hasMoreElements()) { Vector names = new Vector(); Enumeration e = getAttributeNames(); names.copyInto(valueNames); String[] valueNames = new String[names.size()]; public Enumeration getAttributeNames() { return valueNames; return (Enumeration)valuesClone.keys(); Hashtable valuesClone = (Hashtable)values.clone(); public void removeAttribute(String name) { removeAttribute(name); public void removeValue(String name) { Object o = values.get(name); ((HttpSessionBindingListener)o).valueUnbound(e); new HttpSessionBindingEvent(this,name); if (o instanceof HttpSessionBindingListener) { public void setMaxInactiveInterval(int interval) { values.remove(name); public int getMaxInactiveInterval() { inactiveInterval = interval; return inactiveInterval; // ApplicationSession import java.io.IOException; import java.util.Vector; import javax.servlet.ServletException; import java.util.Hashtable; import java.util.Enumeration; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionContext; import org.apache.tomcat.catalina.*; import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionBindingEvent; * serializable, so that it can be stored in persistent storage or transferred * Standard implementation of the <b>Session</b> interface. This object is * internal (Session) and application level (HttpSession) view of the session. * <b>IMPLEMENTATION NOTE</b>: An instance of this class represents both the * <p> * to a different JVM for distributable session support. * HttpSession view of this instance back to a Session view. * of the <code>org.apache.tomcat.session</code> package cannot cast an * However, because the class itself is not declared public, Java logic outside $Revision: 1.2 $ $Date: 2000/05/15 17:54:10 $ Craig R. McClanahan implements HttpSession, Session { final class StandardSession // Constructors manager The manager with which this Session is associated * Construct a new Session associated with the specified Manager. this.manager = manager; super(); public StandardSession(Manager manager) { // Instance Variables private Hashtable attributes = new Hashtable(); * The collection of user data attributes associated with this Session. * The time this session was created, in milliseconds since midnight, private long creationTime = 0L; * January 1, 1970 GMT. private String id = null; * The session identifier of this Session. private static final String info = "StandardSession/1.0"; * Descriptive information describing this Session implementation. private long lastAccessedTime = creationTime; * The last accessed time for this Session. private Manager manager = null; * The Manager with which this Session is associated. * the servlet container may invalidate this session. A negative time * The maximum time interval, in seconds, between client requests before * indicates that the session should never time out. private boolean isNew = true; * Flag indicating whether this session is new or not. private int maxInactiveInterval = -1; private boolean isValid = false; * Flag indicating whether this session is valid or not. * The string manager for this package. private static HttpSessionContext sessionContext = null; * The HTTP session context associated with this session. private long thisAccessedTime = creationTime; * The current accessed time for this session. // Session Properties * Set the creation time for this session. This method is called by the public void setCreationTime(long time) { time The new creation time * Manager when an existing Session instance is reused. this.thisAccessedTime = time; this.lastAccessedTime = time; this.creationTime = time; * Return the session identifier for this session. return (this.id); (manager instanceof ManagerBase)) if ((this.id != null) && (manager != null) && public void setId(String id) { * Set the session identifier for this session. id The new session identifier ((ManagerBase) manager).remove(this); ((ManagerBase) manager).add(this); if ((manager != null) && (manager instanceof ManagerBase)) * <code><description>/<version></code>. * Return descriptive information about this Session implementation and * the corresponding version number, in the format return (this.info); public String getInfo() { * a value associated with the session, do not affect the access time. * GMT. Actions that your application takes, such as getting or setting * session, as the number of milliseconds since midnight, January 1, 1970 * Return the last time the client sent a request associated with this return (this.lastAccessedTime); return (this.manager); public Manager getManager() { * Return the Manager within which this Session is valid. public void setManager(Manager manager) { manager The new Manager * Set the Manager within which this Session is valid. * Return the maximum time interval, in seconds, between client requests IllegalStateException if this method is called on * time indicates that the session should never time out. * before the servlet container will invalidate the session. A negative return (this.maxInactiveInterval); * an invalidated session * Set the maximum time interval, in seconds, between client requests interval The new maximum interval this.maxInactiveInterval = interval; public HttpSession getSession() { * is the facade. * Return the <code>HttpSession</code> for which this object return ((HttpSession) this); // Session Public Methods * session, even if the application does not reference it. * should be called by the context when a request comes in for a particular * Update the accessed time information for this session. This method this.isNew=false; this.thisAccessedTime = System.currentTimeMillis(); this.lastAccessedTime = this.thisAccessedTime; public void access() { * without triggering an exception if the session has already expired. * Perform the internal processing required to invalidate this session, // Remove this session from our manager's active sessions public void expire() { while (attrs.hasMoreElements()) { Enumeration attrs = getAttributeNames(); Vector results = new Vector(); // Unbind any objects associated with this session while (names.hasMoreElements()) { String name = (String) names.nextElement(); Enumeration names = results.elements(); results.addElement(attr); String attr = (String) attrs.nextElement(); setValid(false); // Mark this session as invalid public void recycle() { * preparation for reuse of this object. * Release all object references, and initialize instance variables, in // Reset the instance variables associated with this Session manager = null; isNew = true; maxInactiveInterval = -1; lastAccessedTime = 0L; attributes.clear(); creationTime = 0L; id = null; isValid = false; ((ManagerBase) manager).recycle(this); // Tell our Manager that this Session has been recycled // Session Package Methods boolean isValid() { * Return the <code>isValid</code> flag for this session. return (this.isValid); void setNew(boolean isNew) { isNew The new value for the <code>isNew</code> flag * Set the <code>isNew</code> flag for this session. this.isNew = isNew; isValid The new value for the <code>isValid</code> flag * Set the <code>isValid</code> flag for this session. this.isValid = isValid; void setValid(boolean isValid) { // HttpSession Properties IllegalStateException if this method is called on an * Return the time when this session was created, in milliseconds since * midnight, January 1, 1970 GMT. * invalidated session return (this.creationTime); * Return the session context with which this session is associated. * Java Servlet API. * replacement. It will be removed in a future version of the As of Version 2.1, this method is deprecated and has no return (sessionContext); sessionContext = new StandardSessionContext(); if (sessionContext == null) // HttpSession Public Methods * <code>null</code> if no object is bound with that name. * Return the object bound with the specified name in this session, or name Name of the attribute to be returned return (attributes.get(name)); * Return an <code>Enumeration</code> of <code>String</code> objects * containing the names of the objects bound to this session. return (attributes.keys()); name Name of the value to be returned * <code>getAttribute()</code> As of Version 2.2, this method is replaced by return (getAttribute(name)); * are no such objects, a zero-length array is returned. * Return the set of names of objects bound to this session. If there * <code>getAttributeNames()</code> names[i] = (String) results.elementAt(i); return (names); for (int i = 0; i < names.length; i++) String names[] = new String[results.size()]; * Invalidates this session and unbinds any objects bound to it. expire(); // Cause this session to expire * session, or if the client chooses not to join the session. For * Return <code>true</code> if the client does not yet know about the * request. * has disabled the use of cookies, then a session would be new on each * example, if the server used only cookie-based sessions, and the client return (this.isNew); * Bind an object to this session, using the specified name. If an object * replaced. * of the same name is already bound to this session, the object is name Name to which the object is bound, cannot be null value Object to be bound, cannot be null * <code>valueBound()</code> on the object. * After this method executes, and if the object implements * <code>HttpSessionBindingListener</code>, the container calls * <code>setAttribute()</code> * does nothing. * the session does not have an object bound with this name, this method * Remove the object bound with the specified name from this session. If name Name of the object to remove from this session. * <code>valueUnbound()</code> on the object. Object object = attributes.get(name); synchronized (attributes) { ((HttpSessionBindingListener) object).valueUnbound (new HttpSessionBindingEvent((HttpSession) this, name)); if (object instanceof HttpSessionBindingListener) { // System.out.println( "Removing attribute " + name ); return; attributes.remove(name); if (object == null) * <code>removeAttribute()</code> IllegalArgumentException if an attempt is made to add a * non-serializable object in an environment marked distributable. throw new IllegalArgumentException (sm.getString("standardSession.setAttribute.iae")); !(value instanceof Serializable)) if ((manager != null) && manager.getDistributable() && ((HttpSessionBindingListener) value).valueBound if (value instanceof HttpSessionBindingListener) attributes.put(name, value); // HttpSession Private Methods * is not restored by this method, and must be set explicitly. * <b>IMPLEMENTATION NOTE</b>: The reference to the owning Manager * object input stream. * Read a serialized version of this session object from the specified ClassNotFoundException if an unknown class is specified IOException if an input/output error occurs stream The input stream to read from id = (String) stream.readObject(); lastAccessedTime = ((Long) stream.readObject()).longValue(); creationTime = ((Long) stream.readObject()).longValue(); // Deserialize the scalar instance variables (except Manager) private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException { isNew = ((Boolean) stream.readObject()).booleanValue(); maxInactiveInterval = ((Integer) stream.readObject()).intValue(); String name = (String) stream.readObject(); Object value = (Object) stream.readObject(); for (int i = 0; i < n; i++) { int n = ((Integer) stream.readObject()).intValue(); // Deserialize the attribute count and attribute values isValid = ((Boolean) stream.readObject()).booleanValue(); * object output stream. * Write a serialized version of this session object to the specified * explicitly. * <b>IMPLEMENTATION NOTE</b>: Any attribute that is not Serializable * <code>readObject()</code>, you must set the associated Manager * in the serialized representation of this Session. After calling * <b>IMPLEMENTATION NOTE</b>: The owning Manager will not be stored stream The output stream to write to * Manager is set to <code>true</code>. * be sure the <code>distributable</code> property of our associated * will be silently ignored. If you do not want any such attributes, stream.writeObject(id); stream.writeObject(new Long(creationTime)); private void writeObject(ObjectOutputStream stream) throws IOException { stream.writeObject(new Boolean(isValid)); stream.writeObject(new Integer(maxInactiveInterval)); stream.writeObject(new Long(lastAccessedTime)); // Write the scalar instance variables (except Manager) // Accumulate the names of serializable attributes stream.writeObject(new Boolean(isNew)); if (value instanceof Serializable) Object value = attributes.get(attr); stream.writeObject(new Integer(results.size())); // Serialize the attribute count and the attribute values stream.writeObject(attributes.get(name)); stream.writeObject(name); Enumeration getAttributeNames() | Object getAttribute(String) | crosscut invalidate(StandardSession s): s & (int getMaxInactiveInterval() | long getCreationTime() | String[] getValueNames() | static advice(StandardSession s): invalidate(s) { if (!s.isValid()) before { void setAttribute(String, Object)); void removeAttribute(String) | void invalidate() | boolean isNew() | + ".ise")); + thisJoinPoint.methodName throw new IllegalStateException (s.sm.getString("standardSession." // Private Class * This class is a dummy implementation of the <code>HttpSessionContext</code> * when <code>HttpSession.getSessionContext()</code> is called. * interface, to conform to the requirement that such an object be returned * interface will be removed in a future version of this API. As of Java Servlet API 2.1 with no replacement. The * Return the session identifiers of all sessions defined * within this context. private Vector dummy = new Vector(); final class StandardSessionContext implements HttpSessionContext { public Enumeration getIds() { * and will be removed in a future version of the API. As of Java Servlet API 2.1 with no replacement. * This method must return an empty <code>Enumeration</code> return (dummy.elements()); id Session identifier for which to look up a session * Return the <code>HttpSession</code> associated with the * specified session identifier. return (null); public HttpSession getSession(String id) { * future version of the API. * This method must return null and will be removed in a StandardSession import javax.servlet.http.Cookie; import org.w3c.dom.Node; import org.w3c.dom.NamedNodeMap; * an optional, configurable, maximum number of active sessions allowed. * no session persistence or distributable capabilities, but does support * Standard implementation of the <b>Manager</b> interface that provides * checkInterval="60" maxActiveSessions="-1" * maxInactiveInterval="-1" /> * <Manager className="org.apache.tomcat.session.StandardManager" * <code> * in the following format: * Lifecycle configuration of this component assumes an XML node * where you can adjust the following parameters, with default values * </code> * be active at once, or -1 for no limit. [-1] * <li><b>maxInactiveInterval</b> - The default maximum number of seconds of * inactivity before which the servlet container is allowed to time out * <li><b>maxActiveSessions</b> - The maximum number of sessions allowed to * thread checks for expired sessions. [60] * <li><b>checkInterval</b> - The interval (in seconds) between background * in square brackets: * <ul> * </ul> * descriptor, if any. [-1] * the default session timeout specified in the web application deployment * a session, or -1 for no limit. This value should be overridden from implements Lifecycle, Runnable { extends ManagerBase public final class StandardManager $Revision: $ $Date: 2000/05/02 21:28:30 $ * The interval (in seconds) between checks for expired sessions. private boolean configured = false; * Has this component been configured yet? private int checkInterval = 60; private static final String info = "StandardManager/1.0"; * The descriptive information about this implementation. protected int maxActiveSessions = -1; * The maximum number of active Sessions allowed, or -1 for no limit. private boolean started = false; * Has this component been started yet? private Thread thread = null; * The background thread. * The background thread completion semaphore. private boolean threadDone = false; * Name to register for the background thread. // Properties private String threadName = "StandardManager"; return (this.checkInterval); public int getCheckInterval() { * Return the check interval (in seconds) for this Manager. checkInterval The new check interval * Set the check interval (in seconds) for this Manager. public void setCheckInterval(int checkInterval) { this.checkInterval = checkInterval; * Return descriptive information about this Manager implementation and public int getMaxActiveSessions() { * no limit. * Return the maximum number of active Sessions allowed, or -1 for return (this.maxActiveSessions); * Set the maximum number of actives Sessions allowed, or -1 for this.maxActiveSessions = max; public void setMaxActiveSessions(int max) { max The new maximum number of sessions // Public Methods * method of the returned session. If a new session cannot be created * for any reason, return <code>null</code>. * id will be assigned by this method, and available via the getId() * settings specified by this Manager's properties. The session * Construct and return a new session object, based on the default if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) public Session createSession() { * instantiated for any reason IllegalStateException if a new session cannot be return (super.createSession()); (sm.getString("standardManager.createSession.ise")); // Lifecycle Methods * Configure this component, based on the specified configuration IllegalStateException if this component has already been * (<B>FIXME: What object type should this really be?) parameters Configuration parameters for this component * parameters. This method should be called immediately after the * component instance is created, and before <code>start()</code> * is called. LifecycleException if this component detects a fatal error * configured and/or started if (configured) // Validate and update our current component state throws LifecycleException { * in the configuration parameters it was given public void configure(Node parameters) configured = true; (sm.getString("standardManager.alreadyConfigured")); throw new LifecycleException NamedNodeMap attributes = parameters.getAttributes(); if (!("Manager".equals(parameters.getNodeName()))) // Parse and process our configuration parameters if (parameters == null) Node node = null; } catch (Throwable t) { ; // XXX - Throw exception? setCheckInterval(Integer.parseInt(node.getNodeValue())); try { if (node != null) { node = attributes.getNamedItem("checkInterval"); setMaxActiveSessions(Integer.parseInt(node.getNodeValue())); node = attributes.getNamedItem("maxActiveSessions"); setMaxInactiveInterval(Integer.parseInt(node.getNodeValue())); node = attributes.getNamedItem("maxInactiveInterval"); IllegalStateException if this component has not yet been * configured (if required for this component) * and before any of the public methods of the component are utilized. * component. This method should be called after <code>configure()</code>, * Prepare for the beginning of active use of the public methods of this public void start() throws LifecycleException { * that prevents this component from being used * started (sm.getString("standardManager.notConfigured")); if (!configured) // Start the background reaper thread threadStart(); started = true; (sm.getString("standardManager.alreadyStarted")); if (started) * component. This method should be the last one called on a given * Gracefully terminate the active use of the public methods of this * been stopped * that needs to be reported IllegalStateException if this component has already * instance of this component. IllegalStateException if this component has not been started public void stop() throws LifecycleException { // Stop the background reaper thread threadStop(); started = false; if (!started) (sm.getString("standardManager.notStarted")); // Expire all active sessions continue; session.expire(); if (!session.isValid()) StandardSession session = (StandardSession) sessions[i]; Session sessions[] = findSessions(); for (int i = 0; i < sessions.length; i++) { // Private Methods * Invalidate all sessions that have expired. long timeNow = System.currentTimeMillis(); private void processExpires() { (int) ((timeNow - session.getLastAccessedTime()) / 1000L); if (timeIdle >= maxInactiveInterval) int timeIdle = // Truncate, do not round up if (maxInactiveInterval < 0) int maxInactiveInterval = session.getMaxInactiveInterval(); private void threadSleep() { * property. * Sleep for the duration specified by the <code>checkInterval</code> ; } catch (InterruptedException e) { Thread.sleep(checkInterval * 1000L); if (thread != null) private void threadStart() { * Start the background thread that will periodically check for * session timeouts. thread.start(); thread.setDaemon(true); thread = new Thread(this, threadName); threadDone = false; private void threadStop() { * Stop the background thread that is periodically checking for if (thread == null) thread.join(); thread.interrupt(); threadDone = true; thread = null; // Background Thread * The background thread that checks for session timeouts and shutdown. processExpires(); threadSleep(); // Loop until the termination semaphore is set public void run() { while (!threadDone) { StandardManager StandardSessionManager import org.apache.tomcat.util.SessionUtil; import org.apache.tomcat.core.SessionManager; import org.apache.tomcat.core.Response; import org.apache.tomcat.core.Context; import org.apache.tomcat.core.Request; * XXX - At present, use of <code>StandardManager</code> is hard coded, * that adapts to the new component-based Manager implementation. * Specialized implementation of org.apache.tomcat.core.SessionManager * and lifecycle configuration is not supported. * the core level. The Tomcat.Next "Manager" interface acts more like a * collection class, and has minimal knowledge of the detailed request * paradigm, I would suggest moving the logic implemented here back into * <b>IMPLEMENTATION NOTE</b>: Once we commit to the new Manager/Session * a Context to tell the Manager that we create what the default session * XXX - At present, there is no way (via the SessionManager interface) for * processing semantics of handling sessions. * should be. * timeout for this web application (specified in the deployment descriptor) implements SessionManager { public final class StandardSessionManager * Create a new SessionManager that adapts to the corresponding Manager manager = new StandardManager(); public StandardSessionManager() { * implementation. if (manager instanceof Lifecycle) { } catch (LifecycleException e) { throw new IllegalStateException("" + e); ((Lifecycle) manager).start(); ((Lifecycle) manager).configure(null); * The Manager implementation we are actually using. * Mark the specified session's last accessed time. This should be public void accessed(Context ctx, Request req, String id) { session The session to be marked * called for each request by a RequestInterceptor. HttpSession session=findSession(ctx, id); // cache the HttpSession - avoid another find ((Session) session).access(); if (session instanceof Session) if( session == null) return; req.setSession( session ); Session session = manager.findSession(id); public HttpSession findSession( Context ctx, String id ) { // XXX should we throw exception or just return null ?? } catch (IOException e) { if(session!=null) return session.getSession(); return manager.createSession().getSession(); public HttpSession createSession(Context ctx) { * Remove all sessions because our associated Context is being shut down. ctx The context that is being shut down // contexts, we just want to remove the sessions of ctx! // The manager will still run after that ( i.e. keep database // XXX XXX a manager may be shared by multiple public void removeSessions(Context ctx) { // connection open ((Lifecycle) manager).stop(); * Used by context to configure the session manager's inactivity timeout. * descriptor (web.xml). This method lets the Context conforgure the * Context on the other hand has it's timeout set by the deployment * The SessionManager may have some default session time out, the public void setSessionTimeOut(int minutes) { minutes The session inactivity timeout in minutes. * session manager according to this value. manager.setMaxInactiveInterval(minutes * 60); if(-1 != minutes) { // The manager works with seconds... ServerSessionManager import org.apache.tomcat.util.*; public class ServerSessionManager implements SessionManager { manager = new ServerSessionManager(); static { protected int inactiveInterval = -1; private static ServerSessionManager manager; // = new ServerSessionManager(); private Reaper reaper; private Hashtable sessions = new Hashtable(); return manager; public static ServerSessionManager getManager() { reaper.start(); reaper.setServerSessionManager(this); private ServerSessionManager() { reaper = Reaper.getReaper(); if( apS==null) return; ServerSession servS=apS.getServerSession(); ApplicationSession apS=(ApplicationSession)findSession( ctx, id); public void accessed( Context ctx, Request req, String id ) { req.setSession( apS ); // cache it - no need to compute it again apS.accessed(); servS.accessed(); if(-1 != inactiveInterval) { session.setMaxInactiveInterval(inactiveInterval); sessions.put(sessionId, session); ServerSession session = new ServerSession(sessionId); String sessionId = SessionIdGenerator.generateId(); if(sSession==null) return null; return sSession.getApplicationSession(ctx, false); public HttpSession findSession(Context ctx, String id) { return session.getApplicationSession( ctx, true ); ServerSession sSession=(ServerSession)sessions.get(id); // XXX Enumeration enum = sessions.keys(); synchronized void reap() { // solution for this, but we'll determine something else later. // sync'd for safty -- no other thread should be getting something // from this while we are reaping. This isn't the most optimal session.reap(); Object key = enum.nextElement(); session.validate(); ServerSession session = (ServerSession)sessions.get(key); String id = session.getId(); synchronized void removeSession(ServerSession session) { sessions.remove(id); public void removeSessions(Context context) { session.invalidate(); appSession.invalidate(); if (appSession != null) { session.getApplicationSession(context, false); ApplicationSession appSession = inactiveInterval = (minutes * 60); SessionInterceptor package org.apache.tomcat.request; * It also marks the session as accessed. * in the Request. * Will process the request and determine the session Id, and set it // GS, separates the session id from the jvm route static final char SESSIONID_ROUTE_SEP = '.'; public class SessionInterceptor extends BaseInterceptor implements RequestInterceptor { * add new interceptors for other methods. * This implementation only handles Cookies sessions, please extend or public SessionInterceptor() { int debug=0; ContextManager cm; public void setContextManager( ContextManager cm ) { debug=i; System.out.println("Set debug to " + i); public void setDebug( int i ) { String sessionId = null; public int requestMap(Request request ) { this.cm=cm; if (cookie.getName().equals("JSESSIONID")) { sessionId = cookie.getValue(); Cookie cookie = cookies[i]; for( int i=0; i<cookies.length; i++ ) { Cookie cookies[]=request.getCookies(); // assert !=null request.setRequestedSessionIdFromCookie(true); if (sessionId!=null){ sessionId=validateSessionId(request, sessionId); sessionId=request.getRequestURI().substring(foundAt+sig.length()); request.setRequestURI(request.getRequestURI().substring(0, foundAt)); // rewrite URL, do I need to do anything more? if ((foundAt=request.getRequestURI().indexOf(sig))!=-1){ if( debug>0 ) cm.log(" XXX RURI=" + request.getRequestURI()); String sig=";jsessionid="; int foundAt=-1; return 0; request.setRequestedSessionIdFromURL(true); * It will also clean up the session from load-balancing strings. sessionId, or null if not valid /** Validate and fix the session id. If the session is not valid return null. // We may still set it and just return session invalid. // XXX what is the correct behavior if the session is invalid ? // Separate them ... // GS, We piggyback the JVM id on top of the session cookie private String validateSessionId(Request request, String sessionId){ if(idex > 0) { sessionId = sessionId.substring(0, idex); int idex = sessionId.lastIndexOf(SESSIONID_ROUTE_SEP); if( debug>0 ) cm.log(" Orig sessionId " + sessionId ); if (null != sessionId) { if (sessionId != null && sessionId.length()!=0) { SessionManager sM = ctx.getSessionManager(); if(null != sM.findSession(ctx, sessionId)) { sM.accessed(ctx, request, sessionId ); Context ctx=request.getContext(); // cookie. We must check for validity in the current context. // multiple Session cookies (one for the root // GS, We are in a problem here, we may actually get // context and one for the real context... or old session return sessionId; if( debug>0 ) cm.log(" Final session id " + sessionId ); request.setRequestedSessionId(sessionId); return null; public int beforeBody( Request rrequest, Response response ) { if( debug>0 ) cm.log("Before Body " + reqSessionId ); if( reqSessionId==null) String reqSessionId = response.getSessionId(); if(sessionPath.length() == 0) { sessionPath = "/"; String sessionPath = rrequest.getContext().getPath(); // multiple session cookies can be used, one for each // context. // GS, set the path attribute to the cookie. This way if(null != jvmRoute) { // GS, piggyback the jvm route on the session id. reqSessionId = reqSessionId + SESSIONID_ROUTE_SEP + jvmRoute; String jvmRoute = rrequest.getJvmRoute(); if(!sessionPath.equals("/")) { cookie.setMaxAge(-1); reqSessionId); Cookie cookie = new Cookie("JSESSIONID", CookieTools.getCookieHeaderValue(cookie)); cookie.setVersion(0); response.addHeader( CookieTools.getCookieHeaderName(cookie), cookie.setVersion(1); cookie.setPath(sessionPath); { if( ctx.getDebug() > 0 ) ctx.log("Removing sessions from " + ctx ); public void contextShutdown( Context ctx ) throws TomcatException /** Notification of context shutdown ctx.getSessionManager().removeSessions(ctx); ServerSession * Core implementation of a server session public class ServerSession { private Hashtable appSessions = new Hashtable(); ServerSession(String id) { (ApplicationSession)appSessions.get(context); public ApplicationSession getApplicationSession(Context context, boolean create) { appSession = new ApplicationSession(id, this, context); appSessions.put(context, appSession); // sync to ensure valid? if (appSession == null && create) { return appSession; // a new appSession // inactive interval -- if so, invalidate and create // make sure that we haven't gone over the end of our appSessions.remove(context); void removeApplicationSession(Context context) { void validate() // if we have an inactive interval, check to see if // we've exceeded it ssm.removeSession(this); ServerSessionManager ssm = ServerSessionManager.getManager(); Enumeration enum = appSessions.keys(); synchronized void invalidate() { (ApplicationSession)appSessions.get(key); String msg = sm.getString("serverSession.value.iae"); return values.keys(); public Enumeration getValueNames() { appSession.validate(); תוכנה 1 בשפת Java אוניברסיטת תל אביב

45 שבירת המודולריות נזכיר 3 גישות לפתרון הבעיה:
מעבר לשימוש ברכיבים (components) במקום עצמים כגון: Servlets או EJB’s חסרון: Domain Specific Framework פתרונות ברמת שפת התכנות ותבניות העיצוב: כגון: Mixin או Dynamic Proxy חסרון: דורש "תחזוקה ידנית" של העיצוב מעבר לשפת תכנות בפרדיגמה התומכת ביחסים נוספים בין מחלקות כגון: AspectJ או שפת E חסרון: לימוד שפה חדשה תוכנה 1 בשפת Java אוניברסיטת תל אביב

46 שכתוב מבני refactoring

47 שכתוב מבני (refactoring)
לנקות ולשפר את הקוד בלי להכניס לשגיאות. "שיפור התיכון אחרי שהקוד נכתב" סותר לכאורה את העקרונות שמנחים פיתוח תוכנה. אבל מכיר בעובדה שבמשך הזמן, שינויים בקוד (למשל להוספת תכונות) גורמים לכך שהמבנה נפגע ומסתבך. ב refactoring מבצעים בכל פעם שינוי קטן, טרנספורמציה שמשמרת נכונות (כלומר לא משנה את ההתנהגות החיצונית). לאחר כל שינוי יש לבדוק היטב שהשינוי היה נכון - להריץ את אוסף הבדיקות שצברנו. תוכנה 1 בשפת Java אוניברסיטת תל אביב

48 מקורות האנשים שזיהו את חשיבות הרעיון : Ward Cunningham, Kent Beck ספר:
Martin Fowler, Refactoring, Improving the Design of Existing Code, Addison Wesley (2nd edition 2005) אתר: קשור ל Extreme Programming תוכנה 1 בשפת Java אוניברסיטת תל אביב

49 למה refactoring ? לשפר את תיכון התוכנה – אחרת מבנה המערכת נשחק עם הזמן. לעשות את התוכנה קריאה יותר – הקריאות חיונית למתחזקים. לעזור למצוא שגיאות – קשה למצוא שגיאה בקוד מסורבל. לזרז את כתיבת הקוד – כל השיפורים הללו יקטינו את הזמן שיידרש בהמשך. תוכנה 1 בשפת Java אוניברסיטת תל אביב

50 מתי לעשות refactoring ? כאשר מוסיפים פונקציונליות למערכת - "אם הקוד היה כתוב כך, היה קל יותר להוסיף את הפעולה". כאשר צריך למצוא שגיאה - בכל פעם שמסתכלים על קוד ומתקשים להבין אותו יש לבדוק האם ניתן לשפר. תוך כדי סקר קוד (Code review) באופן כללי, כל פעם שמגלים קוד ש"מריח לא טוב" (code smells). לדוגמא: כפילות בקוד, שרות ארוך מדי, מחלקה גדולה מדי, רשימת פרמטרים ארוכה, סימפטומים של צימוד חזק מדי בין מחלקות.... תוכנה 1 בשפת Java אוניברסיטת תל אביב

51 קטלוג של refactorings הספר של Fowler כולל קטלוג של refactorings שכל אחד כולל שם, סיכום קצר, מוטיבציה, תהליך השינוי, ודוגמא. חלק מה refactorings ניתנים לאוטומציה ע"י סביבות הפיתוח הכלים מאפשרים לראות כיצד ייראה הקוד אחרי השינוי, ולהחליט (וכן לבטל שינוי שנעשה). הכלים יכולים לציין מתי מובטח שהשינוי נכון (כלומר לא משנה התנהגות). למשל ב eclipse אפילו דוגמא פשוטה - שינוי שם של שרות - קשה מאד לשינוי ידני ללא שגיאה. (שינוי גלובלי בעורך טקסט לא יהיה נכון בהכרח). תוכנה 1 בשפת Java אוניברסיטת תל אביב

52 דוגמאות מקטלוג ה refactorings
extract method / inline method Introduce Explaining Variable Move method/Field Rename method Add/Remove Parameter Pull up/Push down Field/Method Extract Subclass/Superclass/Interface Collapse Hierarchy Replace Inheritance with Delegation / vice versa תוכנה 1 בשפת Java אוניברסיטת תל אביב


Download ppt "תוכנה 1 בשפת Java שיעור מספר 14: "מה להנדסה ולזה?""

Similar presentations


Ads by Google