diff --git a/gradle.properties b/gradle.properties index 2809ca81..cba203ff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,6 @@ JPRO_PLATFORM_VERSION = 0.5.1-SNAPSHOT + JPRO_VERSION = 2024.4.1 JAVAFX_BUILD_VERSION = 17.0.12 JAVAFX_EXAMPLES_VERSION = 23.0.1 diff --git a/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestAppCrawler.scala b/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestAppCrawler.scala index 33358fca..bf86617a 100644 --- a/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestAppCrawler.scala +++ b/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestAppCrawler.scala @@ -33,12 +33,10 @@ class TestAppCrawler { @Test def testCrawlApp(): Unit = { - def app = new RouteNode(null) { - setRoute(Route.empty() + def route = Route.empty() .and(Route.get("/", r => Response.view(new Page1))) - .and(Route.get("/page2", r => Response.view(new Page2)))) - } - val result = AppCrawler.crawlApp("http://localhost", () => app) + .and(Route.get("/page2", r => Response.view(new Page2))) + val result = AppCrawler.crawlRoute("http://localhost", () => route) assert(result.pages.contains("/"), result.pages) assert(result.pages.contains("/page2"), result.pages) @@ -48,17 +46,15 @@ class TestAppCrawler { @Test def testEmptyImage(): Unit = { - def app = new RouteNode(null) { - setRoute(Route.empty() + def route = Route.empty() .and(Route.get("/", r => Response.view(new View { override def title: String = "" override def description: String = "" override def content: all.Node = new ImageView(null: Image) - })))) - } - val result = AppCrawler.crawlApp("http://localhost", () => app) + }))) + val result = AppCrawler.crawlRoute("http://localhost", () => route) } @Test @@ -99,16 +95,19 @@ class TestAppCrawler { } @Test - def testImageInStyle (): Unit = inFX { - val view = new View { + def testImageInStyle (): Unit = { + def view = new View { override def title: String = "" override def description: String = "" val content: Node = new Region() { style = "-fx-background-image: url('/testfiles/test.jpg');" } } - val r = AppCrawler.crawlPage(view) - assert(r.pictures.nonEmpty) + val r = AppCrawler.crawlRoute("http://localhost", () => + Route.empty().and(Route.get("/", r => Response.view(view))) + ) + assert(r.pages.length == 1) + assert(r.reports.head.pictures.nonEmpty) } @Test diff --git a/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestMemoryTester.scala b/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestMemoryTester.scala index abe0552c..7748d6ce 100644 --- a/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestMemoryTester.scala +++ b/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestMemoryTester.scala @@ -11,53 +11,48 @@ class TestMemoryTester { @Test def simpleTest(): Unit = { - def app = new RouteNode(null) { - setRoute(Route.empty() + def route = Route.empty() .and(Route.get("/", r => Response.view(new Page1))) .and(Route.get("/page2", r => Response.view(new Page2))) - .and(Route.get("/page4", r => Response.view(new Page2)))) - } - val result = AppCrawler.crawlApp("http://localhost", () => app) - MemoryTester.testForLeaks(result, () => app) + .and(Route.get("/page4", r => Response.view(new Page2))) + val result = AppCrawler.crawlRoute("http://localhost", () => route) + MemoryTester.testForLeaks(result, () => route) } @Test def simpleFailingTest(): Unit = { val page2 = new Page2 - def app = new RouteNode(null) { - setRoute(Route.empty() + def route = Route.empty() .and(Route.get("/", r => Response.view(new Page1))) .and(Route.get("/page2", r => Response.view(page2))) - .and(Route.get("/page4", r => Response.view(new Page2)))) - } - val result = AppCrawler.crawlApp("http://localhost", () => app) - intercept[Throwable](MemoryTester.testForLeaks(result, () => app)) + .and(Route.get("/page4", r => Response.view(new Page2))) + val result = AppCrawler.crawlRoute("http://localhost", () => route) + intercept[Throwable](MemoryTester.testForLeaks(result, () => route)) } @Test def simpleFailingTest2(): Unit = { var node2 = new Label() - def app = new RouteNode(null) { - setRoute(Route.empty() + def route = Route.empty() .and(Route.get("/", r => Response.view(new Page1))) .and(Route.get("/page2", r => Response.node(node2))) - .and(Route.get("/page4", r => Response.view(new Page2)))) - } + .and(Route.get("/page4", r => Response.view(new Page2))) - val result = AppCrawler.crawlApp("http://localhost", () => app) - intercept[Throwable](MemoryTester.testForLeaks(result, () => app)) + val result = AppCrawler.crawlRoute("http://localhost", () => route) + intercept[Throwable](MemoryTester.testForLeaks(result, () => route)) } + /* @Test def simpleFailingTest3(): Unit = { - val app = inFX(new RouteNode(null) { - setRoute(Route.empty() + val route = inFX(Route.empty() .and(Route.get("/", r => Response.view(new Page1))) .and(Route.get("/page2", r => Response.view(new Page2))) - .and(Route.get("/page4", r => Response.view(new Page2)))) - }) - val result = AppCrawler.crawlApp("http://localhost", () => app) - intercept[Throwable](MemoryTester.testForLeaks(result, () => app)) // fails because the webapp is not collectable + .and(Route.get("/page4", r => Response.view(new Page2))) + ) + val result = AppCrawler.crawlApp("http://localhost", () => route) + intercept[Throwable](MemoryTester.testForLeaks(result, () => route)) // fails because the webapp is not collectable } + */ } diff --git a/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestSitemapGenerator.scala b/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestSitemapGenerator.scala index 8b3c5ff9..4fa8f2e0 100644 --- a/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestSitemapGenerator.scala +++ b/jpro-routing/core-test/src/test/scala/one/jpro/platform/routing/crawl/TestSitemapGenerator.scala @@ -9,29 +9,27 @@ import simplefx.experimental._ class TestSitemapGenerator { @Test def test(): Unit = { - def app = new RouteNode(null) { - setRoute(Route.empty() + def route = Route.empty() .and(get("/", r => Response.view(new Page1))) .and(get("/page2", r => Response.view(new Page2))) .and(get("/page4", r => Response.view(new Page2))) - .and(r => Response.view(new Page1))) - } - val result = AppCrawler.crawlApp("http://localhost", () => app) + .and(r => Response.view(new Page1)) + val result = AppCrawler.crawlRoute("http://localhost", () => route) val sm = SitemapGenerator.createSitemap("http://localhost", result) + println("Crawl Report: " + result) println("SiteMap: " + sm) - assert(sm.contains("http://localhost/page4")) - assert(!sm.contains("http://external/link")) + + assert(sm.contains("http://localhost/page4"), "sitemap did not contain page4") + assert(!sm.contains("http://external/link"), "sitemap contained external link") assert(!sm.contains("mailto")) } @Test def testMailToRedirect(): Unit = { - def app = new RouteNode(null) { - setRoute(Route.empty() - .and(get("/", r => Response.view(pageWithLink(List("/page2", "/page3", "mailto:something"))))) - .and(get("/page2", r => Response.redirect("mailto:something-2")))) - } - val result = AppCrawler.crawlApp("http://localhost", () => app) + def route = Route.empty() + .and(get("/", r => Response.view(pageWithLink(List("/page2", "/page3", "mailto:something"))))) + .and(get("/page2", r => Response.redirect("mailto:something-2"))) + val result = AppCrawler.crawlRoute("http://localhost", () => route) println("got result: " + result) val sm = SitemapGenerator.createSitemap("http://localhost", result) println("SiteMap2: " + sm) diff --git a/jpro-routing/core/src/main/java/module-info.java b/jpro-routing/core/src/main/java/module-info.java index 07a1786b..e294390e 100644 --- a/jpro-routing/core/src/main/java/module-info.java +++ b/jpro-routing/core/src/main/java/module-info.java @@ -18,4 +18,5 @@ exports one.jpro.platform.routing.filter.container; exports one.jpro.platform.routing.sessionmanager; exports one.jpro.platform.routing.extensions.linkheader; + exports one.jpro.platform.routing.server; } \ No newline at end of file diff --git a/jpro-routing/core/src/main/java/one/jpro/platform/routing/server/IgnoreMe.java b/jpro-routing/core/src/main/java/one/jpro/platform/routing/server/IgnoreMe.java new file mode 100644 index 00000000..3ee90685 --- /dev/null +++ b/jpro-routing/core/src/main/java/one/jpro/platform/routing/server/IgnoreMe.java @@ -0,0 +1,7 @@ +package one.jpro.platform.routing.server; + +/** + * I only exist to allow the creation of a module-info.java file. + */ +public class IgnoreMe { +} diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/LinkUtil.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/LinkUtil.scala index 1dc7c7a8..1c23eda0 100644 --- a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/LinkUtil.scala +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/LinkUtil.scala @@ -29,7 +29,9 @@ object LinkUtil { } def getSessionManager(node: Node): SessionManager = { - SessionManagerContext.getContext(node) + val sm = SessionManagerContext.getContext(node) + assert(sm != null, "SessionManager was null") + sm } def setLink(node: Node, url: String): Unit = { @@ -86,7 +88,7 @@ object LinkUtil { def refresh(node: Node): Unit = { val man = LinkUtil.getSessionManager(node) assert(man.url != null, "current url was null") - man.gotoURL(man.url, false, true) + man.gotoURL(man.url, false) } private object LinkDesktop { diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/Request.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/Request.scala index 2833bf22..13e3334f 100644 --- a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/Request.scala +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/Request.scala @@ -72,18 +72,28 @@ object Request { private lazy val logger: Logger = LoggerFactory.getLogger(getClass.getName) private var wref_null = new WeakReference[Node](null) - def fromString(x: String): Request = { - if(!isValidLink(x)) { - logger.warn("Warning - Invalid Link: " + x) + + def fromString(s: String, oldView: Node): Request = { + val oldViewW = new WeakReference(oldView) + Request.fromString(s).copy(oldContent = oldViewW, origOldContent = oldViewW) + } + def fromString(s: String): Request = { + try { + if(!isValidLink(s)) { + logger.warn("Warning - Invalid Link: " + s) + } + val uri = new URI(s) + val rawQuery = uri.getRawQuery + val query: Map[String,String] = if(rawQuery == null || rawQuery == "") Map() else rawQuery.split("&").map(x => { + val Array(a,b) = x.split("=") + a -> b + }).toMap + val path = uri.getPath + val res = Request(s, uri.getScheme, uri.getHost, uri.getPort, path,path,"/", query,wref_null,wref_null) + res + } catch { + case e: Exception => + throw new RuntimeException("Could not parse Request from string: " + s, e) } - val uri = new URI(x) - val rawQuery = uri.getRawQuery - val query: Map[String,String] = if(rawQuery == null || rawQuery == "") Map() else rawQuery.split("&").map(x => { - val Array(a,b) = x.split("=") - a -> b - }).toMap - val path = uri.getPath - val res = Request(x, uri.getScheme, uri.getHost, uri.getPort, path,path,"/", query,wref_null,wref_null) - res } } diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/RouteNode.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/RouteNode.scala index 23c263fd..3840649d 100644 --- a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/RouteNode.scala +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/RouteNode.scala @@ -32,17 +32,9 @@ class RouteNode(stage: Stage, route: Route) extends StackPane { THIS => lazy val webAPI: WebAPI = if(WebAPI.isBrowser) com.jpro.webapi.WebAPI.getWebAPI(stage) else null var newRoute: Route = route - def getRoute: Route = newRoute + def getRoute(): Route = newRoute def setRoute(x: Route): Unit = newRoute = x - def route(s: String, oldView: Node) = { - val oldViewW = new WeakReference(oldView) - newRoute(Request.fromString(s).copy(oldContent = oldViewW, origOldContent = oldViewW)) - } - def route = { - (s: String) => newRoute(Request.fromString(s)) - } - def start(sessionManager: SessionManager) = { SessionManagerContext.setContext(this, sessionManager) diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/View.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/View.scala index e9fd29af..db13f1d6 100644 --- a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/View.scala +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/View.scala @@ -33,7 +33,7 @@ abstract class View extends ResponseResult { THIS => * @param x path * @return whether the view handles the url change */ - def handleURL(x: String): Boolean = false + def handleRequest(x: Request): Boolean = false def mapContent(f: Node => Node): View = new View { override def title: String = THIS.title diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/crawl/AppCrawler.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/crawl/AppCrawler.scala index 43ceeb29..5337740f 100644 --- a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/crawl/AppCrawler.scala +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/crawl/AppCrawler.scala @@ -1,7 +1,8 @@ package one.jpro.platform.routing.crawl -import one.jpro.platform.routing.sessionmanager.DummySessionManager -import one.jpro.platform.routing.{Redirect, Response, RouteNode, SessionManagerContext, View} +import one.jpro.platform.routing.crawl.AppCrawler.{CrawlReportApp, CrawlReportPage} +import one.jpro.platform.routing.sessionmanager.{SessionManager, SessionManagerDesktop, SessionManagerDummy} +import one.jpro.platform.routing.{LinkUtil, Redirect, Request, Response, Route, RouteApp, RouteNode, SessionManagerContext, View} import org.slf4j.{Logger, LoggerFactory} import simplefx.all._ import simplefx.core._ @@ -14,6 +15,7 @@ import scala.collection.JavaConverters._ object AppCrawler { private lazy val logger: Logger = LoggerFactory.getLogger(getClass.getName) + case class LinkInfo(url: String, description: String) case class ImageInfo(url: String, description: String) @@ -22,6 +24,7 @@ object AppCrawler { case class CrawlReportApp(pages: List[String], reports: List[CrawlReportPage], deadLinks: List[String]) + def crawlPage(page: View): CrawlReportPage = { var foundLinks: List[LinkInfo] = Nil var images: List[ImageInfo] = Nil @@ -29,14 +32,14 @@ object AppCrawler { var visitedNodes: Set[Node] = Set() def crawlNode(x: Node): Unit = { - if(x == null) return + if (x == null) return if (visitedNodes.contains(x)) return visitedNodes += x if (x.getProperties.containsKey("link")) { val link = x.getProperties.get("link").asInstanceOf[String] - if(link != null) { + if (link != null) { var desc = x.getProperties.get("description").asInstanceOf[String] - if(desc == null) desc = "" + if (desc == null) desc = "" foundLinks ::= LinkInfo(link, desc) } } @@ -52,10 +55,10 @@ object AppCrawler { } if (x.isInstanceOf[ListView[_]]) { val lview = x.asInstanceOf[ListView[Any]] - if(lview.getItems != null) { - lview.getItems.asScala.zipWithIndex.foreach { case (item,index) => + if (lview.getItems != null) { + lview.getItems.asScala.zipWithIndex.foreach { case (item, index) => val factory = lview.cellFactoryProperty().get() - if(factory != null) { + if (factory != null) { val cell: ListCell[Any] = factory.call(lview) cell.setItem(item) cell.updateIndex(index) @@ -69,85 +72,54 @@ object AppCrawler { if (x.isInstanceOf[Region]) { val region = x.asInstanceOf[Region] var rimages = List.empty[Image] - if(region.border != null && region.border.getImages != null) rimages :::= region.border.getImages.asScala.map(_.getImage).toList - if(region.background != null && region.background.getImages != null) rimages :::= region.background.getImages.asScala.map(_.getImage).toList - rimages.foreach{ image => + if (region.border != null && region.border.getImages != null) rimages :::= region.border.getImages.asScala.map(_.getImage).toList + if (region.background != null && region.background.getImages != null) rimages :::= region.background.getImages.asScala.map(_.getImage).toList + rimages.foreach { image => val imgURL = getImageURL(image) - if(imgURL != null) { - images ::= ImageInfo(imgURL,region.accessibleRoleDescription) + if (imgURL != null) { + images ::= ImageInfo(imgURL, region.accessibleRoleDescription) } } } - if(x.isInstanceOf[ImageView]) { + if (x.isInstanceOf[ImageView]) { val view = x.asInstanceOf[ImageView] - if(view.image != null) { + if (view.image != null) { val url = getImageURL(view.image) val description = view.accessibleRoleDescription - if(url != null) { - images ::= ImageInfo(url,description) + if (url != null) { + images ::= ImageInfo(url, description) } } } } - val scene = new Scene(new Group()) - val content = page.realContent.asInstanceOf[Parent] - SessionManagerContext.setContext(content, new DummySessionManager) - scene.setRoot(content) - page.realContent.applyCss() + val node = page.realContent crawlNode(page.realContent) CrawlReportPage(page.url, foundLinks.reverse, images.reverse, page.title, page.description) } - def crawlApp(prefix: String, createApp: Supplier[RouteNode]): CrawlReportApp = { - var toIndex = Set[String]("/") - var indexed = Set[String]() - var redirects = Set[String]() - var deadLinks = Set[String]() - var reports: List[CrawlReportPage] = List() - - def isOwnLink(x: String): Boolean = x.startsWith(prefix) || x.startsWith("/") - while (toIndex.nonEmpty) { - val crawlNext = toIndex.head - toIndex -= crawlNext - indexed += crawlNext - val result = inFX { - createApp.get().route(crawlNext) - }.future.await - result match { - case Redirect(url) => - redirects += crawlNext - if (!indexed.contains(url) && !toIndex.contains(url)) { - if (isOwnLink(url)) { - toIndex += url - } - } - case view: View => - view.url = crawlNext - try { - val newReport = inFX(crawlPage(view)) - reports = newReport :: reports - def simplifyLink(x: String) = { - if(x.startsWith(prefix)) x.drop(prefix.length) else x - } - newReport.links.filter(x => isOwnLink(x.url)).foreach { link => - val url = simplifyLink(link.url) - if (!indexed.contains(url) && !toIndex.contains(url)) { - toIndex += url - } - } - } catch { - case ex: Throwable => - logger.error(s"Error crawling page: $crawlNext", ex) - deadLinks += crawlNext - } - case null => - deadLinks += crawlNext - } - } + def crawlRoute(prefix: String, createRoute: () => Route): CrawlReportApp = { + val crawler = new AppCrawler(prefix, () => { + val stage = new Stage + val routeNode = new RouteNode(stage) + stage.setScene(new Scene(routeNode)) + val sm = new SessionManagerDesktop(routeNode) + routeNode.setRoute(createRoute()) + routeNode.start(sm) + routeNode + }) + crawler.crawlAll() + } - CrawlReportApp((indexed -- redirects -- deadLinks).toList, reports.reverse, deadLinks.toList) + def routeToRouteNode(route: Route): RouteNode = { + val stage = new Stage + val routeNode = new RouteNode(stage) + stage.setScene(new Scene(routeNode)) + val sm = new SessionManagerDesktop(routeNode) + routeNode.setRoute(route) + routeNode.start(sm) + routeNode } def getImageURL(x: Image): String = { @@ -160,6 +132,11 @@ object AppCrawler { } } + def simplifyAndEncode(x: String) = encodeSlashes(simplifyURL(x)) + def encodeSlashes(x: String): String = { + x.replaceAllLiterally("/1", "/11") + .replaceRepeatedly("//", "/1/") + } private val cpTriggers = List[String]("jar!","classes", "main") private val local = new File("").getAbsoluteFile.toURI.toURL.toString @@ -171,11 +148,6 @@ object AppCrawler { home -> "home://", "jar:" + fixFile(home) -> "jar:home://", ) - def simplifyAndEncode(x: String) = encodeSlashes(simplifyURL(x)) - def encodeSlashes(x: String): String = { - x.replaceAllLiterally("/1", "/11") - .replaceRepeatedly("//", "/1/") - } implicit class ExtStr(val x: String) extends AnyVal { def replaceRepeatedly(oldString: String, newString: String): String = { val r = x.replaceAllLiterally(oldString,newString) @@ -202,3 +174,87 @@ object AppCrawler { } } } + +class AppCrawler(prefix: String, createApp: Supplier[RouteNode]) { + import AppCrawler.logger + + assert(!isApplicationThread, "This method must not be called on the application thread") + var toIndex = Set[String]("/") + var indexed = Set[String]() + var redirects = Set[String]() + var deadLinks = Set[String]() + var reports: List[CrawlReportPage] = List() + + def isOwnLink(x: String): Boolean = x.startsWith(prefix) || x.startsWith("/") + + def doStep(): Unit = { + val crawlNext = toIndex.head + toIndex -= crawlNext + indexed += crawlNext + + val app: RouteNode = inFX(createApp.get()) + val result = inFX { + LinkUtil.getSessionManager(app) + val request = Request.fromString(crawlNext) + app.getRoute()(Request.fromString(crawlNext)) + }.future.await + result match { + case Redirect(url) => + redirects += crawlNext + if (!indexed.contains(url) && !toIndex.contains(url)) { + if (isOwnLink(url)) { + toIndex += url + } + } + case view: View => + println(s"View: ${view.url} crawlNext: $crawlNext") + try { + val newReport = inFX{ + runScheduler{ + LinkUtil.getSessionManager(app).gotoURL(crawlNext, view, pushState = false) + view.url = crawlNext + assert(app.scene != null, s"Scene is null for $crawlNext") + assert(app.scene.root != null, s"Root is null for $crawlNext") + } + app.scene.root.applyCss() + //app.scene.root.layout() + //println(SceneGraphSerializer.serialize(app.scene.root)) + assert(view.realContent.parent != null, s"Parent is null for $crawlNext") + assert(view.realContent.scene != null, s"Scene is null for $crawlNext") + println("SCENE WH: " + view.realContent.scene.getWidth + " " + view.realContent.scene.getHeight) + AppCrawler.crawlPage(view) + } + reports = newReport :: reports + def simplifyLink(x: String) = { + if(x.startsWith(prefix)) x.drop(prefix.length) else x + } + newReport.links.filter(x => isOwnLink(x.url)).foreach { link => + val url = simplifyLink(link.url) + if (!indexed.contains(url) && !toIndex.contains(url)) { + toIndex += url + } + } + } catch { + case ex: Throwable => + logger.error(s"Error crawling page: $crawlNext", ex) + deadLinks += crawlNext + } + case null => + deadLinks += crawlNext + } + } + def crawlAll(): CrawlReportApp = { + + while (toIndex.nonEmpty) { + try { + doStep() + } catch { + case ex: Throwable => + logger.error("Error in crawlAll", ex) + } + } + + CrawlReportApp((indexed -- redirects -- deadLinks).toList, reports.reverse, deadLinks.toList) + } + +} diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/crawl/MemoryTester.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/crawl/MemoryTester.scala index 99e62d91..035b72d8 100644 --- a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/crawl/MemoryTester.scala +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/crawl/MemoryTester.scala @@ -1,8 +1,10 @@ package one.jpro.platform.routing.crawl import one.jpro.jmemorybuddy.JMemoryBuddy +import javafx.stage.Stage import one.jpro.platform.routing.crawl.AppCrawler.CrawlReportApp -import one.jpro.platform.routing.{RouteNode, View} +import one.jpro.platform.routing.sessionmanager.SessionManagerDesktop +import one.jpro.platform.routing.{Request, Route, RouteApp, RouteNode, View} import org.slf4j.{Logger, LoggerFactory} import simplefx.cores.default.inFX @@ -12,14 +14,24 @@ object MemoryTester { private lazy val logger: Logger = LoggerFactory.getLogger(getClass.getName) - def testForLeaks(x: CrawlReportApp, appFactory: Supplier[RouteNode]): Unit = { + def testForLeaks(x: CrawlReportApp, appFactory: () => Route): Unit = { + testForLeaks2(x, () => { + val stage = new Stage + val routeNode = new RouteNode(stage) + val sm = new SessionManagerDesktop(routeNode) + routeNode.setRoute(appFactory()) + routeNode.start(sm) + routeNode + }) + } + def testForLeaks2(x: CrawlReportApp, appFactory: Supplier[RouteNode]): Unit = { x.pages.foreach { pageURL => logger.debug(s"Checking for leak for the url: $pageURL") JMemoryBuddy.memoryTest(checker1 => { JMemoryBuddy.memoryTest(checker2 => { - val factory = inFX(appFactory.get()) - assert(factory != null, "The appFactory must not return null") - val view = inFX(appFactory.get().route(pageURL)).future.await + val factory: RouteNode = inFX(appFactory.get()) + assert(factory != null, "The appFactory must not return null ") + val view = inFX(appFactory.get().getRoute()(Request.fromString(pageURL))).future.await checker2.setAsReferenced(factory) checker2.assertCollectable(view) // Hm? diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/server/RouteHTTP.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/server/RouteHTTP.scala new file mode 100644 index 00000000..0c85d8fd --- /dev/null +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/server/RouteHTTP.scala @@ -0,0 +1,49 @@ +package one.jpro.platform.routing.server + +import com.jpro.webapi.server.{Response, ServerAPI} +import one.jpro.platform.routing.{Route, RouteApp} +import one.jpro.platform.routing.crawl.{AppCrawler, SitemapGenerator} + +object RouteHTTP { + var initialized = false + + def main(args: Array[String]): Unit = { + // System.out.println("Hello, world!"); + // val route = getRoute(); + // CrawlReportApp report = AppCrawler.generate(route); + // SiteMapGenerator siteMapGenerator = new SiteMapGenerator(); + } +} + +abstract class RouteHTTP { + + def start(): Unit = { + + new Thread(() => { + val prefix = "localhost" + + if(RouteHTTP.initialized) { + throw new IllegalStateException("RouteHTTP already initialized") + } + + //val appCrawler = new AppCrawler(prefix, () => AppCrawler.routeToRouteNode(getRoute())) + + val report = AppCrawler.crawlRoute(prefix, () => getRoute()) + + ServerAPI.getServerAPI().addRequestHandler( + r => { + println("RouteHTTP> request: " + r.getPath()) + r.getPath() match { + case "/sitemap.xml" => + Response.of(SitemapGenerator.createSitemap(prefix, report).getBytes()) + case _ => + Response.empty() + } + } + ) + }).start() + + + } + def getRoute(): Route +} diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManager.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManager.scala index b3a9803e..2b9aca20 100644 --- a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManager.scala +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManager.scala @@ -4,7 +4,7 @@ import com.jpro.webapi.WebAPI import one.jpro.jmemorybuddy.JMemoryBuddyLive import javafx.beans.property.{ObjectProperty, SimpleObjectProperty} import javafx.collections.{FXCollections, ObservableList} -import one.jpro.platform.routing.{HistoryEntry, Response, ResponseResult, RouteNode, View} +import one.jpro.platform.routing.{HistoryEntry, Request, Response, ResponseResult, RouteNode, View} import org.slf4j.{Logger, LoggerFactory} import simplefx.all._ import simplefx.core._ @@ -21,10 +21,6 @@ trait SessionManager { THIS => def webApp: RouteNode - var ganalytics = false - var gtags = false - var trackingID = "" - val getHistoryBackward: ObservableList[HistoryEntry] = FXCollections.observableArrayList() val currentHistoryProperty: ObjectProperty[HistoryEntry] = new SimpleObjectProperty(null) val getHistoryForwards: ObservableList[HistoryEntry] = FXCollections.observableArrayList() @@ -47,32 +43,32 @@ trait SessionManager { THIS => SessionManager.externalLinkImpl.accept(url) } } else { - gotoURL(url,true,true) + gotoURL(url,true) } } - def gotoURL(url: String, pushState: Boolean = true, track: Boolean = true): Unit = { + def gotoURL(url: String, pushState: Boolean = true): Unit = { val url2 = SessionManager.mergeURLs(THIS.url, url) try { logger.debug(s"goto: $url") - val newView = if(view != null && view.handleURL(url)) Response(FXFuture(view)) else { - getView(url2) + val request = getRequest(url) + val newView = if(view != null && view.handleRequest(request)) Response(FXFuture(view)) else { + webApp.getRoute()(request) } newView.future.map { response => assert(response != null, s"Response for $url2 was null") this.url = url2 - gotoURL(url2, response, pushState, track) + gotoURL(url2, response, pushState) } } catch { case ex: Exception => logger.error(s"Error while loading the path $url2", ex) } } + def gotoURL(_url: String, x: ResponseResult, pushState: Boolean): Unit - def gotoURL(_url: String, x: ResponseResult, pushState: Boolean, track: Boolean): Unit - - def getView(url: String): Response = { + def getRequest(url: String): Request = { val node = if(view == null) null else view.realContent - webApp.route(url, node) + Request.fromString(url, node) } def start(): Unit diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerDesktop.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerDesktop.scala index 6324471c..68758542 100644 --- a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerDesktop.scala +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerDesktop.scala @@ -7,6 +7,7 @@ import simplefx.core._ import simplefx.util.ReflectionUtil class SessionManagerDesktop(val webApp: RouteNode) extends SessionManager { THIS => + assert(webApp != null, "webApp must not be null!") private lazy val logger: Logger = LoggerFactory.getLogger(getClass.getName) @@ -14,7 +15,7 @@ class SessionManagerDesktop(val webApp: RouteNode) extends SessionManager { THIS historyForward = historyCurrent :: historyForward historyCurrent = historyBackward.head historyBackward = historyBackward.tail - gotoURL(historyCurrent.path, false, true) + gotoURL(historyCurrent.path, false) } def goForward(): Unit = { @@ -22,10 +23,10 @@ class SessionManagerDesktop(val webApp: RouteNode) extends SessionManager { THIS historyBackward = historyCurrent :: historyBackward historyCurrent = historyForward.head historyForward = historyForward.tail - gotoURL(historyCurrent.path, false, true) + gotoURL(historyCurrent.path, false) } - def gotoURL(_url: String, x: ResponseResult, pushState: Boolean, track: Boolean): Unit = { + def gotoURL(_url: String, x: ResponseResult, pushState: Boolean): Unit = { x match { case Redirect(url) => logger.debug(s"redirect: ${_url} -> $url") diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/DummySessionManager.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerDummy.scala similarity index 60% rename from jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/DummySessionManager.scala rename to jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerDummy.scala index fc5776ab..84a08c5b 100644 --- a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/DummySessionManager.scala +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerDummy.scala @@ -2,16 +2,15 @@ package one.jpro.platform.routing.sessionmanager import one.jpro.platform.routing.{Response, ResponseResult, RouteNode} -class DummySessionManager extends SessionManager { - override def webApp: RouteNode = null +class SessionManagerDummy(val webApp: RouteNode) extends SessionManager { override def goBack(): Unit = () override def goForward(): Unit = () - override def gotoURL(_url: String, x: ResponseResult, pushState: Boolean, track: Boolean): Unit = () + override def gotoURL(_url: String, x: ResponseResult, pushState: Boolean): Unit = { - override def getView(url: String): Response = Response.empty() + } override def start(): Unit = () } diff --git a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerWeb.scala b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerWeb.scala index 4453ade0..6dbe8e20 100644 --- a/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerWeb.scala +++ b/jpro-routing/core/src/main/scala/one/jpro/platform/routing/sessionmanager/SessionManagerWeb.scala @@ -7,6 +7,7 @@ import simplefx.all._ class SessionManagerWeb(val webApp: RouteNode, val webAPI: WebAPI) extends SessionManager { THIS => + assert(webApp != null, "webApp must not be null!") private lazy val logger: Logger = LoggerFactory.getLogger(getClass.getName) @@ -21,16 +22,18 @@ class SessionManagerWeb(val webApp: RouteNode, val webAPI: WebAPI) extends Sessi webAPI.executeScript("history.go(1);") } - webAPI.addInstanceCloseListener(() => { - // if the session only has redirects, the view is null - if (THIS.view != null) { - THIS.view.onClose() - THIS.view.setSessionManager(null) - markViewCollectable(THIS.view) - } - }) + if(webAPI != null) { // somtetimes webAPI is null, for example when crawling + webAPI.addInstanceCloseListener(() => { + // if the session only has redirects, the view is null + if (THIS.view != null) { + THIS.view.onClose() + THIS.view.setSessionManager(null) + markViewCollectable(THIS.view) + } + }) + } - def gotoURL(_url: String, x: ResponseResult, pushState: Boolean, track: Boolean): Unit = { + def gotoURL(_url: String, x: ResponseResult, pushState: Boolean): Unit = { assert(x != null, "Response was null for url: " + _url) val url = _url x match { @@ -78,40 +81,20 @@ class SessionManagerWeb(val webApp: RouteNode, val webAPI: WebAPI) extends Sessi webAPI.executeScript(s"""document.title = "${view.title.replace("\"","\\\"")}";""") webAPI.executeScript(s"""document.querySelector('meta[name="description"]').setAttribute("content", "${view.description.replace("\"","\\\"")}");""") webAPI.executeScript(s"history.replaceState($initialState, null, null)") - if(ganalytics && track) { - webAPI.executeScript(s""" - |ga('set', { - | page: "${view.url.replace("\"","\\\"")}", - | title: "${view.title.replace("\"","\\\"")}" - |}); - | - |// send it for tracking - |ga('send', 'pageview'); - """.stripMargin) - } - if(gtags && track) { - assert(trackingID.nonEmpty) - webAPI.executeScript(s""" - |gtag('config', '$trackingID', { - | 'page_title' : "${view.title.replace("\"","\\\"")}", - | 'page_location': "${view.title.replace("\"","\\\"")}" - |});""".stripMargin) - } - } } - def gotoFullEncodedURL(x: String, pushState: Boolean = true, track: Boolean = true): Unit = { + def gotoFullEncodedURL(x: String, pushState: Boolean = true): Unit = { // We no longer decode - we should only process proper URLs // If the URL is not proper, we will get a warning when creating the Request. - gotoURL(x, pushState, track) + gotoURL(x, pushState) } def start(): Unit = { - gotoFullEncodedURL(webAPI.getBrowserURL, false, false) + gotoFullEncodedURL(webAPI.getBrowserURL, false) logger.debug("registering popstate") webAPI.registerJavaFunction("popstatejava", (s: String) => { - gotoFullEncodedURL(s.drop(1).dropRight(1).replace("\\\"", "\""), false) + gotoFullEncodedURL(s.drop(1).dropRight(1).replace("\\\"", "\"")) }) webAPI.registerJavaFunction("jproGotoURL", (s: String) => { gotoURL(s.drop(1).dropRight(1).replace("\\\"", "\"")) diff --git a/jpro-routing/example/src/main/scala/example/scala/TestWebApplication.scala b/jpro-routing/example/src/main/scala/example/scala/TestWebApplication.scala index 09cdf1c1..cacf3c28 100644 --- a/jpro-routing/example/src/main/scala/example/scala/TestWebApplication.scala +++ b/jpro-routing/example/src/main/scala/example/scala/TestWebApplication.scala @@ -14,11 +14,13 @@ import simplefx.core._ import scala.collection.JavaConverters.asScalaBufferConverter -class MyApp(stage: Stage) extends RouteNode(stage) { +class TestWebApplication extends RouteApp { - stylesheets ::= "test.css" + override def createRoute(): Route = { + if(getRouteNode() != null) { + getRouteNode().stylesheets ::= "test.css" + } - setRoute( Route.empty() .and(get("", (r) => Response.view(new MainView))) .and(get("/", (r) => Response.view(new MainView))) @@ -44,11 +46,9 @@ class MyApp(stage: Stage) extends RouteNode(stage) { .and(get("/it's\" tricky", (r) => Response.view(new MainView))) .filter(DevFilter.create) .filter(StatisticsFilter.create) - ) + } + - // addTransition{ case (null,view2,true ) => PageTransition.InstantTransition } - // addTransition{ case (view,view2,true ) => PageTransition.MoveDown } - // addTransition{ case (view,view2,false) => PageTransition.MoveUp } } class Header(view: View, sessionManager: SessionManager) extends HBox { @@ -142,8 +142,8 @@ trait Page extends View { view => } } - override def handleURL(x: String): Boolean = { - println("handleURL called: " + x) + override def handleRequest(x: Request): Boolean = { + println("handleRequest called: " + x) return false; } } @@ -368,15 +368,3 @@ class ParalaxPage extends Page { } - - -object TestWebApplication extends App -@SimpleFXApp class TestWebApplication { - val app = new MyApp(stage) - if(WebAPI.isBrowser) { - root = app - } else { - scene = new Scene(app, 1400,800) - } - app.start(SessionManager.getDefault(app,stage)) -} diff --git a/jpro-routing/example/src/main/scala/example/scala/TestWebApplicationHTTP.scala b/jpro-routing/example/src/main/scala/example/scala/TestWebApplicationHTTP.scala new file mode 100644 index 00000000..feda558c --- /dev/null +++ b/jpro-routing/example/src/main/scala/example/scala/TestWebApplicationHTTP.scala @@ -0,0 +1,18 @@ +package example.scala + +import one.jpro.platform.routing.Route +import one.jpro.platform.routing.server.RouteHTTP + + +object TestWebApplicationHTTP { + def main(args: Array[String]): Unit = { + new TestWebApplicationHTTP().start() + } +} +class TestWebApplicationHTTP extends RouteHTTP { + + override def getRoute: Route = { + new TestWebApplication().createRoute() + } + +}