diff --git a/core/common/src/main/scala/net/liftweb/common/Logging.scala b/core/common/src/main/scala/net/liftweb/common/Logging.scala index 5f60b7f405..883d09dd51 100644 --- a/core/common/src/main/scala/net/liftweb/common/Logging.scala +++ b/core/common/src/main/scala/net/liftweb/common/Logging.scala @@ -291,7 +291,16 @@ class WrappedLogger(l: SLF4JLogger) extends Logger { * `val` that will be a `[[Logger]]` instance. */ trait Loggable { - @transient protected val logger = Logger(this.getClass) + // After deserializstion, a transient field is left null because the constructor is not invoked. + // Hence this needs to be a private variable so we can reinit upon deserialization + @transient private[this] var _logger = Logger(this.getClass) + + protected def logger = _logger + + // Think of this as the constructor for deserialization. You are now instantiated, set your state as you wish. + private def readObject(in: java.io.ObjectInputStream): Unit = { + _logger = Logger(this.getClass) + } } /** diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index 61be1871a5..1003e8d6e1 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -29,7 +29,7 @@ import scala.collection.JavaConverters._ * implicit val formats = net.liftweb.json.DefaultFormats * */ -trait Formats { self: Formats => +trait Formats extends Serializable { self: Formats => val dateFormat: DateFormat val typeHints: TypeHints = NoTypeHints val customSerializers: List[Serializer[_]] = Nil @@ -49,7 +49,7 @@ trait Formats { self: Formats => val typeHintFieldName = "jsonClass" /** - * Parameter name reading strategy. By deafult 'paranamer' is used. + * Parameter name reading strategy. By default 'paranamer' is used. */ val parameterNameReader: ParameterNameReader = Meta.ParanamerReader @@ -126,7 +126,7 @@ trait DateFormat { def format(d: Date): String } -trait Serializer[A] { +trait Serializer[A] extends Serializable { def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), A] def serialize(implicit format: Formats): PartialFunction[Any, JValue] } @@ -250,6 +250,8 @@ case class FullTypeHints(hints: List[Class[_]]) extends TypeHints { /** Default date format is UTC time. */ object DefaultFormats extends DefaultFormats { + // In the event a formats gets serialized, we should expect to be deserialized where this thread local can re-init itself + @transient val losslessDate = new ThreadLocal(new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")) val UTC = TimeZone.getTimeZone("UTC") } @@ -257,7 +259,7 @@ object DefaultFormats extends DefaultFormats { trait DefaultFormats extends Formats { import java.text.{ParseException, SimpleDateFormat} - val dateFormat = new DateFormat { + val dateFormat = new DateFormat with Serializable { def parse(s: String) = try { Some(formatter.parse(s)) } catch { diff --git a/core/json/src/main/scala/net/liftweb/json/Meta.scala b/core/json/src/main/scala/net/liftweb/json/Meta.scala index 7161f14c9f..93cb7a503a 100644 --- a/core/json/src/main/scala/net/liftweb/json/Meta.scala +++ b/core/json/src/main/scala/net/liftweb/json/Meta.scala @@ -92,7 +92,7 @@ private[json] object Meta { private val unmangledNames = new Memo[String, String] private val paranamer = new CachingParanamer(new BytecodeReadingParanamer) - object ParanamerReader extends ParameterNameReader { + object ParanamerReader extends ParameterNameReader with Serializable { def lookupParameterNames(constructor: JConstructor[_]): Traversable[String] = paranamer.lookupParameterNames(constructor) } diff --git a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala index 88d87c350b..dbf3bd4ec1 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/LiftSession.scala @@ -960,7 +960,7 @@ class LiftSession(private[http] val _contextPath: String, val underlyingId: Stri // snapshot for ajax calls nmessageCallback.put(S.renderVersion, - S.PageStateHolder(Full(S.renderVersion), this)) + S.PageStateHolder(Full(S.renderVersion))) // But we need to update the function map because there // may be addition functions created during the JsToAppend processing diff --git a/web/webkit/src/main/scala/net/liftweb/http/S.scala b/web/webkit/src/main/scala/net/liftweb/http/S.scala index 3bb4617da6..543244078f 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/S.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/S.scala @@ -214,8 +214,7 @@ object S extends S { /** * 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[http] final case class PageStateHolder(owner: Box[String]) extends AFuncHolder { private val snapshot: Function1[Function0[Any], Any] = RequestVarHandler.generateSnapshotRestorer() override def sessionLife: Boolean = false diff --git a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala index b95458eab2..3737b62640 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/SHtml.scala @@ -324,7 +324,7 @@ trait SHtml extends Loggable { * explicitly capture the template */ def idMemoize(f: IdMemoizeTransform => NodeSeqFuncOrSeqNodeSeqFunc): IdMemoizeTransform = { - new IdMemoizeTransform { + new IdMemoizeTransform with Serializable { var latestElem: Elem = var latestKids: NodeSeq = NodeSeq.Empty diff --git a/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala b/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala index cfa391ea0b..7a6a65fd5e 100644 --- a/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala +++ b/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala @@ -62,7 +62,7 @@ import xml.{NodeSeq, Elem} * } * */ -trait StatefulSnippet extends DispatchSnippet { +trait StatefulSnippet extends DispatchSnippet with Serializable { private[this] var _names: Set[String] = Set() def addName(name: String) { synchronized { diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala index b8cb3e5996..10b4cea8aa 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/Loc.scala @@ -28,7 +28,7 @@ import scala.xml.{NodeSeq, Text} /** * A menu location */ -trait Loc[T] { +trait Loc[T] extends Serializable { def name: String def link: Loc.Link[T] @@ -459,6 +459,26 @@ trait Loc[T] { params.foreach(_ onCreate(this)) } + protected def serializeMe(out: java.io.ObjectOutputStream): Unit = out.writeObject(SerializableLoc(name)) + + private def writeObject(out: java.io.ObjectOutputStream): Unit = serializeMe(out) + + protected def readResolve(): Any = { + SiteMap.findLoc(name).openOrThrowException( + """Attempted to deserialize Loc() but SiteMap.findLoc($name) returned Empty. + |This should only be expected if the serialized instance of Loc originated from a different version of your application code. + """.stripMargin + ) + } +} + +private case class SerializableLoc[T](name: String) extends Loc[T] { + override def link: Loc.Link[T] = ??? + override def text: Loc.LinkText[T] = ??? + override def defaultValue: Box[T] = ??? + override def params: List[Loc.LocParam[T]] = ??? + + override def toString: String = "SerializableLoc("+name+")" } trait ConvertableLoc[T] { @@ -469,6 +489,8 @@ trait ConvertableLoc[T] { * the Loc in createLink */ def convert(str: String): Box[T] + + private def writeObject(out: java.io.ObjectOutputStream): Unit = serializeMe(out) } @@ -511,6 +533,8 @@ object Loc { override val params = xparams.toList init() + + private def writeObject(out: java.io.ObjectOutputStream): Unit = serializeMe(out) } @@ -838,7 +862,7 @@ object Loc { * that begins with the same path. Useful for opening a set of directories * (for example, help pages) */ - class Link[-T](val uriList: List[String], val matchHead_? : Boolean) extends PartialFunction[Req, Box[Boolean]] { + class Link[-T](val uriList: List[String], val matchHead_? : Boolean) extends PartialFunction[Req, Box[Boolean]] with Serializable { def this(b: List[String]) = this(b, false) def isDefinedAt(req: Req): Boolean = { diff --git a/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala b/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala index eda992efea..9ae60b955e 100644 --- a/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala +++ b/web/webkit/src/main/scala/net/liftweb/sitemap/Menu.scala @@ -655,7 +655,7 @@ case class Menu(loc: Loc[_], private val convertableKids: ConvertableToMenu*) ex } } -final class ParamLocLink[T](path: List[LocPath], headMatch: Boolean, backToList: T => List[String]) extends Loc.Link[T](path.map(_.pathItem), headMatch) { +final class ParamLocLink[T](path: List[LocPath], headMatch: Boolean, backToList: T => List[String]) extends Loc.Link[T](path.map(_.pathItem), headMatch) with Serializable { @tailrec def test(toTest: List[String], path: List[LocPath]): Boolean = {