-
Notifications
You must be signed in to change notification settings - Fork 275
/
S.scala
3014 lines (2654 loc) · 98.2 KB
/
S.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
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
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright 2006-2011 WorldWide Conferencing, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.liftweb
package http
import java.util.{Locale, TimeZone, ResourceBundle}
import collection.mutable.{HashMap, ListBuffer}
import xml._
import common._
import actor.LAFuture
import util._
import Helpers._
import js._
import provider._
import http.rest.RestContinuation
class SJBridge {
def s = S
}
/**
* An object representing the current state of the HTTP request and response.
* It uses the DynamicVariable construct such that each thread has its own
* local session info without passing a huge state construct around. The S object
* is initialized by LiftSession on request startup.
*
* @see LiftSession
* @see LiftFilter
*/
object S extends S {
/**
* RewriteHolder holds a partial function that re-writes an incoming request. It is
* used for per-session rewrites, as opposed to global rewrites, which are handled
* by the LiftRules.rewrite RulesSeq. This case class exists so that RewritePFs may
* be manipulated by name. See S.addSessionRewriter for example usage.
*
* @see # sessionRewriter
* @see # addSessionRewriter
* @see # clearSessionRewriter
* @see # removeSessionRewriter
* @see LiftRules # rewrite
*/
case class RewriteHolder(name: String, rewrite: LiftRules.RewritePF)
/**
* DispatchHolder holds a partial function that maps a Req to a LiftResponse. It is
* used for per-session dispatch, as opposed to global dispatch, which are handled
* by the LiftRules.dispatch RulesSeq. This case class exists so that DispatchPFs may
* be manipulated by name. See S.addHighLevelSessionDispatcher for example usage.
*
* @see LiftResponse
* @see LiftRules # dispatch
* @see # highLevelSessionDispatchList
* @see # addHighLevelSessionDispatcher
* @see # removeHighLevelSessionDispatcher
* @see # clearHighLevelSessionDispatcher
*/
case class DispatchHolder(name: String, dispatch: LiftRules.DispatchPF)
/**
* The CookieHolder class holds information about cookies to be sent during
* the session, as well as utility methods for adding and deleting cookies. It
* is used internally.
*
* @see # _responseCookies
* @see # _init
* @see # addCookie
* @see # deleteCookie
* @see # receivedCookies
* @see # responseCookies
* @see # findCookie
*/
case class CookieHolder(inCookies: List[HTTPCookie], outCookies: List[HTTPCookie]) {
def add(in: HTTPCookie) = CookieHolder(inCookies, in :: outCookies.filter(_.name != in.name))
def delete(name: String) = {
add(HTTPCookie(name, "").setMaxAge(0))
}
def delete(old: HTTPCookie) =
add(old.setMaxAge(0).setValue(""))
}
final case class PFPromoter[A, B](pff: () => PartialFunction[A, B])
object PFPromoter {
implicit def fromPF[A, B](pf: PartialFunction[A, B]): PFPromoter[A, B] = new PFPromoter[A, B](() => pf)
implicit def fromFunc[A, B](pff: () => PartialFunction[A, B]): PFPromoter[A, B] = new PFPromoter[A, B](pff)
}
private[http] class ProxyFuncHolder(proxyTo: AFuncHolder, _owner: Box[String]) extends AFuncHolder {
def this(proxyTo: AFuncHolder) = this (proxyTo, Empty)
def owner: Box[String] = _owner or proxyTo.owner
def apply(in: List[String]): Any = proxyTo.apply(in)
override def apply(in: FileParamHolder): Any = proxyTo.apply(in)
override def supportsFileParams_? : Boolean = proxyTo.supportsFileParams_?
override private[http] def lastSeen: Long = proxyTo.lastSeen
override private[http] def lastSeen_=(when: Long) = proxyTo.lastSeen = when
override def sessionLife = proxyTo.sessionLife
}
/**
* Impersonates a function that will be called when uploading files
*/
@serializable
private final class BinFuncHolder(val func: FileParamHolder => Any, val owner: Box[String]) extends AFuncHolder {
def apply(in: List[String]) {logger.info("You attempted to call a 'File Upload' function with a normal parameter. Did you forget to 'enctype' to 'multipart/form-data'?")}
override def apply(in: FileParamHolder) = func(in)
override def supportsFileParams_? : Boolean = true
}
object BinFuncHolder {
def apply(func: FileParamHolder => Any): AFuncHolder = new BinFuncHolder(func, Empty)
def apply(func: FileParamHolder => Any, owner: Box[String]): AFuncHolder = new BinFuncHolder(func, owner)
}
object SFuncHolder {
def apply(func: String => Any): AFuncHolder = new SFuncHolder(func, Empty)
def apply(func: String => Any, owner: Box[String]): AFuncHolder = new SFuncHolder(func, owner)
}
/**
* Impersonates a function that is executed on HTTP requests from client. The function
* takes a String as the only parameter and returns an Any.
*/
@serializable
private final class SFuncHolder(val func: String => Any, val owner: Box[String]) extends AFuncHolder {
def this(func: String => Any) = this (func, Empty)
def apply(in: List[String]): Any = in.headOption.toList.map(func(_))
}
object LFuncHolder {
def apply(func: List[String] => Any): AFuncHolder = new LFuncHolder(func, Empty)
def apply(func: List[String] => Any, owner: Box[String]): AFuncHolder = new LFuncHolder(func, owner)
}
/**
* Impersonates a function that is executed on HTTP requests from client. The function
* takes a List[String] as the only parameter and returns an Any.
*/
@serializable
private final class LFuncHolder(val func: List[String] => Any, val owner: Box[String]) extends AFuncHolder {
def apply(in: List[String]): Any = func(in)
}
object NFuncHolder {
def apply(func: () => Any): AFuncHolder = new NFuncHolder(func, Empty)
def apply(func: () => Any, owner: Box[String]): AFuncHolder = new NFuncHolder(func, owner)
}
/**
* Impersonates a function that is executed on HTTP requests from client. The function
* takes zero arguments and returns an Any.
*/
@serializable
private final class NFuncHolder(val func: () => Any, val owner: Box[String]) extends AFuncHolder {
def apply(in: List[String]): Any = in.headOption.toList.map(s => func())
}
/**
* Abstrats a function that is executed on HTTP requests from client.
*/
@serializable
sealed trait AFuncHolder extends Function1[List[String], Any] {
def owner: Box[String]
def apply(in: List[String]): Any
def apply(in: FileParamHolder): Any = {
error("Attempt to apply file upload to a non-file upload handler")
}
def supportsFileParams_? : Boolean = false
def duplicate(newOwner: String): AFuncHolder = new ProxyFuncHolder(this, Full(newOwner))
@volatile private[this] var _lastSeen: Long = millis
private[http] def lastSeen = _lastSeen
private[http] def lastSeen_=(when: Long) = _lastSeen = when
def sessionLife: Boolean = _sessionLife
private[this] val _sessionLife: Boolean = functionLifespan_?
}
/**
* We create one of these dudes and put it
*/
private[http] final case class PageStateHolder(owner: Box[String], session: LiftSession) extends AFuncHolder {
private val loc = S.location
private val snapshot: Function1[Function0[Any], Any] = RequestVarHandler.generateSnapshotRestorer()
override def sessionLife: Boolean = false
def apply(in: List[String]): Any = {
error("You shouldn't really be calling apply on this dude...")
}
def runInContext[T](f: => T): T = {
val ret = snapshot(() => f).asInstanceOf[T]
ret
}
}
/**
* The companion object that generates AFuncHolders from other functions
*/
object AFuncHolder {
implicit def strToAnyAF(f: String => Any): AFuncHolder =
SFuncHolder(f)
implicit def unitToAF(f: () => Any): AFuncHolder = NFuncHolder(f)
implicit def listStrToAF(f: List[String] => Any): AFuncHolder =
LFuncHolder(f)
implicit def boolToAF(f: Boolean => Any): AFuncHolder =
LFuncHolder(lst => f(lst.foldLeft(false)(
(v, str) => v || Helpers.toBoolean(str))))
}
}
/**
* An object representing the current state of the HTTP request and response.
* It uses the DynamicVariable construct such that each thread has its own
* local session info without passing a huge state construct around. The S object
* is initialized by LiftSession on request startup.
*
* @see LiftSession
* @see LiftFilter
*/
trait S extends HasParams with Loggable {
import S._
/*
* The current session state is contained in the following val/vars:
*/
/**
* Holds the current Req (request) on a per-thread basis.
* @see Req
*/
private val _request = new ThreadGlobal[Req]
/**
* Holds the current functions mappings for this session.
*
* @see # functionMap
* @see # addFunctionMap
* @see # clearFunctionMap
*/
private val __functionMap = new ThreadGlobal[Map[String, AFuncHolder]]
/**
* This is simply a flag so that we know whether or not the state for the S object
* has been initialized for our current scope.
*
* @see # inStatefulScope_ ?
* @see # initIfUninitted
*/
private val inS = (new ThreadGlobal[Boolean]).set(false)
/**
* The _snippetMap holds mappings from snippet names to snippet functions. These mappings
* are valid only in the current request. This val
* is typically modified using the mapSnippet method.
*
* @see # mapSnippet
* @see # locateMappedSnippet
*/
private[http] object _snippetMap extends RequestVar[Map[String, NodeSeq => NodeSeq]](Map())
/**
* Holds the attributes that are set on the current snippet tag. Attributes are available
* to snippet functions via the S.attr and S.attrs methods. The attributes are held in a
* tuple, representing (most recent attributes, full attribute stack).
*
* @see # attrs
* @see # attr
*/
private val _attrs = new ThreadGlobal[(MetaData,List[(Either[String, (String, String)], String)])]
/**
* Holds the per-request LiftSession instance.
*
* @see LiftSession
* @see # session
*/
private val _sessionInfo = new ThreadGlobal[LiftSession]
/**
* Holds a list of ResourceBundles for this request.
*
* @see # resourceBundles
* @see LiftRules # resourceNames
* @see LiftRules # resourceBundleFactories
*/
private val _resBundle = new ThreadGlobal[List[ResourceBundle]]
private[http] object _statefulSnip extends RequestVar[Map[String, DispatchSnippet]](Map())
private val _responseHeaders = new ThreadGlobal[ResponseInfoHolder]
private val _responseCookies = new ThreadGlobal[CookieHolder]
private val _lifeTime = new ThreadGlobal[Boolean]
private val autoCleanUp = new ThreadGlobal[Boolean]
private val _oneShot = new ThreadGlobal[Boolean]
private val _disableTestFuncNames = new ThreadGlobal[Boolean]
private object _exceptionThrown extends TransientRequestVar(false)
private object postFuncs extends TransientRequestVar(new ListBuffer[() => Unit])
/**
* During an Ajax call made on a Comet component, make the Req's
* params available
*/
private object paramsForComet extends TransientRequestVar[Map[String, List[String]]](Map())
/**
* We can now collect JavaScript to append to the outgoing request,
* no matter the format of the outgoing request
*/
private object _jsToAppend extends TransientRequestVar(new ListBuffer[JsCmd])
private object _globalJsToAppend extends TransientRequestVar(new ListBuffer[JsCmd])
/**
* We can now collect Elems to put in the head tag
*/
private object _headTags extends TransientRequestVar(new ListBuffer[Elem])
/**
* We can now collect Elems to put at the end of the body
*/
private object _tailTags extends TransientRequestVar(new ListBuffer[Elem])
private object p_queryLog extends TransientRequestVar(new ListBuffer[(String, Long)])
private object p_notice extends TransientRequestVar(new ListBuffer[(NoticeType.Value, NodeSeq, Box[String])])
/**
* This method returns true if the S object has been initialized for our current scope. If
* the S object has not been initialized then functionality on S will not work.
*/
def inStatefulScope_? : Boolean = inS.value
/**
* Get a Req representing our current HTTP request.
*
* @return A Full(Req) if one has been initialized on the calling thread, Empty otherwise.
*
* @see Req
*/
def request: Box[Req] = (Box !! _request.value) or CurrentReq.box
private[http] object CurrentLocation extends RequestVar[Box[sitemap.Loc[_]]](request.flatMap(_.location))
def location: Box[sitemap.Loc[_]] = CurrentLocation.is or {
//try again in case CurrentLocation was accessed before the request was available
request flatMap { r => CurrentLocation(r.location) }
}
/**
* An exception was thrown during the processing of this request.
* This is tested to see if the transaction should be rolled back
*/
def assertExceptionThrown() {_exceptionThrown.set(true)}
/**
* Was an exception thrown during the processing of the current request?
*/
def exceptionThrown_? : Boolean = _exceptionThrown.get
/**
* @return a List of any Cookies that have been set for this Response. If you want
* a specific cookie, use findCookie.
*
* @see net.liftweb.http.provider.HTTPCookie
* @see # findCookie ( String )
* @see # addCookie ( Cookie )
* @see # deleteCookie ( Cookie )
* @see # deleteCookie ( String )
*/
def receivedCookies: List[HTTPCookie] =
for (rc <- Box.legacyNullTest(_responseCookies.value).toList; c <- rc.inCookies)
yield c.clone().asInstanceOf[HTTPCookie]
/**
* Finds a cookie with the given name that was sent in the request.
*
* @param name - the name of the cookie to find
*
* @return Full ( cookie ) if the cookie exists, Empty otherwise
*
* @see net.liftweb.http.provider.HTTPCookie
* @see # receivedCookies
* @see # addCookie ( Cookie )
* @see # deleteCookie ( Cookie )
* @see # deleteCookie ( String )
*/
def findCookie(name: String): Box[HTTPCookie] =
Box.legacyNullTest(_responseCookies.value).flatMap(
rc => Box(rc.inCookies.filter(_.name == name)).
map(_.clone().asInstanceOf[HTTPCookie]))
/**
* Get the cookie value for the given cookie
*/
def cookieValue(name: String): Box[String] =
for {
cookie <- findCookie(name)
value <- cookie.value
} yield value
def currentCometActor: Box[LiftCometActor] = CurrentCometActor.box
/**
* @return a List of any Cookies that have been added to the response to be sent
* back to the user. If you want the cookies that were sent with the request, see
* receivedCookies.
*
* @see net.liftweb.http.provider.HTTPCookie
* @see # receivedCookies
*/
def responseCookies: List[HTTPCookie] = Box.legacyNullTest(_responseCookies.value).
toList.flatMap(_.outCookies)
/**
* Adds a Cookie to the List[Cookies] that will be sent with the Response.
*
* If you wish to delete a Cookie as part of the Response, use the deleteCookie
* method.
*
* An example of adding and removing a Cookie is:
*
* <pre name="code" class="scala" >
* import net.liftweb.http.provider.HTTPCookie
*
* class MySnippet {
* final val cookieName = "Fred"
*
* def cookieDemo (xhtml : NodeSeq) : NodeSeq = {
* var cookieVal = S.findCookie(cookieName).map(_.getvalue) openOr ""
*
* def setCookie() {
* val cookie = HTTPCookie(cookieName, cookieVal).setMaxAge(3600) // 3600 seconds, or one hour
* S.addCookie(cookie)
* }
*
* bind("cookie", xhtml,
* "value" -> SHtml.text(cookieVal, cookieVal = _),
* "add" -> SHtml.submit("Add", setCookie)
* "remove" -> SHtml.link(S.uri, () => S.deleteCookie(cookieName), "Delete Cookie")
* )
* }
* }
* </pre>
*
* @see net.liftweb.http.provider.HTTPCookie
* @see # deleteCookie ( Cookie )
* @see # deleteCookie ( String )
* @see # responseCookies
*/
def addCookie(cookie: HTTPCookie) {
Box.legacyNullTest(_responseCookies.value).foreach(rc =>
_responseCookies.set(rc.add(cookie))
)
}
/**
* Deletes the cookie from the user's browser.
*
* @param cookie the Cookie to delete
*
* @see net.liftweb.http.provider.HTTPCookie
* @see # addCookie ( Cookie )
* @see # deleteCookie ( String )
*/
def deleteCookie(cookie: HTTPCookie) {
Box.legacyNullTest(_responseCookies.value).foreach(rc =>
_responseCookies.set(rc.delete(cookie))
)
}
/**
* Deletes the cookie from the user's browser.
*
* @param name the name of the cookie to delete
*
* @see net.liftweb.http.provider.HTTPCookie
* @see # addCookie ( Cookie )
* @see # deleteCookie ( Cookie )
*/
def deleteCookie(name: String) {
Box.legacyNullTest(_responseCookies.value).foreach(rc =>
_responseCookies.set(rc.delete(name))
)
}
/**
* Find a template based on the snippet attribute "template"
*/
// TODO: Is this used anywhere? - DCB
def templateFromTemplateAttr: Box[NodeSeq] =
for (templateName <- attr("template") ?~ "Template Attribute missing";
val tmplList = templateName.roboSplit("/");
template <- Templates(tmplList) ?~
"couldn't find template") yield template
/**
* Returns the Locale for this request based on the LiftRules.localeCalculator
* method.
*
* @see LiftRules.localeCalculator ( HTTPRequest )
* @see java.util.Locale
*/
def locale: Locale = LiftRules.localeCalculator(containerRequest)
/**
* Return the current timezone based on the LiftRules.timeZoneCalculator
* method.
*
* @see LiftRules.timeZoneCalculator ( HTTPRequest )
* @see java.util.TimeZone
*/
def timeZone: TimeZone =
LiftRules.timeZoneCalculator(containerRequest)
/**
* @return <code>true</code> if this response should be rendered in
* IE6/IE7 compatibility mode.
*
* @see LiftSession.ieMode
* @see LiftRules.calcIEMode
* @see Req.isIE6
* @see Req.isIE7
* @see Req.isIE8
* @see Req.isIE
*/
def ieMode: Boolean = session.map(_.ieMode.is) openOr false // LiftRules.calcIEMode()
/**
* Get the current instance of HtmlProperties
*/
def htmlProperties: HtmlProperties = {
session.map(_.requestHtmlProperties.is) openOr
LiftRules.htmlProperties.vend(
S.request openOr Req.nil
)
}
/**
* Return a List of the LiftRules.DispatchPF functions that are set for this
* session. See addHighLevelSessionDispatcher for an example of how these are
* used.
*
* @see LiftRules.DispatchPF
* @see # addHighLevelSessionDispatcher ( String, LiftRules.DispatchPF )
* @see # removeHighLevelSessionDispatcher ( String )
* @see # clearHighLevelSessionDispatcher
*/
def highLevelSessionDispatcher: List[LiftRules.DispatchPF] = highLevelSessionDispatchList.map(_.dispatch)
/**
* Return the list of DispatchHolders set for this session.
*
* @see DispatchHolder
*/
def highLevelSessionDispatchList: List[DispatchHolder] =
session map (_.highLevelSessionDispatcher.toList.map(t => DispatchHolder(t._1, t._2))) openOr Nil
/**
* Adds a dispatch function for the current session, as opposed to a global
* dispatch through LiftRules.dispatch. An example would be if we wanted a user
* to be able to download a document only when logged in. First, we define
* a dispatch function to handle the download, specific to a given user:
*
* <pre name="code" class="scala" >
* def getDocument(userId : Long)() : Box[LiftResponse] = { ... }
* </pre>
*
* Then, in the login/logout handling snippets, we could install and remove
* the custom dispatch as appropriate:
*
* <pre name="code" class="scala" >
* def login(xhtml : NodeSeq) : NodeSeq = {
* def doAuth () {
* ...
* if (user.loggedIn_?) {
* S.addHighLevelSessionDispatcher("docDownload", {
* case Req(List("download", "docs"), _, _) => getDocument(user.id)
* } )
* }
* }
*
* def logout(xhtml : NodeSeq) : NodeSeq = {
* def doLogout () {
* ...
* S.removeHighLevelSessionDispatcher("docDownload")
* // or, if more than one dispatch has been installed, this is simpler
* S.clearHighLevelSessionDispatcher
* }
* }
* </pre>
*
* It's important to note that per-session dispatch takes precedence over
* LiftRules.dispatch, so you can override things set there.
*
* @param name A name for the dispatch. This can be used to remove it later by name.
* @param disp The dispatch partial function
*
* @see LiftRules.DispatchPF
* @see LiftRules.dispatch
* @see # removeHighLevelSessionDispatcher
* @see # clearHighLevelSessionDispatcher
*/
def addHighLevelSessionDispatcher(name: String, disp: LiftRules.DispatchPF) =
session map (_.highLevelSessionDispatcher += (name -> disp))
/**
* Removes a custom dispatch function for the current session. See
* addHighLevelSessionDispatcher for an example of usage.
*
* @param name The name of the custom dispatch to be removed.
*
* @see LiftRules.DispatchPF
* @see LiftRules.dispatch
* @see # addHighLevelSessionDispatcher
* @see # clearHighLevelSessionDispatcher
*/
def removeHighLevelSessionDispatcher(name: String) =
session map (_.highLevelSessionDispatcher -= name)
/**
* Clears all custom dispatch functions from the current session. See
* addHighLevelSessionDispatcher for an example of usage.
*
* @see LiftRules.DispatchPF
* @see LiftRules.dispatch
* @see # addHighLevelSessionDispatcher
* @see # clearHighLevelSessionDispatcher
*/
def clearHighLevelSessionDispatcher = session map (_.highLevelSessionDispatcher.clear)
/**
* Return the list of RewriteHolders set for this session. See addSessionRewriter
* for an example of how to use per-session rewrites.
*
* @see RewriteHolder
* @see LiftRules # rewrite
*/
def sessionRewriter: List[RewriteHolder] =
session map (_.sessionRewriter.toList.map(t => RewriteHolder(t._1, t._2))) openOr Nil
/**
* Adds a per-session rewrite function. This can be used if you only want a particular rewrite
* to be valid within a given session. Per-session rewrites take priority over rewrites set in
* LiftRules.rewrite, so you can use this mechanism to override global functionality. For example,
* you could set up a global rule to make requests for the "account profile" page go back to the home
* page by default:
*
* <pre name="code" class="scala" >
* package bootstrap.liftweb
* ... imports ...
* class Boot {
* def boot {
* LiftRules.rewrite.append {
* case RewriteRequest(ParsePath(List("profile")), _, _, _) =>
* RewriteResponse(List("index"))
* }
* }
* }
* </pre>
*
* Then, in your login snippet, you could set up a per-session rewrite to the correct template:
*
* <pre name="code" class="scala" >
* def loginSnippet (xhtml : NodeSeq) : NodeSeq = {
* ...
* def doLogin () {
* ...
* S.addSessionRewriter("profile", {
* case RewriteRequest(ParsePath(List("profile")), _, _, _) =>
* RewriteResponse(List("viewProfile"), Map("user" -> user.id))
* }
* ...
* }
* ...
* }
* </pre>
*
* And in your logout snippet you can remove the rewrite:
*
* <pre name="code" class="scala" >
* def doLogout () {
* S.removeSessionRewriter("profile")
* // or
* S.clearSessionRewriter
* }
* </pre>
*
*
* @param name A name for the rewrite function so that it can be replaced or deleted later.
* @rw The rewrite partial function
*
* @see LiftRules.rewrite
* @see # sessionRewriter
* @see # removeSessionRewriter
* @see # clearSessionRewriter
*/
def addSessionRewriter(name: String, rw: LiftRules.RewritePF) =
session map (_.sessionRewriter += (name -> rw))
/**
* Removes the given per-session rewriter. See addSessionRewriter for an
* example of usage.
*
* @see LiftRules.rewrite
* @see # addSessionRewriter
* @see # clearSessionRewriter
*/
def removeSessionRewriter(name: String) =
session map (_.sessionRewriter -= name)
/**
* Put the given Elem in the head tag. The Elems
* will be de-dupped so no problems adding the
* same style tag multiple times
*/
def putInHead(elem: Elem): Unit = _headTags.is += elem
/**
* Get the accumulated Elems for head
*
* @see putInHead
*/
def forHead(): List[Elem] = _headTags.is.toList
/**
* Put the given Elem at the end of the body tag.
*/
def putAtEndOfBody(elem: Elem): Unit = _tailTags.is += elem
/**
* Get the accumulated Elems for the end of the body
*
* @see putAtEndOfBody
*/
def atEndOfBody(): List[Elem] = _tailTags.is.toList
/**
* Sometimes it's helpful to accumute JavaScript as part of servicing
* a request. For example, you may want to accumulate the JavaScript
* as part of an Ajax response or a Comet Rendering or
* as part of a regular HTML rendering. Call `S.appendJs(jsCmd)`.
* The accumulated Javascript will be emitted as part of the response,
* wrapped in an `OnLoad` to ensure that it executes after
* the entire dom is available. If for some reason you need to run
* javascript at the top-level scope, use appendGlobalJs.
*/
def appendJs(js: JsCmd): Unit = _jsToAppend.is += js
/**
* Sometimes it's helpful to accumute JavaScript as part of servicing
* a request. For example, you may want to accumulate the JavaScript
* as part of an Ajax response or a Comet Rendering or
* as part of a regular HTML rendering. Call `S.appendJs(jsCmd)`.
* The accumulated Javascript will be emitted as part of the response,
* wrapped in an `OnLoad` to ensure that it executes after
* the entire dom is available. If for some reason you need to run
* javascript at the top-level scope, use `appendGlobalJs`.
*/
def appendJs(js: Seq[JsCmd]): Unit = _jsToAppend.is ++= js
/**
* Add javascript to the page rendering that
* will execute in the global scope.
* Usually you should use `appendJs`, so that the javascript
* runs after the entire dom is available. If you need to
* declare a global var or you want javascript to execute
* immediately with no guarantee that the entire dom is available,
* you may use `appendGlobalJs`.
*/
def appendGlobalJs(js: JsCmd*): Unit = _globalJsToAppend.is ++= js
/**
* Get the accumulated JavaScript
*
* @see appendJs
*/
def jsToAppend(): List[JsCmd] = {
import js.JsCmds._
_globalJsToAppend.is.toList ::: (
S.session.map( sess =>
sess.postPageJavaScript(RenderVersion.get ::
S.currentCometActor.map(_.uniqueId).toList)
) match {
case Full(xs) if !xs.isEmpty => List(OnLoad(_jsToAppend.is.toList ::: xs))
case _ => _jsToAppend.is.toList match {
case Nil => Nil
case xs => List(OnLoad(xs))
}
}
)
}
/**
* Clears the per-session rewrite table. See addSessionRewriter for an
* example of usage.
*
* @see LiftRules.rewrite
* @see # addSessionRewriter
* @see # removeSessionRewriter
*/
def clearSessionRewriter = session map (_.sessionRewriter.clear)
/**
* Test the current request to see if it's a POST. This is a thin wrapper
* over Req.post_?
*
* @return <code>true</code> if the request is a POST request, <code>false</code>
* otherwise.
*/
def post_? = request.map(_.post_?).openOr(false)
/**
* Localize the incoming string based on a resource bundle for the current locale. The
* localized string is converted to an XML element if necessary via the <code>LiftRules.localizeStringToXml</code>
* function (the default behavior is to wrap it in a Text element). If the lookup fails for a given resource
* bundle (e.g. a null is returned), then the <code>LiftRules.localizationLookupFailureNotice</code>
* function is called with the input string and locale.
*
* @param str the string or ID to localize
*
* @return A Full box containing the localized XML or Empty if there's no way to do localization
*
* @see # locale
* @see # resourceBundles
* @see LiftRules.localizeStringToXml
* @see LiftRules.localizationLookupFailureNotice
* @see # loc ( String, NodeSeq )
*/
def loc(str: String): Box[NodeSeq] =
resourceBundles.flatMap(r => tryo(r.getObject(str) match {
case null => LiftRules.localizationLookupFailureNotice.foreach(_(str, locale)); Empty
case s: String => Full(LiftRules.localizeStringToXml(s))
case g: Group => Full(g)
case e: Elem => Full(e)
case n: Node => Full(n)
case ns: NodeSeq => Full(ns)
case x => Full(Text(x.toString))
}).flatMap(s => s)).find(e => true)
/**
* Localize the incoming string based on a resource bundle for the current locale,
* with a default value to to return if localization fails.
*
* @param str the string or ID to localize
* @param dflt the default string to return if localization fails
*
* @return the localized XHTML or default value
*
* @see # loc ( String )
*/
def loc(str: String, dflt: NodeSeq): NodeSeq = loc(str).openOr(dflt)
/**
* Localize the incoming string based on a resource bundle for the current locale. The
* localized string is converted to an XML element if necessary via the <code>LiftRules.localizeStringToXml</code>
* function (the default behavior is to wrap it in a Text element). If the lookup fails for a given resource
* bundle (e.g. a null is returned), then the <code>LiftRules.localizationLookupFailureNotice</code>
* function is called with the input string and locale. The
* function is applied to the result/
*
* @param str the string or ID to localize
* @param xform the function that transforms the NodeSeq
*
* @return A Full box containing the localized XML or Empty if there's no way to do localization
*
* @see # locale
* @see # resourceBundles
* @see LiftRules.localizeStringToXml
* @see LiftRules.localizationLookupFailureNotice
* @see # loc ( String, NodeSeq )
*/
def loc(str: String, xform: NodeSeq => NodeSeq): Box[NodeSeq] =
S.loc(str).map(xform)
/**
* Get a List of the resource bundles for the current locale. The resource bundles are defined by
* the LiftRules.resourceNames and LiftRules.resourceBundleFactories variables.
*
* @see LiftRules.resourceNames
* @see LiftRules.resourceBundleFactories
*/
def resourceBundles: List[ResourceBundle] = resourceBundles(locale)
def resourceBundles(loc: Locale): List[ResourceBundle] = {
_resBundle.box match {
case Full(Nil) => {
_resBundle.set(
LiftRules.resourceForCurrentLoc.vend() :::
LiftRules.resourceNames.flatMap(name => tryo{
if (Props.devMode) {
tryo{
val clz = this.getClass.getClassLoader.loadClass("java.util.ResourceBundle")
val meth = clz.getDeclaredMethods.
filter{m => m.getName == "clearCache" && m.getParameterTypes.length == 0}.
toList.head
meth.invoke(null)
}
}
List(ResourceBundle.getBundle(name, loc))
}.openOr(
NamedPF.applyBox((name, loc), LiftRules.resourceBundleFactories.toList).map(List(_)) openOr Nil
)))
_resBundle.value
}
case Full(bundles) => bundles
case _ => throw new IllegalStateException("Attempted to use resource bundles outside of an initialized S scope. " +
"S only usable when initialized, such as during request processing. " +
"Did you call S.? from Boot?")
}
}
private object _liftCoreResBundle extends
RequestVar[Box[ResourceBundle]](tryo(ResourceBundle.getBundle(LiftRules.liftCoreResourceName, locale)))
/**
* Get the lift core resource bundle for the current locale as defined by the
* LiftRules.liftCoreResourceName varibale.
*
* @see LiftRules.liftCoreResourceName
*/
def liftCoreResourceBundle: Box[ResourceBundle] = _liftCoreResBundle.is
/**
* Get a localized string or return the original string.
*
* @param str the string to localize
*
* @return the localized version of the string
*
* @see # resourceBundles
*/
def ?(str: String): String = ?!(str, resourceBundles)
/**
* Get a localized string or return the original string.
*
* @param str the string to localize
*
* @param locale specific locale that should be used to localize this string
*
* @return the localized version of the string
*
* @see # resourceBundles
*/
def ?(str: String, locale: Locale): String = ?!(str, resourceBundles(locale))
/**
* Attempt to localize and then format the given string. This uses the String.format method
* to format the localized string.
*
* @param str the string to localize