1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.server.session;
20
21 import static java.lang.Math.round;
22
23 import java.util.Arrays;
24 import java.util.Collections;
25 import java.util.Enumeration;
26 import java.util.EventListener;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Set;
30 import java.util.concurrent.CopyOnWriteArrayList;
31
32 import javax.servlet.SessionCookieConfig;
33 import javax.servlet.SessionTrackingMode;
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpSession;
36 import javax.servlet.http.HttpSessionAttributeListener;
37 import javax.servlet.http.HttpSessionBindingEvent;
38 import javax.servlet.http.HttpSessionContext;
39 import javax.servlet.http.HttpSessionEvent;
40 import javax.servlet.http.HttpSessionIdListener;
41 import javax.servlet.http.HttpSessionListener;
42
43 import org.eclipse.jetty.http.HttpCookie;
44 import org.eclipse.jetty.server.Server;
45 import org.eclipse.jetty.server.SessionIdManager;
46 import org.eclipse.jetty.server.SessionManager;
47 import org.eclipse.jetty.server.handler.ContextHandler;
48 import org.eclipse.jetty.util.annotation.ManagedAttribute;
49 import org.eclipse.jetty.util.annotation.ManagedObject;
50 import org.eclipse.jetty.util.annotation.ManagedOperation;
51 import org.eclipse.jetty.util.component.ContainerLifeCycle;
52 import org.eclipse.jetty.util.log.Logger;
53 import org.eclipse.jetty.util.statistic.CounterStatistic;
54 import org.eclipse.jetty.util.statistic.SampleStatistic;
55
56
57
58
59
60
61
62
63
64
65
66 @SuppressWarnings("deprecation")
67 @ManagedObject("Abstract Session Manager")
68 public abstract class AbstractSessionManager extends ContainerLifeCycle implements SessionManager
69 {
70 final static Logger __log = SessionHandler.LOG;
71
72 public Set<SessionTrackingMode> __defaultSessionTrackingModes =
73 Collections.unmodifiableSet(
74 new HashSet<SessionTrackingMode>(
75 Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE,SessionTrackingMode.URL})));
76
77
78
79
80 public final static int __distantFuture=60*60*24*7*52*20;
81
82 static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
83 {
84 @Override
85 public HttpSession getSession(String sessionId)
86 {
87 return null;
88 }
89
90 @Override
91 @SuppressWarnings({ "rawtypes", "unchecked" })
92 public Enumeration getIds()
93 {
94 return Collections.enumeration(Collections.EMPTY_LIST);
95 }
96 };
97
98 private boolean _usingCookies=true;
99
100
101
102
103 protected int _dftMaxIdleSecs=-1;
104 protected SessionHandler _sessionHandler;
105 protected boolean _httpOnly=false;
106 protected SessionIdManager _sessionIdManager;
107 protected boolean _secureCookies=false;
108 protected boolean _secureRequestOnly=true;
109
110 protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>();
111 protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>();
112 protected final List<HttpSessionIdListener> _sessionIdListeners = new CopyOnWriteArrayList<HttpSessionIdListener>();
113
114 protected ClassLoader _loader;
115 protected ContextHandler.Context _context;
116 protected String _sessionCookie=__DefaultSessionCookie;
117 protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
118 protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
119 protected String _sessionDomain;
120 protected String _sessionPath;
121 protected int _maxCookieAge=-1;
122 protected int _refreshCookieAge;
123 protected boolean _nodeIdInSessionId;
124 protected boolean _checkingRemoteSessionIdEncoding;
125 protected String _sessionComment;
126
127 public Set<SessionTrackingMode> _sessionTrackingModes;
128
129 private boolean _usingURLs;
130
131 protected final CounterStatistic _sessionsStats = new CounterStatistic();
132 protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
133
134
135
136 public AbstractSessionManager()
137 {
138 setSessionTrackingModes(__defaultSessionTrackingModes);
139 }
140
141
142 public ContextHandler.Context getContext()
143 {
144 return _context;
145 }
146
147
148 public ContextHandler getContextHandler()
149 {
150 return _context.getContextHandler();
151 }
152
153 @ManagedAttribute("path of the session cookie, or null for default")
154 public String getSessionPath()
155 {
156 return _sessionPath;
157 }
158
159 @ManagedAttribute("if greater the zero, the time in seconds a session cookie will last for")
160 public int getMaxCookieAge()
161 {
162 return _maxCookieAge;
163 }
164
165
166 @Override
167 public HttpCookie access(HttpSession session,boolean secure)
168 {
169 long now=System.currentTimeMillis();
170
171 AbstractSession s = ((SessionIf)session).getSession();
172
173 if (s.access(now))
174 {
175
176 if (isUsingCookies() &&
177 (s.isIdChanged() ||
178 (getSessionCookieConfig().getMaxAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
179 )
180 )
181 {
182 HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure);
183 s.cookieSet();
184 s.setIdChanged(false);
185 return cookie;
186 }
187 }
188 return null;
189 }
190
191
192 @Override
193 public void addEventListener(EventListener listener)
194 {
195 if (listener instanceof HttpSessionAttributeListener)
196 _sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
197 if (listener instanceof HttpSessionListener)
198 _sessionListeners.add((HttpSessionListener)listener);
199 if (listener instanceof HttpSessionIdListener)
200 _sessionIdListeners.add((HttpSessionIdListener)listener);
201 addBean(listener,false);
202 }
203
204
205 @Override
206 public void clearEventListeners()
207 {
208 for (EventListener e :getBeans(EventListener.class))
209 removeBean(e);
210 _sessionAttributeListeners.clear();
211 _sessionListeners.clear();
212 _sessionIdListeners.clear();
213 }
214
215
216 @Override
217 public void complete(HttpSession session)
218 {
219 AbstractSession s = ((SessionIf)session).getSession();
220 s.complete();
221 }
222
223
224 @Override
225 public void doStart() throws Exception
226 {
227 _context=ContextHandler.getCurrentContext();
228 _loader=Thread.currentThread().getContextClassLoader();
229
230 final Server server=getSessionHandler().getServer();
231 synchronized (server)
232 {
233 if (_sessionIdManager==null)
234 {
235 _sessionIdManager=server.getSessionIdManager();
236 if (_sessionIdManager==null)
237 {
238
239
240
241
242 ClassLoader serverLoader = server.getClass().getClassLoader();
243 try
244 {
245 Thread.currentThread().setContextClassLoader(serverLoader);
246 _sessionIdManager=new HashSessionIdManager();
247 server.setSessionIdManager(_sessionIdManager);
248 server.manage(_sessionIdManager);
249 _sessionIdManager.start();
250 }
251 finally
252 {
253 Thread.currentThread().setContextClassLoader(_loader);
254 }
255 }
256
257
258 addBean(_sessionIdManager,false);
259 }
260 }
261
262
263
264 if (_context!=null)
265 {
266 String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
267 if (tmp!=null)
268 _sessionCookie=tmp;
269
270 tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty);
271 if (tmp!=null)
272 setSessionIdPathParameterName(tmp);
273
274
275 if (_maxCookieAge==-1)
276 {
277 tmp=_context.getInitParameter(SessionManager.__MaxAgeProperty);
278 if (tmp!=null)
279 _maxCookieAge=Integer.parseInt(tmp.trim());
280 }
281
282
283 if (_sessionDomain==null)
284 _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
285
286
287 if (_sessionPath==null)
288 _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
289
290 tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding);
291 if (tmp!=null)
292 _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
293 }
294
295 super.doStart();
296 }
297
298
299 @Override
300 public void doStop() throws Exception
301 {
302 super.doStop();
303
304 shutdownSessions();
305
306 _loader=null;
307 }
308
309
310
311
312
313 @Override
314 @ManagedAttribute("true if cookies use the http only flag")
315 public boolean getHttpOnly()
316 {
317 return _httpOnly;
318 }
319
320
321 @Override
322 public HttpSession getHttpSession(String nodeId)
323 {
324 String cluster_id = getSessionIdManager().getClusterId(nodeId);
325
326 AbstractSession session = getSession(cluster_id);
327 if (session!=null && !session.getNodeId().equals(nodeId))
328 session.setIdChanged(true);
329 return session;
330 }
331
332
333
334
335
336 @Override
337 @ManagedAttribute("Session ID Manager")
338 public SessionIdManager getSessionIdManager()
339 {
340 return _sessionIdManager;
341 }
342
343
344
345
346
347
348 @Override
349 @ManagedAttribute("defailt maximum time a session may be idle for (in s)")
350 public int getMaxInactiveInterval()
351 {
352 return _dftMaxIdleSecs;
353 }
354
355
356
357
358
359 @ManagedAttribute("maximum number of simultaneous sessions")
360 public int getSessionsMax()
361 {
362 return (int)_sessionsStats.getMax();
363 }
364
365
366
367
368
369 @ManagedAttribute("total number of sessions")
370 public int getSessionsTotal()
371 {
372 return (int)_sessionsStats.getTotal();
373 }
374
375
376 @ManagedAttribute("time before a session cookie is re-set (in s)")
377 public int getRefreshCookieAge()
378 {
379 return _refreshCookieAge;
380 }
381
382
383
384
385
386
387
388 @ManagedAttribute("if true, secure cookie flag is set on session cookies")
389 public boolean getSecureCookies()
390 {
391 return _secureCookies;
392 }
393
394
395
396
397
398 public boolean isSecureRequestOnly()
399 {
400 return _secureRequestOnly;
401 }
402
403
404
405
406
407
408
409 public void setSecureRequestOnly(boolean secureRequestOnly)
410 {
411 _secureRequestOnly = secureRequestOnly;
412 }
413
414
415 @ManagedAttribute("the set session cookie")
416 public String getSessionCookie()
417 {
418 return _sessionCookie;
419 }
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447 @Override
448 public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
449 {
450 if (isUsingCookies())
451 {
452 String sessionPath = (_cookieConfig.getPath()==null) ? contextPath : _cookieConfig.getPath();
453 sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
454 String id = getNodeId(session);
455 HttpCookie cookie = null;
456 if (_sessionComment == null)
457 {
458 cookie = new HttpCookie(
459 _cookieConfig.getName(),
460 id,
461 _cookieConfig.getDomain(),
462 sessionPath,
463 _cookieConfig.getMaxAge(),
464 _cookieConfig.isHttpOnly(),
465 _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));
466 }
467 else
468 {
469 cookie = new HttpCookie(
470 _cookieConfig.getName(),
471 id,
472 _cookieConfig.getDomain(),
473 sessionPath,
474 _cookieConfig.getMaxAge(),
475 _cookieConfig.isHttpOnly(),
476 _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
477 _sessionComment,
478 1);
479 }
480
481 return cookie;
482 }
483 return null;
484 }
485
486 @ManagedAttribute("domain of the session cookie, or null for the default")
487 public String getSessionDomain()
488 {
489 return _sessionDomain;
490 }
491
492
493
494
495
496 public SessionHandler getSessionHandler()
497 {
498 return _sessionHandler;
499 }
500
501
502 @ManagedAttribute("number of currently active sessions")
503 public int getSessions()
504 {
505 return (int)_sessionsStats.getCurrent();
506 }
507
508
509 @Override
510 @ManagedAttribute("name of use for URL session tracking")
511 public String getSessionIdPathParameterName()
512 {
513 return _sessionIdPathParameterName;
514 }
515
516
517 @Override
518 public String getSessionIdPathParameterNamePrefix()
519 {
520 return _sessionIdPathParameterNamePrefix;
521 }
522
523
524
525
526
527 @Override
528 public boolean isUsingCookies()
529 {
530 return _usingCookies;
531 }
532
533
534 @Override
535 public boolean isValid(HttpSession session)
536 {
537 AbstractSession s = ((SessionIf)session).getSession();
538 return s.isValid();
539 }
540
541
542 @Override
543 public String getClusterId(HttpSession session)
544 {
545 AbstractSession s = ((SessionIf)session).getSession();
546 return s.getClusterId();
547 }
548
549
550 @Override
551 public String getNodeId(HttpSession session)
552 {
553 AbstractSession s = ((SessionIf)session).getSession();
554 return s.getNodeId();
555 }
556
557
558
559
560
561 @Override
562 public HttpSession newHttpSession(HttpServletRequest request)
563 {
564 AbstractSession session=newSession(request);
565 session.setMaxInactiveInterval(_dftMaxIdleSecs);
566 addSession(session,true);
567 return session;
568 }
569
570
571 @Override
572 public void removeEventListener(EventListener listener)
573 {
574 if (listener instanceof HttpSessionAttributeListener)
575 _sessionAttributeListeners.remove(listener);
576 if (listener instanceof HttpSessionListener)
577 _sessionListeners.remove(listener);
578 }
579
580
581
582
583
584 @ManagedOperation(value="reset statistics", impact="ACTION")
585 public void statsReset()
586 {
587 _sessionsStats.reset(getSessions());
588 _sessionTimeStats.reset();
589 }
590
591
592
593
594
595
596 public void setHttpOnly(boolean httpOnly)
597 {
598 _httpOnly=httpOnly;
599 }
600
601
602
603
604
605 @Override
606 public void setSessionIdManager(SessionIdManager metaManager)
607 {
608 updateBean(_sessionIdManager, metaManager);
609 _sessionIdManager=metaManager;
610 }
611
612
613
614
615
616 @Override
617 public void setMaxInactiveInterval(int seconds)
618 {
619 _dftMaxIdleSecs=seconds;
620 }
621
622
623 public void setRefreshCookieAge(int ageInSeconds)
624 {
625 _refreshCookieAge=ageInSeconds;
626 }
627
628
629 public void setSessionCookie(String cookieName)
630 {
631 _sessionCookie=cookieName;
632 }
633
634
635
636
637
638
639 @Override
640 public void setSessionHandler(SessionHandler sessionHandler)
641 {
642 _sessionHandler=sessionHandler;
643 }
644
645
646
647 @Override
648 public void setSessionIdPathParameterName(String param)
649 {
650 _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
651 _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
652 }
653
654
655
656
657
658 public void setUsingCookies(boolean usingCookies)
659 {
660 _usingCookies=usingCookies;
661 }
662
663
664 protected abstract void addSession(AbstractSession session);
665
666
667
668
669
670
671 protected void addSession(AbstractSession session, boolean created)
672 {
673 synchronized (_sessionIdManager)
674 {
675 _sessionIdManager.addSession(session);
676 addSession(session);
677 }
678
679 if (created)
680 {
681 _sessionsStats.increment();
682 if (_sessionListeners!=null)
683 {
684 HttpSessionEvent event=new HttpSessionEvent(session);
685 for (HttpSessionListener listener : _sessionListeners)
686 listener.sessionCreated(event);
687 }
688 }
689 }
690
691
692
693
694
695
696
697 public abstract AbstractSession getSession(String idInCluster);
698
699
700
701
702
703
704 protected abstract void shutdownSessions() throws Exception;
705
706
707
708
709
710
711
712
713 protected abstract AbstractSession newSession(HttpServletRequest request);
714
715
716
717
718
719
720 public boolean isNodeIdInSessionId()
721 {
722 return _nodeIdInSessionId;
723 }
724
725
726
727
728
729 public void setNodeIdInSessionId(boolean nodeIdInSessionId)
730 {
731 _nodeIdInSessionId=nodeIdInSessionId;
732 }
733
734
735
736
737
738
739
740 public void removeSession(HttpSession session, boolean invalidate)
741 {
742 AbstractSession s = ((SessionIf)session).getSession();
743 removeSession(s,invalidate);
744 }
745
746
747
748
749
750
751
752 public boolean removeSession(AbstractSession session, boolean invalidate)
753 {
754
755 boolean removed = removeSession(session.getClusterId());
756
757 if (removed)
758 {
759 _sessionsStats.decrement();
760 _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
761
762
763 _sessionIdManager.removeSession(session);
764 if (invalidate)
765 _sessionIdManager.invalidateAll(session.getClusterId());
766
767 if (invalidate && _sessionListeners!=null)
768 {
769 HttpSessionEvent event=new HttpSessionEvent(session);
770 for (int i = _sessionListeners.size()-1; i>=0; i--)
771 {
772 _sessionListeners.get(i).sessionDestroyed(event);
773 }
774 }
775 }
776
777 return removed;
778 }
779
780
781 protected abstract boolean removeSession(String idInCluster);
782
783
784
785
786
787 @ManagedAttribute("maximum amount of time sessions have remained active (in s)")
788 public long getSessionTimeMax()
789 {
790 return _sessionTimeStats.getMax();
791 }
792
793
794 @Override
795 public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
796 {
797 return __defaultSessionTrackingModes;
798 }
799
800
801 @Override
802 public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
803 {
804 return Collections.unmodifiableSet(_sessionTrackingModes);
805 }
806
807
808 @Override
809 public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
810 {
811 _sessionTrackingModes=new HashSet<SessionTrackingMode>(sessionTrackingModes);
812 _usingCookies=_sessionTrackingModes.contains(SessionTrackingMode.COOKIE);
813 _usingURLs=_sessionTrackingModes.contains(SessionTrackingMode.URL);
814 }
815
816
817 @Override
818 public boolean isUsingURLs()
819 {
820 return _usingURLs;
821 }
822
823
824 @Override
825 public SessionCookieConfig getSessionCookieConfig()
826 {
827 return _cookieConfig;
828 }
829
830
831 private SessionCookieConfig _cookieConfig =
832 new CookieConfig();
833
834
835
836
837
838
839 @ManagedAttribute("total time sessions have remained valid")
840 public long getSessionTimeTotal()
841 {
842 return _sessionTimeStats.getTotal();
843 }
844
845
846
847
848
849 @ManagedAttribute("mean time sessions remain valid (in s)")
850 public double getSessionTimeMean()
851 {
852 return _sessionTimeStats.getMean();
853 }
854
855
856
857
858
859 @ManagedAttribute("standard deviation a session remained valid (in s)")
860 public double getSessionTimeStdDev()
861 {
862 return _sessionTimeStats.getStdDev();
863 }
864
865
866
867
868
869 @Override
870 @ManagedAttribute("check remote session id encoding")
871 public boolean isCheckingRemoteSessionIdEncoding()
872 {
873 return _checkingRemoteSessionIdEncoding;
874 }
875
876
877
878
879
880 @Override
881 public void setCheckingRemoteSessionIdEncoding(boolean remote)
882 {
883 _checkingRemoteSessionIdEncoding=remote;
884 }
885
886
887
888
889
890
891
892
893
894 @Override
895 public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
896 {
897 if (!_sessionIdListeners.isEmpty())
898 {
899 AbstractSession session = getSession(newClusterId);
900 HttpSessionEvent event = new HttpSessionEvent(session);
901 for (HttpSessionIdListener l:_sessionIdListeners)
902 {
903 l.sessionIdChanged(event, oldClusterId);
904 }
905 }
906
907 }
908
909
910
911
912
913
914 public final class CookieConfig implements SessionCookieConfig
915 {
916 @Override
917 public String getComment()
918 {
919 return _sessionComment;
920 }
921
922 @Override
923 public String getDomain()
924 {
925 return _sessionDomain;
926 }
927
928 @Override
929 public int getMaxAge()
930 {
931 return _maxCookieAge;
932 }
933
934 @Override
935 public String getName()
936 {
937 return _sessionCookie;
938 }
939
940 @Override
941 public String getPath()
942 {
943 return _sessionPath;
944 }
945
946 @Override
947 public boolean isHttpOnly()
948 {
949 return _httpOnly;
950 }
951
952 @Override
953 public boolean isSecure()
954 {
955 return _secureCookies;
956 }
957
958 @Override
959 public void setComment(String comment)
960 {
961 if (_context != null && _context.getContextHandler().isAvailable())
962 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
963 _sessionComment = comment;
964 }
965
966 @Override
967 public void setDomain(String domain)
968 {
969 if (_context != null && _context.getContextHandler().isAvailable())
970 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
971 _sessionDomain=domain;
972 }
973
974 @Override
975 public void setHttpOnly(boolean httpOnly)
976 {
977 if (_context != null && _context.getContextHandler().isAvailable())
978 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
979 _httpOnly=httpOnly;
980 }
981
982 @Override
983 public void setMaxAge(int maxAge)
984 {
985 if (_context != null && _context.getContextHandler().isAvailable())
986 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
987 _maxCookieAge=maxAge;
988 }
989
990 @Override
991 public void setName(String name)
992 {
993 if (_context != null && _context.getContextHandler().isAvailable())
994 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
995 _sessionCookie=name;
996 }
997
998 @Override
999 public void setPath(String path)
1000 {
1001 if (_context != null && _context.getContextHandler().isAvailable())
1002 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
1003 _sessionPath=path;
1004 }
1005
1006 @Override
1007 public void setSecure(boolean secure)
1008 {
1009 if (_context != null && _context.getContextHandler().isAvailable())
1010 throw new IllegalStateException("CookieConfig cannot be set after ServletContext is started");
1011 _secureCookies=secure;
1012 }
1013 }
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023 public interface SessionIf extends HttpSession
1024 {
1025 public AbstractSession getSession();
1026 }
1027
1028 public void doSessionAttributeListeners(AbstractSession session, String name, Object old, Object value)
1029 {
1030 if (!_sessionAttributeListeners.isEmpty())
1031 {
1032 HttpSessionBindingEvent event=new HttpSessionBindingEvent(session,name,old==null?value:old);
1033
1034 for (HttpSessionAttributeListener l : _sessionAttributeListeners)
1035 {
1036 if (old==null)
1037 l.attributeAdded(event);
1038 else if (value==null)
1039 l.attributeRemoved(event);
1040 else
1041 l.attributeReplaced(event);
1042 }
1043 }
1044 }
1045
1046 @Override
1047 @Deprecated
1048 public SessionIdManager getMetaManager()
1049 {
1050 throw new UnsupportedOperationException();
1051 }
1052 }