From a99469c73d9ed92d412b87a8ba3db38005710d40 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Mon, 6 Jul 2026 13:12:43 +0300 Subject: [PATCH 01/27] apache click -> freemarker migration plan --- .../click-to-freemarker/01-click-inventory.md | 134 ++++++++++++++++++ .../click-to-freemarker/02-recommendation.md | 49 +++++++ .../click-to-freemarker/03-migration-plan.md | 105 ++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 docs/migration/click-to-freemarker/01-click-inventory.md create mode 100644 docs/migration/click-to-freemarker/02-recommendation.md create mode 100644 docs/migration/click-to-freemarker/03-migration-plan.md diff --git a/docs/migration/click-to-freemarker/01-click-inventory.md b/docs/migration/click-to-freemarker/01-click-inventory.md new file mode 100644 index 0000000000..0f5b6887d7 --- /dev/null +++ b/docs/migration/click-to-freemarker/01-click-inventory.md @@ -0,0 +1,134 @@ +# Apache Click usage inventory (OpenAM) + +> Snapshot of where and how Apache Click is used, so this doesn't need to be +> re-discovered. Captured against `master` @ 16.1.2-SNAPSHOT. + +## TL;DR + +Apache Click powers **only** OpenAM's first-run **configurator / upgrade wizard** at +`/openam/config/*` on an *unconfigured* WAR. It does **not** touch the admin console (XUI/React), +auth, or federation runtime. To keep Click alive on Jakarta EE 9 the project carries a **vendored +fork of Apache Click 2.3.0** (57 files, ~36k LOC) and *still* depends on the abandoned upstream +javax-only jars. + +Three surfaces must be unwound: (a) the fork, (b) the residual real-Click Maven deps, (c) the +consumer page classes + `.htm` templates. + +## Modules involved + +| Module dir | artifactId | Click footprint | +|---|---|---| +| `openam-core` | `openam` | The 57-class fork + 13/16 consumer page classes; declares the Click deps | +| `openam-upgrade` | `openam-upgrade` | 1 page class (`Upgrade.java`); no direct dep (inherits from openam-core) | +| `openam-server-only` | `openam-server-only` | Web assets: `click.xml`, `web.xml` ClickServlet + `*.htm` mapping, all 12 `.htm` templates | +| `openam-federation/OpenFM` | `OpenFM` | 2nd `web.xml` (`noconsole`) registering ClickServlet + `*.htm` mapping | + +## Maven dependencies + +- Root `pom.xml`: `2.3.0` (~line 90); fork packages listed in the + javadoc/OSGi `excludePackageNames` (`org.apache.click`, `org.openidentityplatform.openam.click`, + `org.openidentityplatform.openam.velocity`). +- `openam-core/pom.xml` (~lines 260–269): `org.apache.click:click-nodeps:2.3.0` and + `org.apache.click:click-extras:2.3.0`. +- No other module references Click. `net.sf.click` is not used anywhere. + +## The vendored fork (bulk of the code) + +`openam-core/src/main/java/org/openidentityplatform/openam/click/` — **57 Java files**, a +repackaged Apache Click 2.3.0 made to run on `jakarta.servlet`. Sub-packages: +- root: `ClickServlet`, `Page`, `Context`, `Control`, `ControlRegistry`, `ActionEventDispatcher`, + `ActionResult`, `PageInterceptor`, `ActionListener` +- `control/`: `Form`, `Field`, `TextField`, `TextArea`, `HiddenField`, `Button`, `ActionLink`, + `AbstractLink`, `Table`, `Column`, `TablePaginator`, `Radio`, `RadioGroup`, `Label`, `FileField`, + `Container`, `AbstractContainer`, `AbstractControl`, `Decorator` +- `service/`: `TemplateService`, `VelocityTemplateService`, `ConfigService`, `XmlConfigService`, + `LogService`, `ConsoleLogService`, `ResourceService`, `ClickResourceService`, `FileUploadService`, + `MessagesMapService`, `DefaultMessagesMapService` +- `util/`: `ClickUtils`, `ContainerUtils`, `PageImports`, `SessionMap`, `ErrorPage`, `ErrorReport` +- `element/`: `ResourceElement`, `JsImport`, `JsScript`, `CssImport`, `CssStyle` + +Plus a small `org.openidentityplatform.openam.velocity` fork (Velocity is the fork's template +engine). The fork still `import`s upstream `org.apache.click.*` in ~30 places (util/service/ +element), which is why the upstream 2.3.0 jars remain on the classpath. + +## Consumer page classes (what actually uses Click) + +Base/framework classes — `openam-core/src/main/java/com/sun/identity/config/util/`: +- `AjaxPage.java` — `extends org.openidentityplatform.openam.click.Page`; base for all config + pages. **Most of it is framework-agnostic** (i18n `amConfigurator`, OpenDJ LDAP validation via + `getConnection`, JSON AJAX helpers `writeValid`/`writeInvalid`/`writeJsonResponse`, + `toString/toBoolean/toInt`, password checks, session get/set/reset). Click seams: `extends Page`, + `ActionLink` fields, `getContext()`. +- `ProtectedPage.java` — `extends AjaxPage`; `onSecurityCheck()` blocks once + `AMSetupServlet.isConfigured()` is true (the only access control — pre-config bootstrap UI). +- `TemplatedPage.java` — abstract templated-page base. +- `TemplatedForm.java` — **the only class importing REAL Apache Click** + (`org.apache.click.control.Field` / `Form`); `extends` Click `Form`. + +Wizard pages — `openam-core/src/main/java/com/sun/identity/config/wizard/` (extend `ProtectedPage`): +- `Wizard.java` — the "execute" controller. `createConfig()` aggregates all session data from every + step and calls `AMSetupServlet.processRequest()`. Also `testNewInstanceUrl`, `pushConfig`. +- `Step1.java` (admin+agent passwords), `Step2.java` (config dir + cookie domain), + `Step3.java` (`extends LDAPStoreWizardPage`; config data store, embedded/external OpenDJ), + `Step4.java` (external user store), `Step5.java` (site/LB), `Step6.java` (policy-agent password), + `Step7.java` (summary). +- `LDAPStoreWizardPage.java` — shared base for LDAP-store steps (`clearStore`). + +Other config pages — `openam-core/src/main/java/com/sun/identity/config/`: +- `DefaultSummary.java` (`extends ProtectedPage`) — one-click default-config path. +- `Options.java` (`extends TemplatedPage`) — upgrade/coexistence options + version detection. +- Support (non-page): `SessionAttributeNames.java` (session-key constants), `SetupWriter.java`, + `pojos/LDAPStore.java`. + +Upgrade page — `openam-upgrade/src/main/java/com/sun/identity/config/upgrade/Upgrade.java` +(`extends AjaxPage`) — runs `UpgradeServices`; `saveReport` streams the upgrade report. + +**Totals:** 13 concrete page classes + 3 base classes; ~3,400 LOC in openam-core config + +~117 LOC upgrade. + +## Templates (Velocity `.htm`) + +`openam-server-only/src/main/webapp/` — **12 templates** (~2,829 lines): +- `click/error.htm`, `click/not-found.htm` +- `config/options.htm`, `config/defaultSummary.htm` +- `config/upgrade/upgrade.htm` +- `config/wizard/wizard.htm` (the JS tab shell) + `config/wizard/step1..step7.htm` + +Template idioms to port: `$page.getLocalizedString("k")`, `$context` (context path), +`$path` (page path), `$startingTab`. Templates embed **YUI** JS and drive AJAX field validation +against `$context$path?actionLink=`, expecting either plain text or the JSON contract +`{"valid":.., "body":..}`. + +## Configuration & servlet wiring + +- `openam-server-only/src/main/webapp/WEB-INF/click.xml` — `` `production` mode, pages + package `com.sun.identity`, with an `` list that excludes essentially everything + except `/config/*` (also excludes `/config/auth/default/*` and `/config/federation/*`). +- `openam-server-only/src/main/webapp/WEB-INF/web.xml` — servlet `click-servlet` → + `org.openidentityplatform.openam.click.ClickServlet` (the FORK), mapped to `*.htm`. +- `openam-federation/OpenFM/src/main/resources/xml/noconsole/web.xml` — same ClickServlet + `*.htm` + mapping. +- `openam-server-only/src/main/webapp/WEB-INF/classes/click-page.properties` — Click's *own* + control i18n (drop, don't migrate; app i18n is the `amConfigurator` bundle). + +## Architecture (how the wizard flows) + +1. `wizard.htm` renders a JS tab shell (`tab1..tab7`, `wizardStep1..7` divs) and, via YUI + `AjaxUtils.load`, lazy-loads each `config/wizard/stepN.htm` as a **separate Click page request** + into its div. +2. Each step's embedded JS POSTs to `$context$path?actionLink=` → dispatched by ClickServlet + to that page's `ActionLink`-bound method (e.g. `Step1.checkAdminPassword`), which validates and + stores the value into the HTTP **session** (`SessionAttributeNames.*`), returning JSON/text. +3. The final **Create Configuration** calls `?actionLink=createConfig` → `Wizard.createConfig()`, + which reads every session attribute, builds a wrapped request, and calls + `AMSetupServlet.processRequest()`. + +## Backend coupling (the only real integration points) + +- Config: `com.sun.identity.setup.AMSetupServlet` (`processRequest`, `isConfigured`, + `getPresetConfigDir`, `getErrorMessage`), `AMSetupUtils`, `SetupConstants`. +- Upgrade: `org.forgerock.openam.upgrade.UpgradeServices` / `UpgradeUtils` / `VersionUtils` with an + admin `SSOToken` from `AdminTokenAction`. +- LDAP validation: OpenDJ SDK (`org.forgerock.opendj.ldap`) directly in `AjaxPage.getConnection()`. +- Session: Click `Context` session attributes keyed by `SessionAttributeNames`. +- i18n: `ResourceBundle` `amConfigurator`, locale from `?locale=` or `Accept-Language`. diff --git a/docs/migration/click-to-freemarker/02-recommendation.md b/docs/migration/click-to-freemarker/02-recommendation.md new file mode 100644 index 0000000000..49e8ab8e97 --- /dev/null +++ b/docs/migration/click-to-freemarker/02-recommendation.md @@ -0,0 +1,49 @@ +# Recommendation: replace Apache Click with Jakarta Servlets + FreeMarker + +## Decision + +Migrate the OpenAM configurator/upgrade wizard from Apache Click to **plain Jakarta Servlets + +FreeMarker** server-rendered templates. This lets us delete the ~36k-LOC vendored Click fork and +the abandoned upstream `org.apache.click` jars while keeping the existing server-rendered + +AJAX-validation architecture and all backend coupling intact. + +## Constraints that shaped the choice + +- **Runs pre-configuration.** The wizard serves a bare, unconfigured WAR — before the + CREST/CHF/Guice OpenAM runtime is up. The replacement must not depend on that runtime. +- **JDK 11 / Jakarta EE 9 (Servlet 5.x).** The codebase is 100% `jakarta.servlet` already; the + replacement must be Jakarta-namespace clean. +- **Small, self-contained surface.** 13 page classes + 12 templates; thin backend coupling + (`AMSetupServlet`, `UpgradeServices`, OpenDJ). No admin-console/auth/federation impact. +- **Goal is debt removal**, not a product rewrite — the primary win is deleting the fork. + +## Why FreeMarker + Servlets + +- **No new dependency category.** `org.freemarker:freemarker` 2.3.31 is already a managed dependency + used by `openam-oauth2` (`.ftl` consent/error/form-post pages); `jakarta.servlet-api` is already + `provided` in `openam-core`. +- **Runs fine pre-config** — plain servlets + a servlet-agnostic template engine. +- **Nearly mechanical template port.** Velocity `$page.getLocalizedString("k")` / `$context` / + `$path` / `$startingTab` → FreeMarker `${...}`. The embedded YUI JS and the + `{"valid":..,"body":..}` AJAX contract carry over unchanged. +- **Reuses the valuable code.** `AjaxPage`'s validation/i18n/LDAP/JSON helpers are already + framework-agnostic; only the Click seams (`extends Page`, `ActionLink`, `getContext()`) change. +- **Deletes the most code for the least new surface** — the whole point. + +## Alternatives considered (and why not) + +- **React SPA + JAX-RS (Jersey 3.1.10, already on classpath).** Aligns with the XUI React/Vite + modernization direction, but adds a front-end build pipeline and a bigger rewrite for a one-time + bootstrap UI. Overkill here. *Revisit only if the configurator is folded into the XUI React app.* +- **ForgeRock CREST / CHF (the house REST standard).** Resource/CRUD-oriented — a poor fit for a + stateful multi-step wizard — and the CREST/Guice runtime isn't reliably available at setup time. +- **JSP + JSTL** (the other dominant legacy templating, ~445 JSPs). Zero new deps, but doubles down + on legacy tech the project is otherwise moving away from. +- **Spring Boot / Spring MVC.** Only present in the isolated JDK-17 `openam-mcp-server` module; + heavy new deps on a JDK-11 core bootstrap path. Rejected. + +## Delivery strategy + +Incremental, de-risked: **pilot one wizard step end-to-end on a parallel route** (nothing existing +breaks), verify, lock the repeatable pattern, then roll out the remaining pages and remove Click. +See `03-migration-plan.md`. diff --git a/docs/migration/click-to-freemarker/03-migration-plan.md b/docs/migration/click-to-freemarker/03-migration-plan.md new file mode 100644 index 0000000000..76c0c8f097 --- /dev/null +++ b/docs/migration/click-to-freemarker/03-migration-plan.md @@ -0,0 +1,105 @@ +# Migration plan: Click → Jakarta Servlets + FreeMarker + +Living execution doc. Background in `01-click-inventory.md` and `02-recommendation.md`. + +## Phase 1 — Pilot: migrate Step 1 end-to-end (parallel route) + +Build alongside the live Click wizard (which keeps serving `*.htm`) so nothing breaks. + +New code in `openam-core`, package `com.sun.identity.config.servlet`: + +- **`ConfiguratorServlet`** (Jakarta `HttpServlet`) — `GET /config/setup/` renders + `.ftl`; `POST /config/setup/?action=` dispatches to the page handler's named + method (small action→method map or reflection — mirrors how Click's `ActionLink` bound + `?actionLink=` to a method). Self-gates on `AMSetupServlet.isConfigured()` (port of + `ProtectedPage.onSecurityCheck`). +- **`ConfiguratorContext`** — thin wrapper over request/response/session replacing Click `Context` + (`getRequest/getResponse/getSessionAttribute/setSessionAttribute/removeSessionAttribute/ + getWriter`). +- **`SetupPage`** — new base = `AjaxPage` minus Click. Move `AjaxPage`'s framework-agnostic helpers + here (or make `AjaxPage extend SetupPage`): the i18n bundle (`amConfigurator`), + `writeValid/writeInvalid/writeJsonResponse`, `toString/toBoolean/toInt`, `getConnection` (OpenDJ), + session helpers. Holds a `ConfiguratorContext`. +- **`Step1Page extends SetupPage`** — port of `Step1.checkAdminPassword`/`checkAgentPassword` + (logic copied 1:1; only the `getContext()`/`ActionLink` seams change). +- A singleton FreeMarker `Configuration` with a `WebappTemplateLoader` rooted at `/config` + (templates stay in the webapp). + +Templates / wiring: +- `openam-server-only/src/main/webapp/config/setup/step1.ftl` — FreeMarker port of `step1.htm`. + Model exposes `page` (the handler, for `getLocalizedString`), `context`, `path`. +- `openam-server-only/src/main/webapp/WEB-INF/web.xml` — register `ConfiguratorServlet` mapped to + `/config/setup/*` **only**; leave `click-servlet` + `*.htm` untouched. +- `openam-core/pom.xml` — add managed `org.freemarker:freemarker` (version from root pom + `freemarker.version` = 2.3.31). + +## Phase 2 — Verify the pilot + +- **Automated** (match openam-core's TestNG/JUnit + a servlet mock): + 1. Render `step1.ftl` via the FreeMarker `Configuration` with a stub `page` model → assert the + localized title and `adminPassword`/`adminConfirm` inputs render. + 2. Drive `Step1Page.checkAdminPassword` with mock requests: mismatch → `passwords.do.not.match`; + `<8` chars → `invalid.password.length`; valid → `OK` + `SessionAttributeNames. + CONFIG_VAR_ADMIN_PWD` set in session. Assert the `{"valid":..}` JSON body. +- **Manual smoke:** build the `openam-server-only` WAR, deploy on Tomcat 10 / Jetty 11 + (unconfigured), browse `/openam/config/setup/step1`, confirm AJAX validation + localized errors. +- Record the exact working pattern below so rollout is copy-paste. + +### Template token map (Velocity `.htm` → FreeMarker `.ftl`) + +| Velocity | FreeMarker | +|---|---| +| `$page.getLocalizedString("k")` | `${page.getLocalizedString("k")}` | +| `$context` | `${context}` | +| `$path` | `${path}` | +| `$startingTab` | `${startingTab}` | +| `#if(...) ... #end` | `<#if ...> ... ` | +| `#foreach($x in $xs) ... #end` | `<#list xs as x> ... ` | + +(Embedded YUI JS and AJAX endpoints are left as-is; only the `?actionLink=` → `?action=` param name +changes with the dispatch.) + +## Phase 3 — Roll out + remove Click (after pilot sign-off) + +**Per-page pattern:** +1. Port `.htm` → `.ftl` (token map above). +2. Port the page class: base `AjaxPage`/`ProtectedPage`/`TemplatedPage` → `SetupPage`; replace + `ActionLink` fields with entries in the servlet's action→method dispatch; retarget `getContext()` + to `ConfiguratorContext`. +3. Register the route; repoint cross-references (the shell loads `stepN.htm` URLs). + +**Inventory to migrate** (`openam-core/.../config/` unless noted): +- `wizard/Wizard.java` + `wizard/wizard.htm` (JS tab shell) +- `wizard/Step1..Step7.java` + `wizard/step1..step7.htm` +- `wizard/LDAPStoreWizardPage.java` +- `Options.java` + `config/options.htm` +- `DefaultSummary.java` + `config/defaultSummary.htm` +- `click/error.htm` + `click/not-found.htm` → plain servlet error pages +- `openam-upgrade/.../config/upgrade/Upgrade.java` + `config/upgrade/upgrade.htm` +- Reimplement/retire `util/TemplatedForm.java` (only class importing real `org.apache.click`) and + `util/TemplatedPage.java`. + +**Final removal checklist:** +- [ ] `web.xml` in **both** `openam-server-only/src/main/webapp/WEB-INF/web.xml` and + `openam-federation/OpenFM/src/main/resources/xml/noconsole/web.xml` — remove `click-servlet` + registration + `*.htm` mapping; keep only `ConfiguratorServlet`. +- [ ] Delete `openam-core/src/main/java/org/openidentityplatform/openam/click/` (57 files) and the + `org.openidentityplatform.openam.velocity` fork. +- [ ] Delete `WEB-INF/click.xml` and `WEB-INF/classes/click-page.properties`. +- [ ] `openam-core/pom.xml` — remove `click-nodeps` + `click-extras`. +- [ ] Root `pom.xml` — remove ``; drop the click/velocity fork packages from the + javadoc/OSGi `excludePackageNames`. +- [ ] Grep-sweep: zero `import ...click...` remaining; no lingering `*.htm` references. + +## Verification (end-to-end) + +- Pilot: Phase 2 tests pass + manual smoke renders/validates `/config/setup/step1` on an + unconfigured WAR. +- Rollout: on a fresh unconfigured WAR, run the full wizard Steps 1→7 (incl. custom config/user + LDAP stores hitting OpenDJ, site config), then **Create Configuration** succeeds via + `AMSetupServlet.processRequest()` and the console launches. Exercise the Upgrade page against an + older install. Final gate: full reactor build with Click removed and no `import ...click...`. + +## Out of scope + +XUI/admin console, auth/federation UIs, classic `openam-console` JSPs — none use Click. From a542bc608108d5cececef96e6afcaea0fd0483d9 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Mon, 6 Jul 2026 13:26:48 +0300 Subject: [PATCH 02/27] Refactor migration plan: enhance incremental strategy for Apache Click to FreeMarker transition --- .../click-to-freemarker/01-click-inventory.md | 20 +- .../click-to-freemarker/02-recommendation.md | 18 +- .../click-to-freemarker/03-migration-plan.md | 309 +++++++++++++----- 3 files changed, 253 insertions(+), 94 deletions(-) diff --git a/docs/migration/click-to-freemarker/01-click-inventory.md b/docs/migration/click-to-freemarker/01-click-inventory.md index 0f5b6887d7..d7ff014d7e 100644 --- a/docs/migration/click-to-freemarker/01-click-inventory.md +++ b/docs/migration/click-to-freemarker/01-click-inventory.md @@ -63,7 +63,9 @@ Base/framework classes — `openam-core/src/main/java/com/sun/identity/config/ut `AMSetupServlet.isConfigured()` is true (the only access control — pre-config bootstrap UI). - `TemplatedPage.java` — abstract templated-page base. - `TemplatedForm.java` — **the only class importing REAL Apache Click** - (`org.apache.click.control.Field` / `Form`); `extends` Click `Form`. + (`org.apache.click.control.Field` / `Form`); `extends` Click `Form`. **Dead code** — referenced + nowhere but itself, so it can be deleted immediately (removes the last real-`org.apache.click` + import from consumer code; the upstream jars then linger only for the fork). Wizard pages — `openam-core/src/main/java/com/sun/identity/config/wizard/` (extend `ProtectedPage`): - `Wizard.java` — the "execute" controller. `createConfig()` aggregates all session data from every @@ -95,9 +97,19 @@ Upgrade page — `openam-upgrade/src/main/java/com/sun/identity/config/upgrade/U - `config/wizard/wizard.htm` (the JS tab shell) + `config/wizard/step1..step7.htm` Template idioms to port: `$page.getLocalizedString("k")`, `$context` (context path), -`$path` (page path), `$startingTab`. Templates embed **YUI** JS and drive AJAX field validation -against `$context$path?actionLink=`, expecting either plain text or the JSON contract -`{"valid":.., "body":..}`. +`$path` (page path), `$startingTab`, plus per-page vars injected via Click's **public-field +exposure** (`$configStoreHost`, `$type`, `$store`, `$selectEmbedded`, …). Templates embed **YUI** JS +and drive AJAX field validation against `$context$path?actionLink=` (the step posts back to its +own URL). + +**Response contract (verified):** most handlers return **plain text** (`"true"`/`"ok"`/localized +error string), read by the templates as `response.responseText == "true"`. The +`{"valid":.., "body":..}` JSON template (`AjaxPage.java:80`) is essentially **unused** by these +pages. The one structured response is Step 3's `validateHostName`, which returns a **bespoke** JSON +shape (`{code, existingPort, embedded, replication, replicationPort, existingStoreHost, +existingStorePort, message}`) parsed via `eval(...)`. Each handler's exact output bytes must be +preserved. Note: `step1.htm` calls the **base-class `checkPasswords`** handler (plain text), *not* +`Step1`'s own `checkAdminPassword`/`checkAgentPassword` `ActionLink`s, which no template references. ## Configuration & servlet wiring diff --git a/docs/migration/click-to-freemarker/02-recommendation.md b/docs/migration/click-to-freemarker/02-recommendation.md index 49e8ab8e97..bbf4fd88c4 100644 --- a/docs/migration/click-to-freemarker/02-recommendation.md +++ b/docs/migration/click-to-freemarker/02-recommendation.md @@ -44,6 +44,18 @@ AJAX-validation architecture and all backend coupling intact. ## Delivery strategy -Incremental, de-risked: **pilot one wizard step end-to-end on a parallel route** (nothing existing -breaks), verify, lock the repeatable pattern, then roll out the remaining pages and remove Click. -See `03-migration-plan.md`. +Incremental, de-risked, and **URL-preserving** — two firm constraints drive the sequencing: + +- **Every servlet path is kept byte-for-byte.** Rather than a new route, a single wrapper servlet + (`ConfiguratorServlet`) takes over the `*.htm` mapping and routes each request by a migrated-page + registry: migrated → FreeMarker, otherwise → **delegate to the untouched Click fork via a named + `RequestDispatcher`** (which preserves the request path). The wizard shell JS, self-posting + `actionLink` AJAX, and `AMSetupFilter` URL constants all keep working unchanged, and migrating a + page is a Java-only change (one registry entry — no per-page `web.xml` edits). +- **One page off Click at a time — no one-shot cutover.** Pages migrate individually; after each the + reactor builds green and the wizard runs with Click and FreeMarker side by side. The fork, the + upstream jars, and `click.xml` are deleted only in the final increment. + +Pilot `Step1` end-to-end (builds the shared servlet/base/FreeMarker infra), verify, lock the +repeatable per-page pattern, then work through the remaining pages and remove Click last. See +`03-migration-plan.md`. diff --git a/docs/migration/click-to-freemarker/03-migration-plan.md b/docs/migration/click-to-freemarker/03-migration-plan.md index 76c0c8f097..c209294d91 100644 --- a/docs/migration/click-to-freemarker/03-migration-plan.md +++ b/docs/migration/click-to-freemarker/03-migration-plan.md @@ -2,104 +2,239 @@ Living execution doc. Background in `01-click-inventory.md` and `02-recommendation.md`. -## Phase 1 — Pilot: migrate Step 1 end-to-end (parallel route) - -Build alongside the live Click wizard (which keeps serving `*.htm`) so nothing breaks. +## Context + +The configurator/upgrade wizard is the last consumer of the vendored Apache Click 2.3.0 fork +(57 files, ~36k LOC) and the abandoned upstream `org.apache.click` jars. We want that debt gone. +Two hard constraints shape *how*: + +1. **Preserve every servlet path.** All existing URLs stay byte-for-byte identical — + `/config/options.htm`, `/config/wizard/wizard.htm`, `/config/wizard/step1..7.htm`, + `/config/defaultSummary.htm`, `/config/upgrade/upgrade.htm`. These are baked into `AMSetupFilter` + (`SETUP_URI = "/config/options.htm"`, `UPGRADE_URI = "/config/upgrade/upgrade.htm"`), into the + wizard shell's JS (`"$context/config/wizard/step" + n + ".htm"`), and into every step's + self-posting AJAX (`$context$path?actionLink=`). Changing any URL breaks the wizard. +2. **No one-shot cutover.** Pages move off Click **one at a time**. After each increment the reactor + builds green and the wizard works end-to-end with Click and FreeMarker running side by side. + Click, the fork, and the deps are deleted only in the final increment, once nothing uses them. + +This supersedes the earlier draft of this plan, which introduced a *new parallel* `/config/setup/*` +route and a bulk rollout. Both are abandoned: the new route would have changed URLs (violating #1), +and bulk rollout violates #2. + +## How both constraints are satisfied at once + +**A single wrapper servlet owns `*.htm` and routes each request to FreeMarker or Click.** +`ConfiguratorServlet` takes over the `*.htm` mapping Click has today and, per request, consults a +**migrated-page registry** (the `path → PageClass` map it already needs): if the path is migrated it +renders FreeMarker; otherwise it **delegates to the untouched Click fork**. Migrating a page is then a +**Java-only** change (add one registry entry) — no per-page `web.xml` edits. + +Delegation to Click uses a **named `RequestDispatcher`**, which is the one trick that makes this work: + +- Keep `click-servlet` as a `` declaration but **remove its `*.htm` ``** — + it becomes reachable only by name, not by URL. +- Un-migrated path → `getServletContext().getNamedDispatcher("click-servlet").forward(req, res)`. +- A **named** forward does **not** rewrite the request path, so Click still sees + `getServletPath() == /config/wizard/step2.htm` and resolves the page exactly as today. + `ClickServlet.service()` sets up its own thread-locals on that forward; the `/*` filters + (`amSetupFilter`, audit) already ran once on the original request, so there is no double-filtering + and no init problem (Click inits lazily on the first forward). The wrapper decides routing before + writing anything, so the response is never already-committed. + +This replaces the per-page exact-`` scheme from the earlier draft: one **one-time** +`web.xml` change in each of the two descriptors (re-point `*.htm` to the wrapper, unmap +`click-servlet`), after which every increment is pure Java. The two Click-excluded trees +(`/config/auth/default/*`, `/config/federation/*`) are `.jsp`/`.xml`, never `.htm`, so `*.htm` never +sees them; the wrapper's registry must likewise only ever claim the known configurator URLs. + +Because URLs, the `actionLink` query-param name, and the exact **response byte formats** are all +preserved, **the wizard shell and all embedded step JS need zero changes** as pages migrate. A step +served by FreeMarker is indistinguishable to the shell from one served by Click. + +## Corrections to earlier assumptions (verified against source) + +- **Step 1's live handler is the base-class `checkPasswords`, not `checkAdminPassword`.** `step1.htm` + POSTs `?actionLink=checkPasswords` to `AjaxPage.checkPasswords`, which writes **plain text** + `"true"` or a localized error string via `writeToResponse(...)`. The `ActionLink`s named + `checkAdminPassword`/`checkAgentPassword` on `Step1.java` are **not referenced by any template**. +- **The `{"valid":.., "body":..}` JSON contract is essentially unused** by these templates. Most + handlers return plain text (`"true"`/`"ok"`/localized error). The one structured response is + `validateHostName` (Step 3), which returns a **bespoke** JSON shape + (`{code, existingPort, embedded, replication, replicationPort, existingStoreHost, + existingStorePort, message}`) read via `eval(...)`. Each handler's exact output bytes must be + preserved. +- **`TemplatedForm.java` is dead code** — the only class importing *real* `org.apache.click`, and it + is referenced nowhere but itself. It can be deleted immediately (increment 0) as a freebie. + `TemplatedPage.java` is used only by `Options.java`. +- Click auto-exposes a page's **public fields** into the template model (not just `addModel(...)`). + Step 3 relies on this heavily (`$configStoreHost`, `$type`, `$store`, `$selectEmbedded`, …). The + replacement servlet must reproduce public-field exposure (see below) so page classes need minimal + edits. + +## Shared infrastructure (built once, in the pilot increment) New code in `openam-core`, package `com.sun.identity.config.servlet`: -- **`ConfiguratorServlet`** (Jakarta `HttpServlet`) — `GET /config/setup/` renders - `.ftl`; `POST /config/setup/?action=` dispatches to the page handler's named - method (small action→method map or reflection — mirrors how Click's `ActionLink` bound - `?actionLink=` to a method). Self-gates on `AMSetupServlet.isConfigured()` (port of - `ProtectedPage.onSecurityCheck`). -- **`ConfiguratorContext`** — thin wrapper over request/response/session replacing Click `Context` - (`getRequest/getResponse/getSessionAttribute/setSessionAttribute/removeSessionAttribute/ - getWriter`). -- **`SetupPage`** — new base = `AjaxPage` minus Click. Move `AjaxPage`'s framework-agnostic helpers - here (or make `AjaxPage extend SetupPage`): the i18n bundle (`amConfigurator`), - `writeValid/writeInvalid/writeJsonResponse`, `toString/toBoolean/toInt`, `getConnection` (OpenDJ), - session helpers. Holds a `ConfiguratorContext`. -- **`Step1Page extends SetupPage`** — port of `Step1.checkAdminPassword`/`checkAgentPassword` - (logic copied 1:1; only the `getContext()`/`ActionLink` seams change). -- A singleton FreeMarker `Configuration` with a `WebappTemplateLoader` rooted at `/config` - (templates stay in the webapp). - -Templates / wiring: -- `openam-server-only/src/main/webapp/config/setup/step1.ftl` — FreeMarker port of `step1.htm`. - Model exposes `page` (the handler, for `getLocalizedString`), `context`, `path`. -- `openam-server-only/src/main/webapp/WEB-INF/web.xml` — register `ConfiguratorServlet` mapped to - `/config/setup/*` **only**; leave `click-servlet` + `*.htm` untouched. -- `openam-core/pom.xml` — add managed `org.freemarker:freemarker` (version from root pom - `freemarker.version` = 2.3.31). - -## Phase 2 — Verify the pilot - -- **Automated** (match openam-core's TestNG/JUnit + a servlet mock): - 1. Render `step1.ftl` via the FreeMarker `Configuration` with a stub `page` model → assert the - localized title and `adminPassword`/`adminConfirm` inputs render. - 2. Drive `Step1Page.checkAdminPassword` with mock requests: mismatch → `passwords.do.not.match`; - `<8` chars → `invalid.password.length`; valid → `OK` + `SessionAttributeNames. - CONFIG_VAR_ADMIN_PWD` set in session. Assert the `{"valid":..}` JSON body. -- **Manual smoke:** build the `openam-server-only` WAR, deploy on Tomcat 10 / Jetty 11 - (unconfigured), browse `/openam/config/setup/step1`, confirm AJAX validation + localized errors. -- Record the exact working pattern below so rollout is copy-paste. +- **`ConfiguratorServlet`** (`jakarta.servlet.HttpServlet`) — the wrapper/dispatcher, **mapped to + `*.htm`**. For each request: + 0. Look up the URL in the **migrated-page registry** (a small explicit `path → class` map — we + hard-wire the known pages rather than re-implement Click's CamelCase automapping). **Not + migrated → `getNamedDispatcher("click-servlet").forward(req, res)`** and return. Migrated → + continue. + 1. Instantiate the page class, attach a `ConfiguratorContext`, call `onSecurityCheck()`; if it + returns false (already configured), render nothing — the port of `ProtectedPage.onSecurityCheck`. + 2. If the request carries `?actionLink=` (on GET or POST — Click checks both), **dispatch to + the same-named handler method** and stop (the handler writes the response directly). This is the + port of Click's `ActionLink` → method binding. + 3. Otherwise run `onInit()`/`onGet()`, build the FreeMarker model, render the page's `.ftl`. + Dispatch is restricted to methods marked with a new **`@ConfiguratorAction`** annotation (action + name defaults to the method name), so only intended handlers are web-invocable — a safe, explicit + replacement for the public `ActionLink` fields. Adding a page later = one registry entry (Java + only); the Click-fallback branch is deleted in the final increment. +- **`ConfiguratorContext`** — thin wrapper over `HttpServletRequest`/`HttpServletResponse`/session, + the drop-in for Click `Context.getThreadLocalContext()`: `getRequest()`, `getResponse()`, + `getSession()`, `get/set/removeSessionAttribute(...)`, `getWriter()`. Passed into the page (a field) + instead of Click's thread-local lookup. +- **`SetupPage`** — new non-Click base class = the framework-agnostic half of `AjaxPage`, adapted to + hold a `ConfiguratorContext` instead of calling `getContext()`. Carries: the `amConfigurator` i18n + (`getLocalizedString`, bundle init from the request locale), the response writers + (`writeValid/writeInvalid/writeJsonResponse/writeToResponse`), param coercion + (`toString/toBoolean/toInt`), OpenDJ `getConnection` + LDAP error mapping, session helpers, the + base AJAX handlers (`checkPasswords`, `validateInput`, `resetSessionAttributes`), and a + `skipRender` flag replacing the `setPath(null)` idiom. Overridable lifecycle hooks: + `onSecurityCheck()`/`onInit()`/`onGet()`/`onRender()`. + - To keep the two engines behaviorally identical during coexistence, extract the **pure** helpers + (no request/response: `getConnection`, `getMessage(ResultCode)`, `toBoolean`, `toInt`, the JSON + string builders, password-length/host/port checks) into a shared static `SetupUtils` used by + **both** `SetupPage` and the still-live Click `AjaxPage`. One source of truth for validation + semantics that must stay byte-identical; the duplication shrinks to nothing as pages migrate. +- **FreeMarker `Configuration`** — a lazily-initialized singleton (add `org.freemarker:freemarker` to + `openam-core/pom.xml`; version is managed in root pom, `freemarker.version = 2.3.31`, already used + by `openam-oauth2`). Templates load from a non-web-accessible root + `WEB-INF/templates/config/**` via a `WebappTemplateLoader` (so `.ftl` sources are never served + raw). Object wrapper exposes public methods so `${page.getLocalizedString("k")}` works. + +**FreeMarker model per render** (reproduces what Click injected; templates are ported to reference +the same names): `page` (the handler — for `getLocalizedString`), `context` (= `request +.getContextPath()`), `path` (= the request's own `.htm` servlet path, so `$context$path` still +posts-back-to-self), plus the handler's **public fields** copied in by reflection (Click parity), +plus any explicit `addModel(...)` entries (e.g. `startingTab` for the shell, `store`/`type` for +Step 3). Drop Click's `request`/`response`/`session`/`format`/`messages` keys unless a template +actually references them (the wizard templates use only `page`/`context`/`path` + page vars). + +## The per-page increment recipe (repeat for each page) + +Each increment is a self-contained, mergeable unit. Order within an increment: + +1. **Port the template**: create `WEB-INF/templates/config/<...>/.ftl` from the Velocity + `.htm`, using the token map below. Keep the URL, the inline `
-

$page.getLocalizedString("step1.title")

-

$page.getLocalizedString("step1.description")

- +

${page.getLocalizedString('step1.title')}

+

${page.getLocalizedString('step1.description')}

+
-

* $page.getLocalizedString("required.field.label")

+

* ${page.getLocalizedString('required.field.label')}

-
$page.getLocalizedString("step1.subtitle")
+
${page.getLocalizedString('step1.subtitle')}
- + - + -
$page.getLocalizedString("step1.admin.user.name")
${page.getLocalizedString('step1.admin.user.name')}
 * $page.getLocalizedString("password.label") * ${page.getLocalizedString('password.label')} - @@ -56,16 +56,16 @@

$page.getLocalizedString("step1.title") +

 * ${page.getLocalizedString('confirm.label')} - + onchange="APP.callDelayed(this,validateAdminPasswords)">
+
diff --git a/openam-server-only/src/main/webapp/WEB-INF/web.xml b/openam-server-only/src/main/webapp/WEB-INF/web.xml index 87d493406e..85da60ec56 100644 --- a/openam-server-only/src/main/webapp/WEB-INF/web.xml +++ b/openam-server-only/src/main/webapp/WEB-INF/web.xml @@ -350,10 +350,17 @@ + click-servlet org.openidentityplatform.openam.click.ClickServlet + + configurator-servlet + org.openidentityplatform.openam.config.servlet.ConfiguratorServlet + + MonitoringFedConfig com.sun.identity.configuration.MonitoringFedConfig @@ -739,7 +746,7 @@ - click-servlet + configurator-servlet *.htm From f7430094d2f96619e1356a568b5a2a0eede35cce Mon Sep 17 00:00:00 2001 From: maximthomas Date: Tue, 7 Jul 2026 10:42:52 +0300 Subject: [PATCH 04/27] Increment 2 migration --- .../04-implementation-notes.md | 108 ++++++- .../com/sun/identity/config/wizard/Step2.java | 56 ++-- .../com/sun/identity/config/wizard/Step4.java | 173 ++++++------ .../com/sun/identity/config/wizard/Step5.java | 33 ++- .../com/sun/identity/config/wizard/Step6.java | 48 ++-- .../config/servlet/ConfiguratorContext.java | 5 + .../config/servlet/ConfiguratorServlet.java | 8 + .../openam/config/servlet/SetupPage.java | 37 +++ .../sun/identity/config/wizard/Step2Test.java | 143 ++++++++++ .../sun/identity/config/wizard/Step4Test.java | 264 ++++++++++++++++++ .../sun/identity/config/wizard/Step5Test.java | 153 ++++++++++ .../sun/identity/config/wizard/Step6Test.java | 142 ++++++++++ .../servlet/ConfiguratorServletTest.java | 12 +- .../templates/config/wizard/step2.ftl} | 79 +++--- .../templates/config/wizard/step4.ftl} | 218 +++++++-------- .../templates/config/wizard/step5.ftl} | 44 +-- .../templates/config/wizard/step6.ftl} | 42 +-- 17 files changed, 1222 insertions(+), 343 deletions(-) create mode 100644 openam-core/src/test/java/com/sun/identity/config/wizard/Step2Test.java create mode 100644 openam-core/src/test/java/com/sun/identity/config/wizard/Step4Test.java create mode 100644 openam-core/src/test/java/com/sun/identity/config/wizard/Step5Test.java create mode 100644 openam-core/src/test/java/com/sun/identity/config/wizard/Step6Test.java rename openam-server-only/src/main/webapp/{config/wizard/step2.htm => WEB-INF/templates/config/wizard/step2.ftl} (67%) rename openam-server-only/src/main/webapp/{config/wizard/step4.htm => WEB-INF/templates/config/wizard/step4.ftl} (75%) rename openam-server-only/src/main/webapp/{config/wizard/step5.htm => WEB-INF/templates/config/wizard/step5.ftl} (74%) rename openam-server-only/src/main/webapp/{config/wizard/step6.htm => WEB-INF/templates/config/wizard/step6.ftl} (71%) diff --git a/docs/migration/click-to-freemarker/04-implementation-notes.md b/docs/migration/click-to-freemarker/04-implementation-notes.md index 2fac845e1f..08cbbe6f25 100644 --- a/docs/migration/click-to-freemarker/04-implementation-notes.md +++ b/docs/migration/click-to-freemarker/04-implementation-notes.md @@ -1,7 +1,7 @@ -# Implementation notes: increments 0 & 1 +# Implementation notes: increments 0, 1 & 2 -Findings from actually building the pilot, worth knowing before increments 2+. Background in -`02-recommendation.md` / `03-migration-plan.md`. +Findings from actually building the pilot (0, 1) and the Steps 2/4/5/6 batch (2), worth knowing +before increments 3+. Background in `02-recommendation.md` / `03-migration-plan.md`. ## FreeMarker's `WebappTemplateLoader` cannot be used (javax vs jakarta) @@ -133,3 +133,105 @@ on `AMSetupServlet`. Skipped for this pilot as disproportionate for one `if`; re - `ConfiguratorServlet.PAGES` is a `private static final Map>` populated in a static initializer — migrating a page is one `PAGES.put(...)` line, nothing else in the servlet changes. + +## Increment 2: Steps 2, 4, 5, 6 + +All four pages migrated in one batch (Java-only registry entries, no `web.xml` changes needed — +the `*.htm` wiring from increment 1 already covers them). `ConfiguratorServletTest`'s +"unregistered path forwards to Click" example was moved from `step2.htm` (now migrated) to +`step3.htm` (still Click, next up in increment 3) — update this again whenever step3 lands. + +### Step6 has the same "template posts to the base handler, not its own ActionLink" quirk as Step1 + +`step6.htm`'s `validateAgentPasswords()` posts `?actionLink=checkPasswords&type=agent` — the +inherited `SetupPage.checkPasswords()` (same base handler Step1 uses), **not** `Step6`'s own +`checkAgentPassword()`/`validateAgent` ActionLink. Confirmed by reading the template JS, matching +the exact situation `01-click-inventory.md` already flagged for Step1. Handled the same way: +`checkAgentPassword()` is kept, `@ConfiguratorAction`-annotated, purely for URL-preservation +(it was a real, if unreferenced, Click `ActionLink` before) — not deleted, not treated as the +live path. `Step6Test` covers it directly; the actual template-driven behavior is exercised via +`Step1Test`/`ConfiguratorServletTest`'s coverage of the shared `checkPasswords`. + +### Two pre-existing Velocity-silently-blank template variables in step4.htm — now need `!""` in FreeMarker + +Cross-checked every `$var` in each `.htm` against every `addModel(...)` call in the matching +`.java` (the systematic check the plan's token-map section recommends but doesn't spell out a +method for — `grep -oE '\$[A-Za-z_][A-Za-z0-9_]*'` on the template vs. `grep -oE +'addModel\("[A-Za-z0-9_]+"'` on the page class, diffed by eye). Found two gaps in step4.htm/ +Step4.java, both pre-existing Click/Velocity behavior preserved verbatim, not "fixed": + +1. **`$selectADUserStoreSSL` is referenced by the template but never `addModel`-ed anywhere in + `Step4.java`.** Always silently blank in the old Velocity render. Ported as + `${selectADUserStoreSSL!""}`. +2. **The `LDAPv3ForODSEE` branch of `Step4.onInit()` has a genuine bug**: it sets + `selectLDAPv3odsee` to `"checked=\"checked\""` and then, two lines later, overwrites the same + key back to `""` (should have been `selectLDAPv3opends`). Net effect, unchanged by this port: + picking the ODSEE store type leaves *both* the ODSEE and OpenDS radio buttons unchecked, and + `selectLDAPv3opends` is left unset for that one request. Old Velocity rendered the unset var as + silently empty; FreeMarker's strict undefined-variable check would turn it into a 500. Ported + as `${selectLDAPv3opends!""}` in the template, with a comment at the bug site in `Step4.java` + pointing here. The Java logic itself was copied 1:1 (bug included) per the byte-identical + constraint — this is a pre-existing cosmetic bug, not something introduced by the migration, + and not this increment's job to fix. + +Worth doing this same `$var`-vs-`addModel` diff before templating any future page, especially +Step3 (increment 3), which has by far the most public-field/model-var surface area. + +### `Step4.setAll()` was already dead code before this port — left un-annotated + +No `ActionLink` field bound to it in the old `Step4.java`, and no template ever calls +`?actionLink=setAll`. Unlike Step1's/Step6's unreferenced-by-template-but-still-a-real-ActionLink +methods (kept annotated for URL parity), `setAll()` never had a `ClickActionLink` at all, so it +was never web-invokable in the first place. Kept as a plain, non-`@ConfiguratorAction` method +(matches its old unreachable state exactly) rather than either deleting it or making it newly +invokable. + +### New `SetupPage` helpers added for Step2/Step4: `getAttribute`, `getHostName`, `getCookieDomain`, `getBaseDir` + +These four were called out in the increment-1 notes as "add only when the page that needs them +migrates." Step2 needed `getBaseDir`/`getCookieDomain` (which itself calls `getHostName`); Step4 +needed `getAttribute`/`getHostName`. Added directly to `SetupPage` as `protected` instance +methods, copied 1:1 from `AjaxPage`'s originals but reading through `ConfiguratorContext` instead +of Click's `Context`. Deliberately **not** extracted into `SetupUtils` and **not** back-ported +into `AjaxPage`: unlike `checkPasswords`'s validation semantics (genuine byte-identical-behavior +risk, hence the `SetupUtils` extraction in increment 1), these are one-line, low-risk +computations (`request.getServerName()`, a `System.getProperty("user.home")` fallback) with no +realistic drift risk between the two engines, so duplicating them is cheaper and more surgical +than touching `AjaxPage` (still used by unmigrated pages) for no behavioral benefit. Revisit this +call if a future increment's helper *does* carry real validation-style risk. + +`ConfiguratorContext` also gained `getServletContext()` (delegates to +`request.getServletContext()`), needed by Step2's `deployURI` computation — mirrors Click's own +`Context.getServletContext()`. + +### Handlers with no `writeToResponse` call still need an explicit `skipRender()` + +The increment-1 note about `SetupPage.writeToResponse()` baking in `skipRender()` only covers +handlers that write a body. Three ported handlers never call `writeToResponse` at all — +`Step4.setUMEmbedded()`, `Step4.resetUMEmbedded()`, `Step5.clear()` — matching the old Click code, +where these fire-and-forget AJAX endpoints (`setUMEmbedded`/`resetUMEmbedded`/`clear` in the +templates have no response-handler callback) genuinely write nothing and only called +`setPath(null)`. These three keep an explicit `skipRender()` call (translated 1:1 from +`setPath(null)`), since there's no implicit trigger for them. Everywhere else, a trailing +`setPath(null)` immediately after a `writeToResponse`/`writeValid`/`writeInvalid` call was dropped +entirely (including one at the very *top* of `Step4.validateUMDomainName()`, confirmed redundant +by tracing that every exit path of that method writes a response) — matching the pattern already +set by the increment-1 Step1 port. + +### `ServicesDefaultValues.isCookieDomainValid` is untestable from an `openam-core` unit test + +`Step2.validateCookieDomain()`'s first line calls `ServicesDefaultValues.isCookieDomainValid(...)` +unconditionally. That class has an eagerly-initialized singleton +(`private static ServicesDefaultValues instance = new ServicesDefaultValues();`) whose constructor +loads `ResourceBundle.getBundle("serviceDefaultValues")` — and that resource +(`serviceDefaultValues.properties`) lives only under +`openam-server-only/src/main/resources/config/`, never on `openam-core`'s classpath (main or +test) in any build configuration, since `openam-core` is upstream of `openam-server-only` in the +reactor, not the reverse. First hit while writing `Step2Test`: any locale, any environment, +calling this method from an `openam-core` test throws `ExceptionInInitializerError` / +`NoClassDefFoundError`. Not fixable without duplicating a production resource file into +`openam-core`'s test resources purely for this, which is out of scope here. `Step2Test` covers +`validateConfigDir()` fully and documents this gap inline instead of testing +`validateCookieDomain()`; the manual smoke step (deploy the unconfigured WAR, exercise Step 2 in +the browser) is the only current coverage for that handler, same category as the still-open +"real FreeMarker render of `step1.ftl` isn't unit-tested" gap above. diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step2.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step2.java index 34cc0ce668..79c9cb39bc 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step2.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step2.java @@ -25,12 +25,12 @@ * $Id: Step2.java,v 1.15 2010/01/04 19:08:36 veiming Exp $ * * Portions Copyrighted 2011-2016 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; import com.sun.identity.setup.AMSetupServlet; import com.sun.identity.setup.ServicesDefaultValues; import com.sun.identity.setup.SetupConstants; @@ -38,14 +38,22 @@ import java.net.MalformedURLException; import java.net.URL; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.SetupPage; import org.forgerock.openam.utils.StringUtils; -public class Step2 extends ProtectedPage { - public ActionLink validateConfigDirLink = - new ActionLink("validateConfigDir", this, "validateConfigDir"); - public ActionLink validateCookieDomainLink = - new ActionLink("validateCookieDomain", this, "validateCookieDomain"); +public class Step2 extends SetupPage { + + @Override + public boolean onSecurityCheck() { + // Ported from the old com.sun.identity.config.util.ProtectedPage: block re-entry once + // OpenAM has already been configured. + if (AMSetupServlet.isConfigured()) { + skipRender(); + return false; + } + return true; + } @Override public void onInit() { @@ -88,9 +96,9 @@ public void onInit() { if (!hasWritePermission(baseDir)) { String deployURI = getContext().getServletContext().getContextPath(); - add("initialCheck", "" + - getLocalizedString("configuration.wizard.step2.no.write.permission.to.basedir") + + getLocalizedString("configuration.wizard.step2.no.write.permission.to.basedir") + ""); } else if (alreadyHasContent(baseDir)) { String deployURI = getContext().getServletContext().getContextPath(); @@ -101,17 +109,17 @@ public void onInit() { } else { add("initialCheck", ""); } - + super.onInit(); } - + private static boolean alreadyHasContent(String dirName) { - File f = new File(dirName); - + File f = new File(dirName); + if (f.exists() && f.isDirectory()) { return (f.list().length > 0); } - + return false; } @@ -122,10 +130,11 @@ private static boolean hasWritePermission(String dirName) { } return (f == null) ? false : f.isDirectory() && f.canWrite(); } - + + @ConfiguratorAction public boolean validateConfigDir() { String configDir = toString("dir"); - + if (configDir == null) { writeToResponse(getLocalizedString("missing.required.field")); } else if (!hasWritePermission(configDir)) { @@ -139,10 +148,10 @@ public boolean validateConfigDir() { SessionAttributeNames.CONFIG_DIR, configDir); writeToResponse("true"); } - setPath(null); - return false; + return false; } + @ConfiguratorAction public boolean validateCookieDomain() { String serverUrl = toString("serverurl"); String domain = toString("domain"); @@ -155,10 +164,9 @@ public boolean validateCookieDomain() { getContext().setSessionAttribute(SessionAttributeNames.COOKIE_DOMAIN, domain); writeToResponse("true"); } - setPath(null); return false; } - + private boolean mismatchedCookieDomain(String serverUrl, String domain) { if (StringUtils.isNotEmpty(serverUrl) && StringUtils.isNotEmpty(domain)) { try { @@ -173,14 +181,14 @@ private boolean mismatchedCookieDomain(String serverUrl, String domain) { } private String getServerURL() { - String hostname = (String)getContext().getRequest().getServerName(); - int portnum = (int)getContext().getRequest().getServerPort(); - String protocol = (String)getContext().getRequest().getScheme(); + String hostname = getContext().getRequest().getServerName(); + int portnum = getContext().getRequest().getServerPort(); + String protocol = getContext().getRequest().getScheme(); return protocol + "://" + hostname + ":" + portnum; } /** - * used to add the key to the page and to the session so it can + * used to add the key to the page and to the session so it can * be retrieved when the final store is done */ private void add(String key, String value) { diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step4.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step4.java index 3d759a3ed2..dcaef66308 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step4.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step4.java @@ -25,12 +25,13 @@ * $Id: Step4.java,v 1.20 2009/10/27 05:31:45 hengming Exp $ * * Portions Copyrighted 2011-2016 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; +import com.sun.identity.setup.AMSetupServlet; import com.sun.identity.setup.SetupConstants; import java.io.IOException; import java.net.Socket; @@ -40,8 +41,9 @@ import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.openidentityplatform.openam.config.servlet.SetupPage; import org.forgerock.openam.ldap.LDAPRequests; import org.forgerock.openam.ldap.LDAPUtils; import org.forgerock.opendj.ldap.Connection; @@ -52,45 +54,29 @@ /** * Step 4 is the input of the remote user data store properties. */ -public class Step4 extends ProtectedPage { +public class Step4 extends SetupPage { public static final String LDAP_STORE_SESSION_KEY = "wizardCustomUserStore"; - public ActionLink validateUMHostLink = - new ActionLink("validateUMHost", this, "validateUMHost"); - public ActionLink validateUMDomainNameLink = - new ActionLink("validateUMDomainName", this, - "validateUMDomainName"); - public ActionLink setSSLLink = - new ActionLink("setSSL", this, "setSSL"); - public ActionLink setUMEmbedded = - new ActionLink("setUMEmbedded", this, "setUMEmbedded"); - public ActionLink resetUMEmbedded = - new ActionLink("resetUMEmbedded", this, "resetUMEmbedded"); - public ActionLink setHostLink = - new ActionLink("setHost", this, "setHost"); - public ActionLink setDomainNameLink = - new ActionLink("setDomainName", this, "setDomainName"); - public ActionLink setPortLink = - new ActionLink("setPort", this, "setPort"); - public ActionLink setRootSuffixLink = - new ActionLink("setRootSuffix", this, "setRootSuffix"); - public ActionLink setLoginIDLink = - new ActionLink("setLoginID", this, "setLoginID"); - public ActionLink setPasswordLink = - new ActionLink("setPassword", this, "setPassword"); - public ActionLink setStoreTypeLink = - new ActionLink("setStoreType", this, "setStoreType"); private String responseString = "ok"; private static final String ObjectClassFilter = "(objectclass=*)"; - - public Step4() { + + @Override + public boolean onSecurityCheck() { + // Ported from the old com.sun.identity.config.util.ProtectedPage: block re-entry once + // OpenAM has already been configured. + if (AMSetupServlet.isConfigured()) { + skipRender(); + return false; + } + return true; } - + + @Override public void onInit() { super.onInit(); - Context ctx = getContext(); - + ConfiguratorContext ctx = getContext(); + if (ctx.getSessionAttribute(SessionAttributeNames.USER_STORE_HOST) == null) { String val = getAttribute(SetupConstants.CONFIG_VAR_DATA_STORE, @@ -138,7 +124,7 @@ public void onInit() { String val = getAttribute(SetupConstants.USER_STORE_HOST,getHostName()); ctx.setSessionAttribute(SessionAttributeNames.USER_STORE_HOST, val); addModel("userStoreHost", val); - + val = getAttribute(SetupConstants.USER_STORE_SSL, "SIMPLE"); ctx.setSessionAttribute(SessionAttributeNames.USER_STORE_SSL, val); if (val.equals("SSL")) { @@ -156,7 +142,7 @@ public void onInit() { ctx.setSessionAttribute(SessionAttributeNames.USER_STORE_LOGIN_ID, val); addModel("userStoreLoginId", val); - val = getAttribute(SetupConstants.USER_STORE_ROOT_SUFFIX, + val = getAttribute(SetupConstants.USER_STORE_ROOT_SUFFIX, Wizard.defaultRootSuffix); ctx.setSessionAttribute(SessionAttributeNames.USER_STORE_ROOT_SUFFIX, val); @@ -185,6 +171,14 @@ public void onInit() { addModel("selectLDAPv3opends", ""); addModel("selectLDAPv3tivoli", ""); } else if (val.equals("LDAPv3ForODSEE")) { + // NOTE: pre-existing bug ported verbatim from the old Click page (verified against + // source, see docs/migration/click-to-freemarker/04-implementation-notes.md): this + // branch sets selectLDAPv3odsee to "checked" and then immediately overwrites it back + // to "" instead of setting selectLDAPv3opends. Net effect (unchanged from Click): + // neither the ODSEE nor OpenDS radio renders checked when the store type is ODSEE, + // and selectLDAPv3opends is left unset. The template defaults these vars with !"" so + // FreeMarker's strict undefined-variable check doesn't turn this cosmetic bug into a + // 500 error. addModel("selectLDAPv3odsee", "checked=\"checked\""); addModel("selectLDAPv3ad", ""); addModel("selectLDAPv3addc", ""); @@ -217,12 +211,16 @@ public void onInit() { addModel("selectExternalUM", ""); } } - - public boolean setAll() { - setPath(null); + + // Not bound to any Click ActionLink and not called by step4.htm/step4.ftl in the original + // page either - genuinely unreachable before and after this port, so left un-annotated + // (no @ConfiguratorAction) to keep it that way. Kept only because it was pre-existing code. + public boolean setAll() { + skipRender(); return false; } - + + @ConfiguratorAction public boolean setSSL() { String ssl = toString("ssl"); if ((ssl != null) && ssl.length() > 0) { @@ -233,56 +231,58 @@ public boolean setSSL() { SessionAttributeNames.USER_STORE_SSL, "SIMPLE"); } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } + @ConfiguratorAction public boolean setDomainName() { String domainname = toString("domainname"); if ((domainname != null) && domainname.length() > 0) { getContext().setSessionAttribute( - SessionAttributeNames.USER_STORE_DOMAINNAME, + SessionAttributeNames.USER_STORE_DOMAINNAME, domainname); getContext().setSessionAttribute( SessionAttributeNames.EXT_DATA_STORE, "true"); } else { - responseString = "missing.domain.name"; + responseString = "missing.domain.name"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } + @ConfiguratorAction public boolean setHost() { String host = toString("host"); if ((host != null) && host.length() > 0) { getContext().setSessionAttribute( SessionAttributeNames.USER_STORE_HOST, host); } else { - responseString = "missing.host.name"; + responseString = "missing.host.name"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } + @ConfiguratorAction public boolean setUMEmbedded() { getContext().setSessionAttribute(SessionAttributeNames.EXT_DATA_STORE, "false"); - setPath(null); + skipRender(); return false; } + @ConfiguratorAction public boolean resetUMEmbedded() { getContext().setSessionAttribute(SessionAttributeNames.EXT_DATA_STORE, "true"); - setPath(null); + skipRender(); return false; } - + + @ConfiguratorAction public boolean setPort() { String port = toString("port"); - + if ((port != null) && port.length() > 0) { int intValue = Integer.parseInt(port); if ((intValue > 0) && (intValue < 65535)) { @@ -292,39 +292,39 @@ public boolean setPort() { responseString = "invalid.port.number"; } } else { - responseString = "missing.host.port"; + responseString = "missing.host.port"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } - + + @ConfiguratorAction public boolean setLoginID() { String dn = toString("dn"); if ((dn != null) && dn.length() > 0) { getContext().setSessionAttribute( SessionAttributeNames.USER_STORE_LOGIN_ID, dn); } else { - responseString = "missing.login.id"; + responseString = "missing.login.id"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } - + + @ConfiguratorAction public boolean setPassword() { String pwd = toString("password"); if ((pwd != null) && pwd.length() > 0) { getContext().setSessionAttribute( SessionAttributeNames.USER_STORE_LOGIN_PWD, pwd); } else { - responseString = "missing.password"; + responseString = "missing.password"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } - + + @ConfiguratorAction public boolean setRootSuffix() { String rootsuffix = toString("rootsuffix"); @@ -333,33 +333,33 @@ public boolean setRootSuffix() { getContext().setSessionAttribute( SessionAttributeNames.USER_STORE_ROOT_SUFFIX, rootsuffix); } else { - responseString = "invalid.dn"; + responseString = "invalid.dn"; } } else { - responseString = "missing.root.suffix"; + responseString = "missing.root.suffix"; } writeToResponse(getLocalizedString(responseString)); - setPath(null); return false; } - + + @ConfiguratorAction public boolean setStoreType() { String type = toString("type"); if ((type != null) && type.length() > 0) { getContext().setSessionAttribute( SessionAttributeNames.USER_STORE_TYPE, type); - } + } writeToResponse(responseString); - setPath(null); return false; } - + + @ConfiguratorAction public boolean validateUMHost() { - Context ctx = getContext(); + ConfiguratorContext ctx = getContext(); String strSSL = (String)ctx.getSessionAttribute( SessionAttributeNames.USER_STORE_SSL); boolean ssl = (strSSL != null) && (strSSL.equals("SSL")); - + String host = (String)ctx.getSessionAttribute( SessionAttributeNames.USER_STORE_HOST); String strPort = (String)ctx.getSessionAttribute( @@ -371,7 +371,7 @@ public boolean validateUMHost() { SessionAttributeNames.USER_STORE_ROOT_SUFFIX); String bindPwd = (String)ctx.getSessionAttribute( SessionAttributeNames.USER_STORE_LOGIN_PWD); - + try (Connection conn = getConnection(host, port, bindDN, bindPwd.toCharArray(), 5, ssl)) { //String filter = "cn=" + "\"" + rootSuffix + "\""; // NOT SURE Why "cn" is specified. would never work. String[] attrs = {""}; @@ -381,18 +381,17 @@ public boolean validateUMHost() { ResultCode resultCode = lex.getResult().getResultCode(); if (!writeErrorToResponse(resultCode)) { writeToResponse(getLocalizedString("cannot.connect.to.SM.datastore")); - } + } } catch (Exception e) { writeToResponse(getLocalizedString("cannot.connect.to.SM.datastore")); } - setPath(null); return false; } + @ConfiguratorAction public boolean validateUMDomainName() { - setPath(null); - Context ctx = getContext(); + ConfiguratorContext ctx = getContext(); String strSSL = (String)ctx.getSessionAttribute( SessionAttributeNames.USER_STORE_SSL); boolean ssl = (strSSL != null) && (strSSL.equals("SSL")); @@ -401,7 +400,7 @@ public boolean validateUMDomainName() { SessionAttributeNames.USER_STORE_DOMAINNAME); String rootSuffixAD = dnsDomainToDN(domainName); getContext().setSessionAttribute( - SessionAttributeNames.USER_STORE_ROOT_SUFFIX, + SessionAttributeNames.USER_STORE_ROOT_SUFFIX, rootSuffixAD); String[] hostAndPort = {""}; try { @@ -414,7 +413,7 @@ public boolean validateUMDomainName() { writeToResponse( getLocalizedString("cannot.connect.to.UM.datastore")); return false; - } + } String host = hostAndPort[0]; int port = Integer.parseInt(hostAndPort[1]); @@ -424,7 +423,7 @@ public boolean validateUMDomainName() { SessionAttributeNames.USER_STORE_ROOT_SUFFIX); String bindPwd = (String)ctx.getSessionAttribute( SessionAttributeNames.USER_STORE_LOGIN_PWD); - + try (Connection conn = getConnection(host, port, bindDN, bindPwd.toCharArray(), 3, ssl)) { //String filter = "cn=" + "\"" + rootSuffix + "\""; String[] attrs = {""}; @@ -443,20 +442,20 @@ public boolean validateUMDomainName() { // Method to get hostname and port number with the // provided Domain Name for Active Directory user data store. - private String[] getLdapHostAndPort(String domainName) + private String[] getLdapHostAndPort(String domainName) throws NamingException, IOException { if (!domainName.endsWith(".")) { domainName+='.'; } DirContext ictx = null; // Check if domain name is a valid one. - // The resource record type A is defined in RFC 1035. + // The resource record type A is defined in RFC 1035. try { Hashtable env = new Hashtable(); - env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, + env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory"); ictx = new InitialDirContext(env); - Attributes attributes = + Attributes attributes = ictx.getAttributes(domainName, new String[]{"A"}); Attribute attrib = attributes.get("A"); if (attrib == null) { @@ -474,10 +473,10 @@ private String[] getLdapHostAndPort(String domainName) final String ldapServer = "_ldap._tcp." + domainName; try { // Attempting to resolve ldapServer to SRV record. - // This is a mechanism defined in MSDN, querying + // This is a mechanism defined in MSDN, querying // SRV records for _ldap._tcp.DOMAINNAME. // and get host and port from domain. - Attributes attributes = + Attributes attributes = ictx.getAttributes(ldapServer, new String[]{"SRV"}); Attribute attr = attributes.get("SRV"); if (attr == null) { @@ -485,12 +484,12 @@ private String[] getLdapHostAndPort(String domainName) } String[] srv = attr.get().toString().split(" "); String hostNam = srv[3]; - serverHostName = + serverHostName = hostNam.substring(0, hostNam.length() -1); - if ((serverHostName != null) && + if ((serverHostName != null) && serverHostName.length() > 0) { getContext().setSessionAttribute( - SessionAttributeNames.USER_STORE_HOST, + SessionAttributeNames.USER_STORE_HOST, serverHostName); } serverPortStr = srv[2]; @@ -500,7 +499,7 @@ private String[] getLdapHostAndPort(String domainName) throw e; } - // try to connect to LDAP port to make sure this machine + // try to connect to LDAP port to make sure this machine // has LDAP service int serverPort = Integer.parseInt(serverPortStr); if ((serverPort > 0) && (serverPort < 65535)) { @@ -521,7 +520,7 @@ private String[] getLdapHostAndPort(String domainName) } // Method to convert the domain name to the root suffix. - // eg., Domain Name amqa.test.com is converted to root suffix + // eg., Domain Name amqa.test.com is converted to root suffix // DC=amqa,DC=test,DC=com static String dnsDomainToDN(String domainName) { StringBuilder buf = new StringBuilder(); diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step5.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step5.java index 49877744ad..ee2e856ea0 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step5.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step5.java @@ -25,6 +25,7 @@ * $Id: Step5.java,v 1.9 2009/01/05 23:17:10 veiming Exp $ * * Portions Copyrighted 2011-2016 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; @@ -32,10 +33,11 @@ import java.net.MalformedURLException; import java.net.URL; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.SetupPage; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; +import com.sun.identity.setup.AMSetupServlet; /** * Wizard Step # 5: Site Name, URL and Session HA Failover indicator. @@ -45,18 +47,20 @@ * as this information will be replicated by the underlying store. * */ -public class Step5 extends ProtectedPage { +public class Step5 extends SetupPage { - public ActionLink clearLink = new ActionLink( - "clear", this, "clear"); - public ActionLink validateLink = new ActionLink( - "validateURL", this, "validateURL"); - public ActionLink validateSiteLink = new ActionLink( - "validateSite", this, "validateSite"); - - public Step5() { + @Override + public boolean onSecurityCheck() { + // Ported from the old com.sun.identity.config.util.ProtectedPage: block re-entry once + // OpenAM has already been configured. + if (AMSetupServlet.isConfigured()) { + skipRender(); + return false; + } + return true; } + @Override public void onInit() { String host = (String) getContext().getSessionAttribute( SessionAttributeNames.LB_SITE_NAME); @@ -77,10 +81,11 @@ public void onInit() { * * @return boolean indicator to view. */ + @ConfiguratorAction public boolean clear() { getContext().removeSessionAttribute(SessionAttributeNames.LB_SITE_NAME); getContext().removeSessionAttribute(SessionAttributeNames.LB_PRIMARY_URL); - setPath(null); + skipRender(); return false; } @@ -91,6 +96,7 @@ public boolean clear() { * port = primary loadURL * Just a little confusing! */ + @ConfiguratorAction public boolean validateSite() { boolean returnVal = false; String siteName = toString("host"); @@ -102,7 +108,6 @@ public boolean validateSite() { SessionAttributeNames.LB_SITE_NAME, siteName); writeValid("ok.label"); } - setPath(null); return returnVal; } @@ -113,6 +118,7 @@ public boolean validateSite() { * port = primary loadURL * Just a little confusing! */ + @ConfiguratorAction public boolean validateURL() { boolean returnVal = false; String primaryURL = toString("port"); @@ -139,7 +145,6 @@ public boolean validateURL() { returnVal = true; } } - setPath(null); return returnVal; } diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step6.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step6.java index 012cd0b07c..eb4f485218 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step6.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step6.java @@ -25,63 +25,75 @@ * $Id: Step6.java,v 1.13 2009/01/05 23:17:10 veiming Exp $ * * Portions Copyrighted 2011-2014 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; -import org.openidentityplatform.openam.click.control.ActionLink; +import com.sun.identity.setup.AMSetupServlet; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.SetupPage; /** * This is the first step in the advanced configuration flow. * The user will be required to add the default admin password and * the agent passwords. */ -public class Step6 extends ProtectedPage { - - // this links required for client side validation calls - public ActionLink validateAgent = - new ActionLink("checkAgentPassword", this, "checkAgentPassword" ); - - public void onInit() { +public class Step6 extends SetupPage { + + @Override + public boolean onSecurityCheck() { + // Ported from the old com.sun.identity.config.util.ProtectedPage: block re-entry once + // OpenAM has already been configured. + if (AMSetupServlet.isConfigured()) { + skipRender(); + return false; + } + return true; + } + + @Override + public void onInit() { String agentPwd = (String)getContext().getSessionAttribute( SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD); if (agentPwd != null) { addModel("agentPassword",agentPwd); } - + String confirmPwd = (String)getContext().getSessionAttribute( SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD_CONFIRM); if (confirmPwd != null) { addModel("agentConfirm", confirmPwd); } - + super.onInit(); } - + + // Not referenced by step6.htm/step6.ftl (the template posts to the base checkPasswords + // handler instead, same as Step1), but kept web-invokable for byte-identical behavior with + // the old Click page - see docs/migration/click-to-freemarker/04-implementation-notes.md. + @ConfiguratorAction public boolean checkAgentPassword() { String agentPassword = toString("agent"); String agentConfirm = toString("agentConfirm"); String tmpadmin = (String)getContext().getSessionAttribute( SessionAttributeNames.CONFIG_VAR_ADMIN_PWD); - - if (agentPassword == null || agentConfirm == null) { + + if (agentPassword == null || agentConfirm == null) { writeInvalid(getLocalizedString("missing.required.field")); } else if (agentPassword.equals(tmpadmin)) { writeInvalid(getLocalizedString("agent.admin.passwords.match")); } else if (agentPassword.length() < 8) { writeInvalid(getLocalizedString("invalid.password.length")); } else if (!agentPassword.equals(agentConfirm)) { - writeInvalid(getLocalizedString("passwords.do.not.match")); + writeInvalid(getLocalizedString("passwords.do.not.match")); } else { writeValid("OK"); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD, agentPassword); } - setPath(null); return false; - } + } } - diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorContext.java b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorContext.java index 57b7a333d1..25e4361003 100644 --- a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorContext.java +++ b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorContext.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.PrintWriter; +import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; @@ -48,6 +49,10 @@ public HttpSession getSession() { return request.getSession(); } + public ServletContext getServletContext() { + return request.getServletContext(); + } + public Object getSessionAttribute(String name) { HttpSession session = request.getSession(false); return session == null ? null : session.getAttribute(name); diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java index 21ad953b44..8affeda808 100644 --- a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java +++ b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java @@ -31,6 +31,10 @@ import jakarta.servlet.http.HttpServletResponse; import com.sun.identity.config.wizard.Step1; +import com.sun.identity.config.wizard.Step2; +import com.sun.identity.config.wizard.Step4; +import com.sun.identity.config.wizard.Step5; +import com.sun.identity.config.wizard.Step6; import com.sun.identity.shared.debug.Debug; import freemarker.template.Configuration; import freemarker.template.Template; @@ -61,6 +65,10 @@ public class ConfiguratorServlet extends HttpServlet { private static final Map> PAGES = new HashMap<>(); static { PAGES.put("/config/wizard/step1.htm", Step1.class); + PAGES.put("/config/wizard/step2.htm", Step2.class); + PAGES.put("/config/wizard/step4.htm", Step4.class); + PAGES.put("/config/wizard/step5.htm", Step5.class); + PAGES.put("/config/wizard/step6.htm", Step6.class); } private volatile Configuration freemarkerConfig; diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java index 2088cd250c..5cadc3319f 100644 --- a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java +++ b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java @@ -15,6 +15,7 @@ */ package org.openidentityplatform.openam.config.servlet; +import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.security.GeneralSecurityException; @@ -27,6 +28,7 @@ import jakarta.servlet.http.HttpServletResponse; import com.sun.identity.config.SessionAttributeNames; +import com.sun.identity.setup.AMSetupServlet; import com.sun.identity.setup.SetupConstants; import com.sun.identity.shared.debug.Debug; import com.sun.identity.shared.locale.Locale; @@ -55,6 +57,7 @@ public abstract class SetupPage { private boolean skipRender = false; private java.util.Locale configLocale; private ResourceBundle rb; + private String hostName; public String responseString = "true"; @@ -209,6 +212,40 @@ public String getLocalizedString(String i18nKey) { return (localizedValue == null) ? i18nKey : localizedValue; } + protected String getAttribute(String attr, String defaultValue) { + String value = (String) getContext().getSessionAttribute(attr); + return (value != null) ? value : defaultValue; + } + + protected String getHostName() { + if (hostName == null) { + hostName = getContext().getRequest().getServerName(); + } + return hostName; + } + + protected String getCookieDomain() { + return getHostName(); + } + + protected String getBaseDir(HttpServletRequest req) { + String basedir = AMSetupServlet.getPresetConfigDir(); + if ((basedir == null) || (basedir.length() == 0)) { + String tmp = System.getProperty("user.home"); + if (File.separatorChar == '\\') { + tmp = tmp.replace('\\', '/'); + } + String uri = req.getRequestURI(); + int idx = uri.indexOf("/", 1); + if (idx != -1) { + uri = uri.substring(0, idx); + } + basedir = (tmp.endsWith("/")) ? tmp.substring(0, tmp.length() - 1) : tmp; + basedir += uri; + } + return basedir; + } + @ConfiguratorAction public boolean validateInput() { String key = toString("key"); diff --git a/openam-core/src/test/java/com/sun/identity/config/wizard/Step2Test.java b/openam-core/src/test/java/com/sun/identity/config/wizard/Step2Test.java new file mode 100644 index 0000000000..aaa11ce010 --- /dev/null +++ b/openam-core/src/test/java/com/sun/identity/config/wizard/Step2Test.java @@ -0,0 +1,143 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package com.sun.identity.config.wizard; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import com.sun.identity.config.SessionAttributeNames; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Byte-exact response and session side-effect coverage for the migrated Step2 page, matching + * the old Click AjaxPage/ProtectedPage/Step2 behavior these handlers were ported from. + */ +public class Step2Test { + + private HttpServletRequest request; + private HttpServletResponse response; + private StringWriter responseBody; + private Map sessionAttributes; + private Step2 step2; + private Path tempDir; + + @BeforeMethod + public void setup() throws Exception { + sessionAttributes = new HashMap<>(); + + HttpSession session = mock(HttpSession.class); + doAnswer(inv -> sessionAttributes.put(inv.getArgument(0), inv.getArgument(1))) + .when(session).setAttribute(anyString(), any()); + when(session.getAttribute(anyString())).thenAnswer(inv -> sessionAttributes.get(inv.getArgument(0))); + + request = mock(HttpServletRequest.class); + when(request.getSession()).thenReturn(session); + when(request.getSession(false)).thenReturn(session); + when(request.getParameter("locale")).thenReturn("en"); + + responseBody = new StringWriter(); + response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(new PrintWriter(responseBody)); + + step2 = new Step2(); + step2.setContext(new ConfiguratorContext(request, response)); + } + + @AfterMethod + public void cleanup() throws IOException { + if (tempDir != null && Files.exists(tempDir)) { + try (var stream = Files.walk(tempDir)) { + stream.sorted((a, b) -> b.compareTo(a)).forEach(p -> p.toFile().delete()); + } + } + } + + private void param(String name, String value) { + when(request.getParameter(name)).thenReturn(value); + } + + @Test + public void validateConfigDirRejectsMissingDir() { + step2.validateConfigDir(); + + assertThat(responseBody.toString()).isEqualTo("Missing Required Field"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateConfigDirRejectsNoWritePermission() throws IOException { + tempDir = Files.createTempDirectory("step2-nowrite"); + tempDir.toFile().setWritable(false); + param("dir", tempDir.toString()); + + step2.validateConfigDir(); + + assertThat(responseBody.toString()).isEqualTo("Do not have write permission to this directory."); + assertThat(sessionAttributes).isEmpty(); + + tempDir.toFile().setWritable(true); + } + + @Test + public void validateConfigDirRejectsNonEmptyDir() throws IOException { + tempDir = Files.createTempDirectory("step2-content"); + Files.createFile(tempDir.resolve("existing.txt")); + param("dir", tempDir.toString()); + + step2.validateConfigDir(); + + assertThat(responseBody.toString()).isEqualTo("Directory is not empty"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateConfigDirAcceptsWritableEmptyDir() throws IOException { + tempDir = Files.createTempDirectory("step2-ok"); + param("dir", tempDir.toString()); + + step2.validateConfigDir(); + + assertThat(responseBody.toString()).isEqualTo("true"); + assertThat(sessionAttributes.get(SessionAttributeNames.CONFIG_DIR)).isEqualTo(tempDir.toString()); + } + + // validateCookieDomain() is not covered here: its first line unconditionally calls + // ServicesDefaultValues.isCookieDomainValid(...), whose class has an eager singleton static + // initializer that loads serviceDefaultValues.properties - a resource that lives only in + // openam-server-only/src/main/resources/config/, never on openam-core's classpath (test or + // main) in any build configuration. That makes this method untestable from an openam-core + // unit test regardless of environment; see docs/migration/click-to-freemarker/ + // 04-implementation-notes.md. +} diff --git a/openam-core/src/test/java/com/sun/identity/config/wizard/Step4Test.java b/openam-core/src/test/java/com/sun/identity/config/wizard/Step4Test.java new file mode 100644 index 0000000000..82d3a16d0b --- /dev/null +++ b/openam-core/src/test/java/com/sun/identity/config/wizard/Step4Test.java @@ -0,0 +1,264 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package com.sun.identity.config.wizard; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import com.sun.identity.config.SessionAttributeNames; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Byte-exact response and session side-effect coverage for the migrated Step4 page, matching + * the old Click AjaxPage/ProtectedPage/Step4 behavior these handlers were ported from. + * + *

{@code validateUMHost}/{@code validateUMDomainName} are not covered here: both require a + * live LDAP (and, for the AD path, DNS SRV) connection, which would make this suite slow and + * environment-dependent - the same disproportionate-effort call already made for the + * already-configured branch of {@code onSecurityCheck} in increment 1's Step1Test. See + * docs/migration/click-to-freemarker/04-implementation-notes.md. + */ +public class Step4Test { + + private HttpServletRequest request; + private HttpServletResponse response; + private StringWriter responseBody; + private Map sessionAttributes; + private Step4 step4; + + @BeforeMethod + public void setup() throws Exception { + sessionAttributes = new HashMap<>(); + + HttpSession session = mock(HttpSession.class); + doAnswer(inv -> sessionAttributes.put(inv.getArgument(0), inv.getArgument(1))) + .when(session).setAttribute(anyString(), any()); + when(session.getAttribute(anyString())).thenAnswer(inv -> sessionAttributes.get(inv.getArgument(0))); + + request = mock(HttpServletRequest.class); + when(request.getSession()).thenReturn(session); + when(request.getSession(false)).thenReturn(session); + when(request.getParameter("locale")).thenReturn("en"); + + responseBody = new StringWriter(); + response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(new PrintWriter(responseBody)); + + step4 = new Step4(); + step4.setContext(new ConfiguratorContext(request, response)); + } + + private void param(String name, String value) { + when(request.getParameter(name)).thenReturn(value); + } + + @Test + public void setSSLDefaultsToSimpleWhenMissing() { + step4.setSSL(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get(SessionAttributeNames.USER_STORE_SSL)).isEqualTo("SIMPLE"); + } + + @Test + public void setSSLAcceptsExplicitValue() { + param("ssl", "SSL"); + + step4.setSSL(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get(SessionAttributeNames.USER_STORE_SSL)).isEqualTo("SSL"); + } + + @Test + public void setDomainNameRejectsMissingValue() { + step4.setDomainName(); + + assertThat(responseBody.toString()).isEqualTo("Missing Domain Name"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void setDomainNameAcceptsValue() { + param("domainname", "corp.example.com"); + + step4.setDomainName(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get(SessionAttributeNames.USER_STORE_DOMAINNAME)).isEqualTo("corp.example.com"); + assertThat(sessionAttributes.get(SessionAttributeNames.EXT_DATA_STORE)).isEqualTo("true"); + } + + @Test + public void setHostRejectsMissingValue() { + step4.setHost(); + + assertThat(responseBody.toString()).isEqualTo("Missing Host Name"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void setHostAcceptsValue() { + param("host", "ds.example.com"); + + step4.setHost(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get(SessionAttributeNames.USER_STORE_HOST)).isEqualTo("ds.example.com"); + } + + @Test + public void setUMEmbeddedWritesNoResponseBody() { + step4.setUMEmbedded(); + + assertThat(responseBody.toString()).isEmpty(); + assertThat(sessionAttributes.get(SessionAttributeNames.EXT_DATA_STORE)).isEqualTo("false"); + } + + @Test + public void resetUMEmbeddedWritesNoResponseBody() { + step4.resetUMEmbedded(); + + assertThat(responseBody.toString()).isEmpty(); + assertThat(sessionAttributes.get(SessionAttributeNames.EXT_DATA_STORE)).isEqualTo("true"); + } + + @Test + public void setPortRejectsMissingValue() { + step4.setPort(); + + assertThat(responseBody.toString()).isEqualTo("Missing Port Number"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void setPortRejectsOutOfRangeValue() { + param("port", "70000"); + + step4.setPort(); + + assertThat(responseBody.toString()) + .isEqualTo("Port must be greater than or equal to 1 and less than or equal to 65535"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void setPortAcceptsValidValue() { + param("port", "1389"); + + step4.setPort(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get(SessionAttributeNames.USER_STORE_PORT)).isEqualTo("1389"); + } + + @Test + public void setLoginIDRejectsMissingValue() { + step4.setLoginID(); + + assertThat(responseBody.toString()).isEqualTo("Missing Login ID"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void setLoginIDAcceptsValue() { + param("dn", "cn=Directory Manager"); + + step4.setLoginID(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get(SessionAttributeNames.USER_STORE_LOGIN_ID)).isEqualTo("cn=Directory Manager"); + } + + @Test + public void setPasswordRejectsMissingValue() { + step4.setPassword(); + + assertThat(responseBody.toString()).isEqualTo("Missing Password"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void setPasswordAcceptsValue() { + param("password", "secret123"); + + step4.setPassword(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get(SessionAttributeNames.USER_STORE_LOGIN_PWD)).isEqualTo("secret123"); + } + + @Test + public void setRootSuffixRejectsMissingValue() { + step4.setRootSuffix(); + + assertThat(responseBody.toString()).isEqualTo("Missing Root Suffix"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void setRootSuffixRejectsInvalidDN() { + param("rootsuffix", "not a dn"); + + step4.setRootSuffix(); + + assertThat(responseBody.toString()).isEqualTo("Invalid Distinguished Name"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void setRootSuffixAcceptsValidDN() { + param("rootsuffix", "dc=example,dc=com"); + + step4.setRootSuffix(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get(SessionAttributeNames.USER_STORE_ROOT_SUFFIX)).isEqualTo("dc=example,dc=com"); + } + + @Test + public void setStoreTypeWritesRawResponseStringWithoutLocalization() { + param("type", "LDAPv3ForAD"); + + step4.setStoreType(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get(SessionAttributeNames.USER_STORE_TYPE)).isEqualTo("LDAPv3ForAD"); + } + + @Test + public void setStoreTypeIgnoresMissingType() { + step4.setStoreType(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes).isEmpty(); + } +} diff --git a/openam-core/src/test/java/com/sun/identity/config/wizard/Step5Test.java b/openam-core/src/test/java/com/sun/identity/config/wizard/Step5Test.java new file mode 100644 index 0000000000..9c1eeeba45 --- /dev/null +++ b/openam-core/src/test/java/com/sun/identity/config/wizard/Step5Test.java @@ -0,0 +1,153 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package com.sun.identity.config.wizard; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import com.sun.identity.config.SessionAttributeNames; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Byte-exact response and session side-effect coverage for the migrated Step5 page, matching + * the old Click AjaxPage/ProtectedPage/Step5 behavior these handlers were ported from. + */ +public class Step5Test { + + private HttpServletRequest request; + private HttpServletResponse response; + private StringWriter responseBody; + private Map sessionAttributes; + private Step5 step5; + + @BeforeMethod + public void setup() throws Exception { + sessionAttributes = new HashMap<>(); + + HttpSession session = mock(HttpSession.class); + doAnswer(inv -> sessionAttributes.put(inv.getArgument(0), inv.getArgument(1))) + .when(session).setAttribute(anyString(), any()); + when(session.getAttribute(anyString())).thenAnswer(inv -> sessionAttributes.get(inv.getArgument(0))); + doAnswer(inv -> sessionAttributes.remove(inv.getArgument(0))) + .when(session).removeAttribute(anyString()); + + request = mock(HttpServletRequest.class); + when(request.getSession()).thenReturn(session); + when(request.getSession(false)).thenReturn(session); + when(request.getParameter("locale")).thenReturn("en"); + + responseBody = new StringWriter(); + response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(new PrintWriter(responseBody)); + + step5 = new Step5(); + step5.setContext(new ConfiguratorContext(request, response)); + } + + private void param(String name, String value) { + when(request.getParameter(name)).thenReturn(value); + } + + @Test + public void clearRemovesSiteAttributesAndWritesNoResponseBody() { + sessionAttributes.put(SessionAttributeNames.LB_SITE_NAME, "site1"); + sessionAttributes.put(SessionAttributeNames.LB_PRIMARY_URL, "http://site1.example.com/openam"); + + step5.clear(); + + assertThat(responseBody.toString()).isEmpty(); + assertThat(sessionAttributes).doesNotContainKeys( + SessionAttributeNames.LB_SITE_NAME, SessionAttributeNames.LB_PRIMARY_URL); + } + + @Test + public void validateSiteRejectsMissingName() { + boolean invalid = step5.validateSite(); + + assertThat(invalid).isTrue(); + assertThat(responseBody.toString()).isEqualTo("{\"valid\":\"false\", \"body\":\"Site Name Missing\"}"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateSiteAcceptsName() { + param("host", "site1"); + + boolean invalid = step5.validateSite(); + + assertThat(invalid).isFalse(); + assertThat(responseBody.toString()).isEqualTo("{\"valid\":\"true\", \"body\":\"ok.label\"}"); + assertThat(sessionAttributes.get(SessionAttributeNames.LB_SITE_NAME)).isEqualTo("site1"); + } + + @Test + public void validateURLRejectsMissingValue() { + boolean invalid = step5.validateURL(); + + assertThat(invalid).isTrue(); + assertThat(responseBody.toString()).isEqualTo("Missing Primary URL"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateURLRejectsMalformedURL() { + param("port", "not-a-url"); + + boolean invalid = step5.validateURL(); + + assertThat(invalid).isTrue(); + assertThat(responseBody.toString()).isEqualTo("Primary URL is not valid"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateURLRejectsMissingPath() { + param("port", "http://site1.example.com"); + + boolean invalid = step5.validateURL(); + + assertThat(invalid).isTrue(); + assertThat(responseBody.toString()).isEqualTo("primary.url.no.uri"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateURLAcceptsWellFormedURL() { + param("port", "http://site1.example.com/openam"); + + boolean invalid = step5.validateURL(); + + assertThat(invalid).isFalse(); + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get(SessionAttributeNames.LB_PRIMARY_URL)) + .isEqualTo("http://site1.example.com/openam"); + } +} diff --git a/openam-core/src/test/java/com/sun/identity/config/wizard/Step6Test.java b/openam-core/src/test/java/com/sun/identity/config/wizard/Step6Test.java new file mode 100644 index 0000000000..52d8bc0506 --- /dev/null +++ b/openam-core/src/test/java/com/sun/identity/config/wizard/Step6Test.java @@ -0,0 +1,142 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package com.sun.identity.config.wizard; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import com.sun.identity.config.SessionAttributeNames; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Byte-exact response and session side-effect coverage for the migrated Step6 page, matching + * the old Click AjaxPage/ProtectedPage/Step6 behavior these handlers were ported from. + * + *

{@code checkAgentPassword} is not the live handler step6.htm/step6.ftl actually posts to + * (that's the base {@code checkPasswords}, exercised via {@code Step1Test}/{@code + * ConfiguratorServletTest} - same base method, same behavior) - see + * docs/migration/click-to-freemarker/04-implementation-notes.md. It is still covered here since + * it stays a live, annotated, web-invokable endpoint for URL-preservation parity with the old + * Click page. + */ +public class Step6Test { + + private HttpServletRequest request; + private HttpServletResponse response; + private StringWriter responseBody; + private Map sessionAttributes; + private Step6 step6; + + @BeforeMethod + public void setup() throws Exception { + sessionAttributes = new HashMap<>(); + + HttpSession session = mock(HttpSession.class); + doAnswer(inv -> sessionAttributes.put(inv.getArgument(0), inv.getArgument(1))) + .when(session).setAttribute(anyString(), any()); + when(session.getAttribute(anyString())).thenAnswer(inv -> sessionAttributes.get(inv.getArgument(0))); + + request = mock(HttpServletRequest.class); + when(request.getSession()).thenReturn(session); + when(request.getSession(false)).thenReturn(session); + when(request.getParameter("locale")).thenReturn("en"); + + responseBody = new StringWriter(); + response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(new PrintWriter(responseBody)); + + step6 = new Step6(); + step6.setContext(new ConfiguratorContext(request, response)); + } + + private void param(String name, String value) { + when(request.getParameter(name)).thenReturn(value); + } + + @Test + public void checkAgentPasswordRejectsMissingFields() { + step6.checkAgentPassword(); + + assertThat(responseBody.toString()) + .isEqualTo("{\"valid\":\"false\", \"body\":\"Missing Required Field\"}"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void checkAgentPasswordRejectsSameAsAdminPassword() { + sessionAttributes.put(SessionAttributeNames.CONFIG_VAR_ADMIN_PWD, "sharedpassword1"); + param("agent", "sharedpassword1"); + param("agentConfirm", "sharedpassword1"); + + step6.checkAgentPassword(); + + assertThat(responseBody.toString()) + .isEqualTo("{\"valid\":\"false\", \"body\":\"Agent password is same as Admin Password\"}"); + assertThat(sessionAttributes.get(SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD)).isNull(); + } + + @Test + public void checkAgentPasswordRejectsShortPassword() { + param("agent", "short"); + param("agentConfirm", "short"); + + step6.checkAgentPassword(); + + assertThat(responseBody.toString()) + .isEqualTo("{\"valid\":\"false\", \"body\":\"Password must be at least 8 characters\"}"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void checkAgentPasswordRejectsMismatch() { + param("agent", "longenough1"); + param("agentConfirm", "different1"); + + step6.checkAgentPassword(); + + assertThat(responseBody.toString()) + .isEqualTo("{\"valid\":\"false\", \"body\":\"Passwords do not match\"}"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void checkAgentPasswordAcceptsDistinctPassword() { + sessionAttributes.put(SessionAttributeNames.CONFIG_VAR_ADMIN_PWD, "adminpassword1"); + param("agent", "agentpassword1"); + param("agentConfirm", "agentpassword1"); + + step6.checkAgentPassword(); + + assertThat(responseBody.toString()).isEqualTo("{\"valid\":\"true\", \"body\":\"OK\"}"); + assertThat(sessionAttributes.get(SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD)) + .isEqualTo("agentpassword1"); + } +} diff --git a/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java b/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java index 42c83f681f..e8805cff30 100644 --- a/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java +++ b/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java @@ -35,10 +35,12 @@ /** * Routing behavior of {@link ConfiguratorServlet} against the real migrated-page registry - * (only {@code /config/wizard/step1.htm} -> {@code Step1} in this increment): a registered path - * dispatches {@code ?actionLink=} requests to the matching {@code @ConfiguratorAction} method, - * and an unregistered path forwards, by name, to the still-installed {@code click-servlet} - - * proving the mixed-engine fallback this whole approach depends on. + * (steps 1, 2, 4, 5, 6 as of this increment): a registered path dispatches {@code ?actionLink=} + * requests to the matching {@code @ConfiguratorAction} method, and an unregistered path forwards, + * by name, to the still-installed {@code click-servlet} - proving the mixed-engine fallback this + * whole approach depends on. {@code step3.htm} (not yet migrated - see + * docs/migration/click-to-freemarker/03-migration-plan.md, increment 3) stands in for "still on + * Click" below; update this to whichever page is next in line as increments land. */ public class ConfiguratorServletTest { @@ -93,7 +95,7 @@ public void unknownActionLinkOnRegisteredPageIsRejected() throws Exception { @Test public void unregisteredPathForwardsToClickByName() throws Exception { - when(request.getServletPath()).thenReturn("/config/wizard/step2.htm"); + when(request.getServletPath()).thenReturn("/config/wizard/step3.htm"); RequestDispatcher dispatcher = mock(RequestDispatcher.class); when(servletContext.getNamedDispatcher("click-servlet")).thenReturn(dispatcher); diff --git a/openam-server-only/src/main/webapp/config/wizard/step2.htm b/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step2.ftl similarity index 67% rename from openam-server-only/src/main/webapp/config/wizard/step2.htm rename to openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step2.ftl index 4dace076b6..6ad6783f5f 100644 --- a/openam-server-only/src/main/webapp/config/wizard/step2.htm +++ b/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step2.ftl @@ -5,7 +5,7 @@ var configDirectoryValid = true; var encryptionKeyValid = true; var cookieDomainValid = true; - + function serverFieldsValid() { $('nextTabButton').disabled = !(serverURLValid && @@ -16,7 +16,7 @@ } function validateInput(field, handler) { - var callUrl = "$context$path?actionLink=validateInput"; + var callUrl = "${context}${path}?actionLink=validateInput"; var key = "&key=" + field; var value = "&value=" + $(field).value; ie7fix++; @@ -37,19 +37,19 @@ } function validated(response, field) { - if (response.responseText == "true") { + if (response.responseText == "true") { eval(field + "Valid = true;" ); $(field + 'Status').innerHTML = okString; } else if (response.responseText.search("warning") == 0) { eval(field + "Valid = true;" ); - $(field + 'Status').innerHTML = warningImage + + $(field + 'Status').innerHTML = warningImage + '' + response.responseText.substring(7) + ''; - + } else { eval(field + "Valid = false;" ); $(field + 'Status').innerHTML = errorImage + '' + response.responseText + ''; - } + } serverFieldsValid(); field = ""; } @@ -62,81 +62,81 @@ function validateLocale() { field = "platformLocale"; validateInput(field, null); - } + } function validateConfigDir() { field = "configDirectory"; - var callUrl = "$context$path?actionLink=validateConfigDir&dir=" + + var callUrl = "${context}${path}?actionLink=validateConfigDir&dir=" + encodeURIComponent($(field).value); ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call(callUrl, serverFieldValidated); - } + } function validateCookieDomain() { field = "cookieDomain"; - var callUrl = "$context$path?actionLink=validateCookieDomain&domain=" + + var callUrl = "${context}${path}?actionLink=validateCookieDomain&domain=" + encodeURIComponent($(field).value) + "&serverurl=" + encodeURIComponent($("serverURL").value); ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call(callUrl, serverFieldValidated); - } + }

-

$page.getLocalizedString("step2.title")

-

$page.getLocalizedString("step2.description")

+

${page.getLocalizedString('step2.title')}

+

${page.getLocalizedString('step2.description')}

-

* $page.getLocalizedString("required.field.label")

+

* ${page.getLocalizedString('required.field.label')}

-
$page.getLocalizedString("step2.server.settings")
-
+
${page.getLocalizedString('step2.server.settings')}
+
- + - + - - - + + + - - - - + + + + - +
-
- - - help + + help
- +
- - $initialCheck + + ${initialCheck}
@@ -151,4 +151,3 @@

$page.getLocalizedString("step2.title")' + response.responseText + ''; } } - + function validateADUserFields(response) { - if (response.responseText == "ok") { + if (response.responseText == "ok") { eval(field + "Valid = true;" ); $(field + 'Status').innerHTML = okString; if (field == "ADuserStorePassword") { @@ -67,12 +67,12 @@ eval(field + "Valid = false;" ); $(field + 'Status').innerHTML = errorImage + '' + response.responseText + ''; - } + } field = ""; } function validateUserFields(response) { - if (response.responseText == "ok") { + if (response.responseText == "ok") { eval(field + "Valid = true;" ); $(field + 'Status').innerHTML = okString; allValid(); @@ -81,13 +81,13 @@ $('nextTabButton').disabled = true; $(field + 'Status').innerHTML = errorImage + '' + response.responseText + ''; - } + } field = ""; - } + } function validateUserStoreSSL() { field = "userStoreSSL"; - var callUrl = "$context$path?actionLink=setSSL"; + var callUrl = "${context}${path}?actionLink=setSSL"; var value = ($('userStoreSSL').checked) ? "SSL" : "SIMPLE"; var param = "&ssl=" + value; ie7fix++; @@ -101,7 +101,7 @@ function validateADUserStoreSSL() { field = "ADuserStoreSSL"; - var callUrl = "$context$path?actionLink=setSSL"; + var callUrl = "${context}${path}?actionLink=setSSL"; var value = ($('ADuserStoreSSL').checked) ? "SSL" : "SIMPLE"; var param = "&ssl=" + value; ie7fix++; @@ -115,84 +115,84 @@ function validateUserStoreDomainName() { field = "userStoreDomainName"; - var callUrl = "$context$path?actionLink=setDomainName"; + var callUrl = "${context}${path}?actionLink=setDomainName"; var param = "&domainname=" + $('userStoreDomainName').value; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call(callUrl+param, validateADUserFields); - } + } function validateADUserStoreLoginId() { field = "ADuserStoreLoginId"; - var callUrl = "$context$path?actionLink=setLoginID"; + var callUrl = "${context}${path}?actionLink=setLoginID"; var param = "&dn=" + $('ADuserStoreLoginId').value; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call(callUrl+param, validateADUserFields); - } + } function validateADUserStorePassword() { field = "ADuserStorePassword"; - var callUrl = "$context$path?actionLink=setPassword"; + var callUrl = "${context}${path}?actionLink=setPassword"; var param = "password=" + encodeURIComponent($('ADuserStorePassword').value); ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; - AjaxUtils.doPost(null, callUrl, param, validateADUserFields, + AjaxUtils.doPost(null, callUrl, param, validateADUserFields, null, null); - } + } function validateUserStoreHost() { field = "userStoreHost"; - var callUrl = "$context$path?actionLink=setHost"; + var callUrl = "${context}${path}?actionLink=setHost"; var param = "&host=" + $('userStoreHost').value; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call(callUrl+param, validateUserFields); - } - + } + function validateUserStorePort() { field = "userStorePort"; - var callUrl = "$context$path?actionLink=setPort"; + var callUrl = "${context}${path}?actionLink=setPort"; var param = "&port=" + $('userStorePort').value; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call(callUrl+param, validateUserFields); - } + } function validateUserStoreRootSuffix() { field = "userStoreRootSuffix"; - var callUrl = "$context$path?actionLink=setRootSuffix"; + var callUrl = "${context}${path}?actionLink=setRootSuffix"; var param = "&rootsuffix=" + encodeURIComponent($('userStoreRootSuffix').value); ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call(callUrl+param, validateUserFields); } - + function validateUserStoreLoginId() { field = "userStoreLoginId"; - var callUrl = "$context$path?actionLink=setLoginID"; + var callUrl = "${context}${path}?actionLink=setLoginID"; var param = "&dn=" + $('userStoreLoginId').value; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call(callUrl+param, validateUserFields); - } + } function validateUserStorePassword() { field = "userStorePassword"; - var callUrl = "$context$path?actionLink=setPassword"; + var callUrl = "${context}${path}?actionLink=setPassword"; var param = "password=" + encodeURIComponent($('userStorePassword').value); ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.doPost(null, callUrl, param, validateUserFields, null, null); - } - + } + function setADforDomainName() { setType(document.getElementById("ldapv3adforDomainName").value); document.getElementById("activeDirectoryConfigSettings").style.display = ""; document.getElementById("embeddedConfigSettings").style.display = "none"; document.getElementById("userStoreSettings").style.display = "none"; ie7fix++; - AjaxUtils.call("$context$path?actionLink=resetUMEmbedded&ie7fix=" + ie7fix); + AjaxUtils.call("${context}${path}?actionLink=resetUMEmbedded&ie7fix=" + ie7fix); } function setAD() { @@ -226,23 +226,23 @@ } function setType(type) { - var callUrl = "$context$path?actionLink=setStoreType"; + var callUrl = "${context}${path}?actionLink=setStoreType"; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call(callUrl + "&type=" + type); } - + function enableEmbeddedConfig() { userStoreChoice = "embedded"; document.getElementById("embeddedConfigSettings").style.display = ""; - document.getElementById("externalConfigSettings").style.display = "none"; + document.getElementById("externalConfigSettings").style.display = "none"; document.getElementById("activeDirectoryConfigSettings").style.display = "none"; $('nextTabButton').disabled = false; ie7fix++; - AjaxUtils.call("$context$path?actionLink=setUMEmbedded&ie7fix=" + ie7fix); + AjaxUtils.call("${context}${path}?actionLink=setUMEmbedded&ie7fix=" + ie7fix); } - + function enableExternalConfig() { userStoreChoice = "external"; @@ -253,136 +253,136 @@ document.getElementById("embeddedConfigSettings").style.display = "none"; allValid(); ie7fix++; - AjaxUtils.call("$context$path?actionLink=resetUMEmbedded&ie7fix=" + ie7fix); + AjaxUtils.call("${context}${path}?actionLink=resetUMEmbedded&ie7fix=" + ie7fix); } - + function initUserStorePage() { - if ( "$EXT_DATA_STORE" == "true" ) { + if ( "${EXT_DATA_STORE}" == "true" ) { enableExternalConfig(); } else { enableEmbeddedConfig(); } } - + YAHOO.util.Event.onDOMReady(initUserStorePage);
-

$page.getLocalizedString("step4.title")

-

$page.getLocalizedString("step4.description")

+

${page.getLocalizedString('step4.title')}

+

${page.getLocalizedString('step4.description')}

- +
-

* $page.getLocalizedString("required.field.label")

+

* ${page.getLocalizedString('required.field.label')}

-
$page.getLocalizedString("step4.sub.title")
+
${page.getLocalizedString('step4.sub.title')}
+
diff --git a/openam-server-only/src/main/webapp/config/wizard/step6.htm b/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step6.ftl similarity index 71% rename from openam-server-only/src/main/webapp/config/wizard/step6.htm rename to openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step6.ftl index aaa421328b..3654c07741 100644 --- a/openam-server-only/src/main/webapp/config/wizard/step6.htm +++ b/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step6.ftl @@ -1,5 +1,5 @@
-

$page.getLocalizedString("agent.step.title")

-

$page.getLocalizedString("agent.step.description")

- +

${page.getLocalizedString('agent.step.title')}

+

${page.getLocalizedString('agent.step.description')}

+
-

* $page.getLocalizedString("required.field.label")

+

* ${page.getLocalizedString('required.field.label')}

-
$page.getLocalizedString("agent.step.subtitle")
+
${page.getLocalizedString('agent.step.subtitle')}
-
- $page.getLocalizedString("agent.user.name") + ${page.getLocalizedString('agent.user.name')}
-  * $page.getLocalizedString("password.label") +  * ${page.getLocalizedString('password.label')} - + onchange="APP.callDelayed(this,validateAgentPasswords)">
-  * $page.getLocalizedString("confirm.label") +  * ${page.getLocalizedString('confirm.label')} - + onchange="APP.callDelayed(this,validateAgentPasswords)"/>
+
From ced37ee240d0d2f3cb469115bcfb3a68560759e6 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Tue, 7 Jul 2026 11:25:08 +0300 Subject: [PATCH 05/27] Increment 3 migration --- .../04-implementation-notes.md | 92 +++++- .../config/wizard/LDAPStoreWizardPage.java | 27 +- .../com/sun/identity/config/wizard/Step3.java | 164 +++++----- .../config/servlet/ConfiguratorServlet.java | 2 + .../openam/config/servlet/SetupPage.java | 5 + .../sun/identity/config/wizard/Step3Test.java | 303 ++++++++++++++++++ .../servlet/ConfiguratorServletTest.java | 8 +- .../templates/config/wizard/step3.ftl} | 303 +++++++++--------- 8 files changed, 651 insertions(+), 253 deletions(-) create mode 100644 openam-core/src/test/java/com/sun/identity/config/wizard/Step3Test.java rename openam-server-only/src/main/webapp/{config/wizard/step3.htm => WEB-INF/templates/config/wizard/step3.ftl} (76%) diff --git a/docs/migration/click-to-freemarker/04-implementation-notes.md b/docs/migration/click-to-freemarker/04-implementation-notes.md index 08cbbe6f25..5aaa4ff7be 100644 --- a/docs/migration/click-to-freemarker/04-implementation-notes.md +++ b/docs/migration/click-to-freemarker/04-implementation-notes.md @@ -1,7 +1,8 @@ -# Implementation notes: increments 0, 1 & 2 +# Implementation notes: increments 0-3 -Findings from actually building the pilot (0, 1) and the Steps 2/4/5/6 batch (2), worth knowing -before increments 3+. Background in `02-recommendation.md` / `03-migration-plan.md`. +Findings from actually building the pilot (0, 1), the Steps 2/4/5/6 batch (2), and Step3 + +LDAPStoreWizardPage (3), worth knowing before increments 4+. Background in +`02-recommendation.md` / `03-migration-plan.md`. ## FreeMarker's `WebappTemplateLoader` cannot be used (javax vs jakarta) @@ -235,3 +236,88 @@ calling this method from an `openam-core` test throws `ExceptionInInitializerErr `validateCookieDomain()`; the manual smoke step (deploy the unconfigured WAR, exercise Step 2 in the browser) is the only current coverage for that handler, same category as the still-open "real FreeMarker render of `step1.ftl` isn't unit-tested" gap above. + +## Increment 3: Step3 + LDAPStoreWizardPage + +Step3 is the heaviest page so far (many public-field/session pre-fills, the `LDAPStore` object, +`clearStore`, the bespoke `validateHostName` JSON shape). It's also the first page with a +*migrated intermediate base class*: `LDAPStoreWizardPage` sat between `Step3` and the old +`ProtectedPage`, so it had to be ported too, following the same recipe as `SetupPage` itself +(`onSecurityCheck()` moved onto `LDAPStoreWizardPage`, not duplicated onto `Step3` — Step3 never +overrode it in the old code either, it just inherited the check). + +### `setConfigType()`/`setReplication()`: old Click `ActionLink`s that returned `true` with no render-skip + +Unlike every handler ported in increments 1-2 (which all either call `writeToResponse(...)` — which +now bakes in `skipRender()` — or explicitly call `setPath(null)`/`skipRender()`), Step3's +`setConfigType()` and `setReplication()` write **nothing** to the response and return `true` with +no `setPath(null)` at all. In the old Click framework an `ActionLink` handler returning `true` (and +never calling `setPath(null)`) tells Click to *continue* normal request processing — i.e. run +`onGet()` and render the full page template into the same AJAX response. +`ConfiguratorServlet.invokeAction()` has no equivalent: it invokes the `@ConfiguratorAction` method +and returns unconditionally, **ignoring the method's boolean return value entirely**. So in the new +code these two handlers now produce an empty response instead of a full-page render. + +Checked whether this is an observable behavior change: both callers +(`enableRemote`/`disableRemote`/`enableExisting`/`disableExisting` in `step3.ftl`) invoke them via +`AjaxUtils.call(url)` with **no callback**, i.e. fire-and-forget — nothing on the client ever reads +the response body either way. And `Step3.onInit()` has no session side effects (only session reads ++ `addModel` calls), so re-running it (as the old full-page render would have done) wouldn't have +produced any additional session state. Net effect: the missing render is bytes nobody reads, not a +behavior difference. Ported as plain `skipRender()` + `@ConfiguratorAction`, with a comment at each +call site pointing back here — **if a future page has this same "return true, no write" shape but +its `onInit()` *does* have session side effects, that page cannot be ported this simply** and +`ConfiguratorServlet` would need to grow support for continuing to render on a truthy return. + +### `getAvailablePort(int)` added to `SetupPage` + +Called out in earlier increments as "add only when the page that needs it migrates" (mirrors the +`getBaseDir`/`getCookieDomain`/`getHostName`/`getAttribute` additions in increment 2). Step3 is the +first migrated page that needs it (`configStorePort`/`configStoreAdminPort`/`configStoreJmxPort`/ +`localRepPort`/`existingPort` defaults, plus the `validateSMHost` port fallback). Copied 1:1 from +`AjaxPage.getAvailablePort(int)`: `Integer.toString(AMSetupUtils.getFirstUnusedPort(getHostName(), +portNumber, 1000))`. + +### Two more pre-existing Velocity-silently-blank template variables, this time in step3.htm + +Same `$var`-vs-`addModel` diff recommended by the plan and already used in increment 2's Step4 +port. `step3.htm` references `$existingStoreHost` and `$existingStorePort` in the "existing LDAP" +section's initial `value="..."` attributes, but **neither key is ever `addModel`-ed by +`Step3.onInit()` or `LDAPStoreWizardPage.onInit()`**. They're populated only client-side, by JS +reading the `validateHostName` JSON response (`resp.existingStoreHost`/`resp.existingStorePort`) +and setting the `` `.value` directly — the server-rendered initial value was always silently +blank under Velocity. Ported as `${existingStoreHost!""}` / `${existingStorePort!""}`. (By contrast, +`configStorePassword` *is* `addModel`-ed but never referenced by the template at all — the +template only reads `$store.password` for that field. Harmless, unused-model-entry case, left +as-is, not a gap that needs a `!""` default.) + +### The `LDAPStore`/`store`/`clearStore`/`LDAP_STORE_SESSION_KEY` machinery looks mostly vestigial + +`LDAPStoreWizardPage.save(LDAPStore)` is never called anywhere in the repo (confirmed by grep before +porting) — the `store` field is populated once per request (`getConfig()`/`ensureConfig()`) and +handed to the template for the one `$store.password` prefill, but nothing ever writes a populated +`LDAPStore` back into the `customConfigStore` session key, so that prefill is always blank in +practice. `Step3.LDAP_STORE_SESSION_KEY = "wizardCustomConfigStore"` is also dead — the session key +actually used is `LDAPStoreWizardPage`'s own default, `"customConfigStore"` (a different string); +the constant is never read anywhere. All of this is pre-existing (predates this migration) and +ported byte-for-byte, including the naming mismatch — not a bug introduced here, and not this +increment's job to clean up. Worth knowing if a future increment is tempted to "simplify" this +class: the apparent dead weight is original behavior, not migration residue. + +### Increment 3 test coverage gaps (same category as prior increments) + +- `validateHostName()`/`validateSMHost()`: require a live remote-server HTTP round trip and a live + LDAP connection respectively — same disproportionate-effort call as Step4Test's + `validateUMHost`/`validateUMDomainName` skip. +- `validateLocalPort()`/`validateLocalAdminPort()`/`validateLocalJmxPort()`: the "port not in use" + happy path calls `AMSetupUtils.isPortInUse()`, which binds a real `ServerSocket` — environment- + dependent. Only the parameter-validation branches and the external-data-store bypass (which + short-circuits before ever calling `isPortInUse()`) are unit-tested; the local/embedded + port-in-use branch is manual-smoke-only. +- `validateConfigStoreHost()`'s `UnknownHostException` branch isn't unit-tested — DNS behavior for a + deliberately-invalid hostname isn't reliably reproducible across environments/sandboxes. The + resolvable-host branch is covered using `localhost` (resolves without real network). + +`ConfiguratorServletTest`'s "unregistered path forwards to Click" example was moved from +`step3.htm` (now migrated) to `step7.htm` (still Click, next up in increment 4) — update this again +whenever step7 lands. diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/LDAPStoreWizardPage.java b/openam-core/src/main/java/com/sun/identity/config/wizard/LDAPStoreWizardPage.java index 5536149cb5..a835c6b096 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/LDAPStoreWizardPage.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/LDAPStoreWizardPage.java @@ -25,23 +25,23 @@ * $Id: LDAPStoreWizardPage.java,v 1.7 2008/06/25 05:42:42 qcheng Exp $ * * Portions Copyrighted 2011-2014 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; import com.sun.identity.config.pojos.LDAPStore; -import com.sun.identity.config.util.ProtectedPage; -import org.openidentityplatform.openam.click.control.ActionLink; +import com.sun.identity.setup.AMSetupServlet; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.SetupPage; /* * LDAPStoreWizardPage is the base for Step 3. */ -public class LDAPStoreWizardPage extends ProtectedPage { +public class LDAPStoreWizardPage extends SetupPage { public LDAPStore store = null; - public ActionLink clearLink = - new ActionLink("clearStore", this, "clearStore"); private String type = "config"; private String typeTitle = "Configuration"; private String storeSessionName = "customConfigStore"; @@ -50,6 +50,17 @@ public class LDAPStoreWizardPage extends ProtectedPage { public LDAPStoreWizardPage() { } + @Override + public boolean onSecurityCheck() { + // Ported from the old com.sun.identity.config.util.ProtectedPage: block re-entry once + // OpenAM has already been configured. + if (AMSetupServlet.isConfigured()) { + skipRender(); + return false; + } + return true; + } + public String getType() { return type; } @@ -82,6 +93,7 @@ public void setStoreSessionName( String storeSessionName ) { this.storeSessionName = storeSessionName; } + @Override public void onInit() { addModel("type", getType()); addModel("typeTitle", getTypeTitle()); @@ -91,12 +103,13 @@ public void onInit() { store = ensureConfig(); addModel("store", store); - super.onInit(); + super.onInit(); } + @ConfiguratorAction public boolean clearStore() { getContext().removeSessionAttribute( getStoreSessionName()); - setPath(null); + skipRender(); return false; } diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step3.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step3.java index e2efd62131..ad14cc8c8a 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step3.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step3.java @@ -42,8 +42,8 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Map; -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; import org.forgerock.openam.ldap.LDAPUtils; import org.forgerock.opendj.ldap.Connection; import org.forgerock.opendj.ldap.DN; @@ -52,48 +52,28 @@ import org.forgerock.opendj.ldap.ResultCode; /** - * Step 3 is for selecting the embedded or external configuration store + * Step 3 is for selecting the embedded or external configuration store */ public class Step3 extends LDAPStoreWizardPage { public static final String LDAP_STORE_SESSION_KEY = "wizardCustomConfigStore"; - public ActionLink validateSMHostLink = - new ActionLink("validateSMHost", this, "validateSMHost"); - public ActionLink validateRootSuffixLink = - new ActionLink("validateRootSuffix", this, "validateRootSuffix"); - public ActionLink setReplicationLink = - new ActionLink("setReplication", this, "setReplication"); - public ActionLink validateHostNameLink = - new ActionLink("validateHostName", this, "validateHostName"); - public ActionLink validateConfigStoreHost = - new ActionLink("validateConfigStoreHost", this, "validateConfigStoreHost"); - public ActionLink setConfigType = - new ActionLink("setConfigType", this, "setConfigType"); - public ActionLink validateLocalPortLink = - new ActionLink("validateLocalPort", this, "validateLocalPort"); - public ActionLink validateLocalAdminPortLink = - new ActionLink("validateLocalAdminPort", this, "validateLocalAdminPort"); - public ActionLink validateLocalJmxPortLink = - new ActionLink("validateLocalJmxPort", this, "validateLocalJmxPort"); - public ActionLink validateEncKey = - new ActionLink("validateEncKey", this, "validateEncKey"); - private static final String QUOTE = "\""; private static final String SEPARATOR = "\" : \""; private String localRepPort; - + public Step3() { } - + + @Override public void onInit() { String val = getAttribute("rootSuffix", Wizard.defaultRootSuffix); addModel("rootSuffix", val); val = getAttribute("encryptionKey", AMSetupUtils.getRandomString()); addModel("encryptionKey", val); - + val = getAttribute("configStorePort", getAvailablePort(50389)); addModel("configStorePort", val); addModel("localConfigPort", val); @@ -109,7 +89,7 @@ public void onInit() { localRepPort = getAttribute("localRepPort", getAvailablePort(58989)); addModel("localRepPort", localRepPort); - val = getAttribute("existingPort", getAvailablePort(50389)); + val = getAttribute("existingPort", getAvailablePort(50389)); addModel("existingPort", val); val = getAttribute("existingRepPort", getAvailablePort(58990)); @@ -117,7 +97,7 @@ public void onInit() { val = getAttribute("configStoreSSL", "SIMPLE"); addModel("configStoreSSL", val); - + if (val.equals("SSL")) { addModel("selectConfigStoreSSL", "checked=\"checked\""); } else { @@ -126,10 +106,10 @@ public void onInit() { // initialize the data store type being used val = getAttribute( - SetupConstants.CONFIG_VAR_DATA_STORE, + SetupConstants.CONFIG_VAR_DATA_STORE, SetupConstants.SMS_EMBED_DATASTORE); addModel(SetupConstants.CONFIG_VAR_DATA_STORE, val); - + if (val.equals(SetupConstants.SMS_EMBED_DATASTORE)) { addModel("selectEmbedded", "checked=\"checked\""); addModel("selectExternal", ""); @@ -159,8 +139,9 @@ public void onInit() { } super.onInit(); - } + } + @ConfiguratorAction public boolean setConfigType() { String type = toString("type"); if (type.equals("remote")) { @@ -170,21 +151,34 @@ public boolean setConfigType() { getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_HOST, "localhost"); } - getContext().setSessionAttribute( + getContext().setSessionAttribute( SessionAttributeNames.CONFIG_VAR_DATA_STORE, type); + // NOTE: the old Click ActionLink returned `true` here with no setPath(null), which in the + // Click framework would go on to run onGet() + render the full step3 page into this AJAX + // response body. ConfiguratorServlet's actionLink dispatch never renders after invoking a + // handler (see ConfiguratorServlet.invokeAction), regardless of the method's return value. + // The callers (enableRemote/disableRemote in step3.ftl) are fire-and-forget - no callback + // reads the response - and Step3.onInit() has no session side effects beyond what is set + // above, so skipping that render is behaviorally invisible to the wizard. See + // docs/migration/click-to-freemarker/04-implementation-notes.md. + skipRender(); return true; } + @ConfiguratorAction public boolean setReplication() { String type = toString("multi"); if (type.equals("enable")) { type = SetupConstants.DS_EMP_REPL_FLAG_VAL; - } + } getContext().setSessionAttribute( SessionAttributeNames.DS_EMB_REPL_FLAG, type); + // See the NOTE in setConfigType() above - same fire-and-forget, no-render situation. + skipRender(); return true; } + @ConfiguratorAction public boolean validateRootSuffix() { String rootsuffix = toString("rootSuffix"); if ((rootsuffix == null) || (rootsuffix.trim().length() == 0)) { @@ -197,13 +191,13 @@ public boolean validateRootSuffix() { getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_ROOT_SUFFIX, rootsuffix); } - setPath(null); - return false; + return false; } - + + @ConfiguratorAction public boolean validateLocalPort() { String port = toString("port"); - + if (port == null) { writeToResponse(getLocalizedString("missing.required.field")); } else { @@ -243,11 +237,11 @@ public boolean validateLocalPort() { writeToResponse(getLocalizedString("invalid.port.number")); } } - setPath(null); - return false; + return false; } - public boolean validateLocalAdminPort() { + @ConfiguratorAction + public boolean validateLocalAdminPort() { String port = toString("port"); if (port == null) { @@ -289,10 +283,10 @@ public boolean validateLocalAdminPort() { writeToResponse(getLocalizedString("invalid.port.number")); } } - setPath(null); return false; } + @ConfiguratorAction public boolean validateLocalJmxPort() { String port = toString("port"); @@ -335,7 +329,6 @@ public boolean validateLocalJmxPort() { writeToResponse(getLocalizedString("invalid.port.number")); } } - setPath(null); return false; } @@ -343,6 +336,7 @@ public boolean validateLocalJmxPort() { * Returns false always. Length of encryption key * must be at least 10 chars. */ + @ConfiguratorAction public boolean validateEncKey() { String key = toString("encKey"); @@ -357,16 +351,15 @@ public boolean validateEncKey() { writeToResponse("true"); } } - setPath(null); return false; - } + } + @ConfiguratorAction public boolean validateConfigStoreHost() { String host = toString("configStoreHost"); if (host == null) { writeToResponse(getLocalizedString("missing.required.field")); - setPath(null); return false; } else { getContext().setSessionAttribute("configStoreHost", host); @@ -378,7 +371,6 @@ public boolean validateConfigStoreHost() { } catch (UnknownHostException uhe) { writeToResponse(getLocalizedString("contact.host.unknown")); } - setPath(null); return false; } @@ -386,21 +378,22 @@ public boolean validateConfigStoreHost() { * a call is made to the OpenAM url entered in the browser. If * the OpenAM server * exists a Map of data will be returned which contains the - * information about the existing servers data store, including any + * information about the existing servers data store, including any * replication ports if its embedded. * Information to control the UI is returned in a JSON object of the form - * { - * "param1" : "value1", + * { + * "param1" : "value1", * "param2" : "value2" * } * The JS on the browser will interpret the above and make the necessary * changes to prompt the user for any more details required. */ + @ConfiguratorAction public boolean validateHostName() { StringBuffer sb = new StringBuffer(); String hostName = toString("hostName"); - - if (hostName == null) { + + if (hostName == null) { addObject(sb, "code", "100"); addObject(sb, "message", getLocalizedString("missing.required.field")); @@ -409,18 +402,18 @@ public boolean validateHostName() { String admin = "amadmin"; String password = (String)getContext().getSessionAttribute( SessionAttributeNames.CONFIG_VAR_ADMIN_PWD); - - try { + + try { String dsType; Map data = AMSetupUtils.getRemoteServerInfo(hostName, admin, password); - + // data returned from existing OpenAM server - if (data != null && !data.isEmpty()) { + if (data != null && !data.isEmpty()) { addObject(sb, "code", "100"); addObject(sb, "message", getLocalizedString("ok.string")); - + setupDSParams(data); - + String key = (String)data.get("enckey"); getContext().setSessionAttribute( SessionAttributeNames.ENCRYPTION_KEY, key); @@ -428,27 +421,27 @@ public boolean validateHostName() { getContext().setSessionAttribute( SessionAttributeNames.ENCLDAPUSERPASSWD, (String)data.get("ENCLDAPUSERPASSWD")); - + // true for embedded, false for ODSEE - String embedded = + String embedded = (String)data.get(BootstrapData.DS_ISEMBEDDED); - addObject(sb, "embedded", embedded); + addObject(sb, "embedded", embedded); String host = (String)data.get(BootstrapData.DS_HOST); if (embedded.equals("true")) { getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_HOST, getHostName()); addObject(sb, "configStoreHost", getHostName()); - - // set the multi embedded flag + + // set the multi embedded flag getContext().setSessionAttribute( - SessionAttributeNames.CONFIG_VAR_DATA_STORE, - SetupConstants.SMS_EMBED_DATASTORE); - + SessionAttributeNames.CONFIG_VAR_DATA_STORE, + SetupConstants.SMS_EMBED_DATASTORE); + getContext().setSessionAttribute( SessionAttributeNames.DS_EMB_REPL_FLAG, - SetupConstants.DS_EMP_REPL_FLAG_VAL); - + SetupConstants.DS_EMP_REPL_FLAG_VAL); + // get the existing replication ports if any String replAvailable = (String)data.get( BootstrapData.DS_REPLICATIONPORT_AVAILABLE); @@ -475,10 +468,10 @@ public boolean validateHostName() { SessionAttributeNames.CONFIG_STORE_PWD, password); } else { getContext().setSessionAttribute( - SessionAttributeNames.CONFIG_STORE_PORT, + SessionAttributeNames.CONFIG_STORE_PORT, (String)data.get(BootstrapData.DS_PORT)); getContext().setSessionAttribute( - SessionAttributeNames.CONFIG_STORE_HOST, host); + SessionAttributeNames.CONFIG_STORE_HOST, host); addObject(sb, "configStoreHost", host); String dsprot = (String)data.get( @@ -488,7 +481,7 @@ public boolean validateHostName() { getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_SSL, dsSSL); addObject(sb, "configStoreSSL", dsSSL); - + String dspwd = (String)data.get(BootstrapData.DS_PWD); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_PWD, @@ -509,7 +502,7 @@ public boolean validateHostName() { getContext().setSessionAttribute( SessionAttributeNames.EXISTING_STORE_PORT, ds_existingStorePort); addObject(sb, "existingStorePort", ds_existingStorePort); - + getContext().setSessionAttribute( SessionAttributeNames.EXISTING_HOST, host); @@ -542,12 +535,11 @@ public boolean validateHostName() { addObject(sb, "message", message); } } - sb.append(" }"); + sb.append(" }"); writeToResponse(sb.toString()); - setPath(null); return false; } - + private void addObject(StringBuffer sb, String key, String value) { if (sb.length() < 1) { // add first object @@ -559,16 +551,16 @@ private void addObject(StringBuffer sb, String key, String value) { .append(key) .append(SEPARATOR) .append(value) - .append(QUOTE); + .append(QUOTE); } - + /* * the following value have been pulled from an existing OpenAM - * server which was configured to use an external DS. We need to set the DS + * server which was configured to use an external DS. We need to set the DS * values in the request so they can be used to configure the existing * OpenAM server. */ - private void setupDSParams(Map data) { + private void setupDSParams(Map data) { String tmp = (String)data.get(BootstrapData.DS_BASE_DN); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_ROOT_SUFFIX, tmp); @@ -576,22 +568,23 @@ private void setupDSParams(Map data) { tmp = (String)data.get(BootstrapData.DS_MGR); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_LOGIN_ID, tmp); - + tmp = (String)data.get(BootstrapData.DS_PWD); getContext().setSessionAttribute( SessionAttributeNames.CONFIG_STORE_PWD, tmp); - + getContext().setSessionAttribute( - SessionAttributeNames.CONFIG_VAR_DATA_STORE, - SetupConstants.SMS_DS_DATASTORE); + SessionAttributeNames.CONFIG_VAR_DATA_STORE, + SetupConstants.SMS_DS_DATASTORE); } /** * Validate an Existing SM Host for Configuration Backend. * @return */ + @ConfiguratorAction public boolean validateSMHost() { - Context ctx = getContext(); + ConfiguratorContext ctx = getContext(); String strSSL = (String)ctx.getSessionAttribute( SessionAttributeNames.CONFIG_STORE_SSL); boolean ssl = (strSSL != null) && (strSSL.equals("SSL")); @@ -656,7 +649,6 @@ public boolean validateSMHost() { writeToResponse(getLocalizedString("cannot.connect.to.SM.datastore")); } - setPath(null); return false; } -} \ No newline at end of file +} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java index 8affeda808..4b2ac50fba 100644 --- a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java +++ b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java @@ -32,6 +32,7 @@ import com.sun.identity.config.wizard.Step1; import com.sun.identity.config.wizard.Step2; +import com.sun.identity.config.wizard.Step3; import com.sun.identity.config.wizard.Step4; import com.sun.identity.config.wizard.Step5; import com.sun.identity.config.wizard.Step6; @@ -66,6 +67,7 @@ public class ConfiguratorServlet extends HttpServlet { static { PAGES.put("/config/wizard/step1.htm", Step1.class); PAGES.put("/config/wizard/step2.htm", Step2.class); + PAGES.put("/config/wizard/step3.htm", Step3.class); PAGES.put("/config/wizard/step4.htm", Step4.class); PAGES.put("/config/wizard/step5.htm", Step5.class); PAGES.put("/config/wizard/step6.htm", Step6.class); diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java index 5cadc3319f..21c722883f 100644 --- a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java +++ b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java @@ -29,6 +29,7 @@ import com.sun.identity.config.SessionAttributeNames; import com.sun.identity.setup.AMSetupServlet; +import com.sun.identity.setup.AMSetupUtils; import com.sun.identity.setup.SetupConstants; import com.sun.identity.shared.debug.Debug; import com.sun.identity.shared.locale.Locale; @@ -228,6 +229,10 @@ protected String getCookieDomain() { return getHostName(); } + protected String getAvailablePort(int portNumber) { + return Integer.toString(AMSetupUtils.getFirstUnusedPort(getHostName(), portNumber, 1000)); + } + protected String getBaseDir(HttpServletRequest req) { String basedir = AMSetupServlet.getPresetConfigDir(); if ((basedir == null) || (basedir.length() == 0)) { diff --git a/openam-core/src/test/java/com/sun/identity/config/wizard/Step3Test.java b/openam-core/src/test/java/com/sun/identity/config/wizard/Step3Test.java new file mode 100644 index 0000000000..9342ce498b --- /dev/null +++ b/openam-core/src/test/java/com/sun/identity/config/wizard/Step3Test.java @@ -0,0 +1,303 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package com.sun.identity.config.wizard; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import com.sun.identity.config.SessionAttributeNames; +import com.sun.identity.config.pojos.LDAPStore; +import com.sun.identity.setup.SetupConstants; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Byte-exact response and session side-effect coverage for the migrated Step3 page and its base + * {@link LDAPStoreWizardPage}, matching the old Click AjaxPage/ProtectedPage/LDAPStoreWizardPage/ + * Step3 behavior these handlers were ported from. + * + *

{@code validateHostName}/{@code validateSMHost} are not covered here: both require live + * network access (a remote-server HTTP call and an LDAP connection, respectively), the same + * disproportionate-effort call already made for the LDAP-dependent handlers skipped in Step4Test. + * The "resolvable host" branch of {@code validateConfigStoreHost} is exercised with {@code + * localhost} (resolvable without real network); the {@code UnknownHostException} branch is not, + * since DNS behavior for a deliberately-invalid hostname is not reliably reproducible across + * environments. See docs/migration/click-to-freemarker/04-implementation-notes.md. + */ +public class Step3Test { + + private HttpServletRequest request; + private HttpServletResponse response; + private StringWriter responseBody; + private Map sessionAttributes; + private Step3 step3; + + @BeforeMethod + public void setup() throws Exception { + sessionAttributes = new HashMap<>(); + + HttpSession session = mock(HttpSession.class); + doAnswer(inv -> sessionAttributes.put(inv.getArgument(0), inv.getArgument(1))) + .when(session).setAttribute(anyString(), any()); + when(session.getAttribute(anyString())).thenAnswer(inv -> sessionAttributes.get(inv.getArgument(0))); + doAnswer(inv -> sessionAttributes.remove(inv.getArgument(0))) + .when(session).removeAttribute(anyString()); + + request = mock(HttpServletRequest.class); + when(request.getSession()).thenReturn(session); + when(request.getSession(false)).thenReturn(session); + when(request.getParameter("locale")).thenReturn("en"); + + responseBody = new StringWriter(); + response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(new PrintWriter(responseBody)); + + step3 = new Step3(); + step3.setContext(new ConfiguratorContext(request, response)); + } + + private void param(String name, String value) { + when(request.getParameter(name)).thenReturn(value); + } + + // --- LDAPStoreWizardPage.clearStore() ----------------------------------------------------- + + @Test + public void clearStoreRemovesSessionAttributeAndWritesNoResponseBody() { + sessionAttributes.put("customConfigStore", new LDAPStore()); + + step3.clearStore(); + + assertThat(responseBody.toString()).isEmpty(); + assertThat(sessionAttributes).doesNotContainKey("customConfigStore"); + } + + // --- setConfigType() / setReplication() --------------------------------------------------- + // NOTE: the old Click ActionLinks returned `true` here with no setPath(null)/writeToResponse + // call; ConfiguratorServlet's actionLink dispatch never renders regardless of the handler's + // return value, and nothing reads this fire-and-forget call's response body either way - see + // the NOTE in Step3.setConfigType() and 04-implementation-notes.md. Only the session + // side-effects are asserted here. + + @Test + public void setConfigTypeEmbeddedDefaultsHostToLocalhost() { + param("type", "embedded"); + + step3.setConfigType(); + + assertThat(responseBody.toString()).isEmpty(); + assertThat(sessionAttributes.get(SessionAttributeNames.CONFIG_STORE_HOST)).isEqualTo("localhost"); + assertThat(sessionAttributes.get(SessionAttributeNames.CONFIG_VAR_DATA_STORE)) + .isEqualTo(SetupConstants.SMS_EMBED_DATASTORE); + } + + @Test + public void setConfigTypeRemoteLeavesHostUntouched() { + param("type", "remote"); + + step3.setConfigType(); + + assertThat(sessionAttributes).doesNotContainKey(SessionAttributeNames.CONFIG_STORE_HOST); + assertThat(sessionAttributes.get(SessionAttributeNames.CONFIG_VAR_DATA_STORE)) + .isEqualTo(SetupConstants.SMS_DS_DATASTORE); + } + + @Test + public void setReplicationEnableSetsReplicationFlag() { + param("multi", "enable"); + + step3.setReplication(); + + assertThat(responseBody.toString()).isEmpty(); + assertThat(sessionAttributes.get(SetupConstants.DS_EMB_REPL_FLAG)) + .isEqualTo(SetupConstants.DS_EMP_REPL_FLAG_VAL); + } + + @Test + public void setReplicationOtherValuePassesThroughRaw() { + param("multi", "disable"); + + step3.setReplication(); + + assertThat(sessionAttributes.get(SetupConstants.DS_EMB_REPL_FLAG)).isEqualTo("disable"); + } + + // --- validateRootSuffix() ------------------------------------------------------------------ + + @Test + public void validateRootSuffixRejectsMissingValue() { + step3.validateRootSuffix(); + + // Pre-existing quirk ported verbatim: the missing-value branch doesn't return early, so + // execution falls through into the isDN() check too, and both messages get written to the + // response back-to-back. See Step3.validateRootSuffix(). + assertThat(responseBody.toString()).isEqualTo("Missing Required FieldInvalid Distinguished Name"); + assertThat(sessionAttributes).doesNotContainKey(SessionAttributeNames.CONFIG_STORE_ROOT_SUFFIX); + } + + @Test + public void validateRootSuffixRejectsInvalidDN() { + param("rootSuffix", "not a dn"); + + step3.validateRootSuffix(); + + assertThat(responseBody.toString()).isEqualTo("Invalid Distinguished Name"); + assertThat(sessionAttributes).doesNotContainKey(SessionAttributeNames.CONFIG_STORE_ROOT_SUFFIX); + } + + @Test + public void validateRootSuffixAcceptsValidDN() { + param("rootSuffix", "dc=example,dc=com"); + + step3.validateRootSuffix(); + + assertThat(responseBody.toString()).isEqualTo("true"); + assertThat(sessionAttributes.get(SessionAttributeNames.CONFIG_STORE_ROOT_SUFFIX)).isEqualTo("dc=example,dc=com"); + } + + // --- validateLocalPort() / validateLocalAdminPort() / validateLocalJmxPort() -------------- + // The "port not in use" happy path calls AMSetupUtils.isPortInUse(), which binds a real socket + // - environment-dependent, so only the parameter-validation branches and the external-store + // bypass (which never calls isPortInUse()) are covered here. + + @Test + public void validateLocalPortRejectsMissingValue() { + step3.validateLocalPort(); + + assertThat(responseBody.toString()).isEqualTo("Missing Required Field"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateLocalPortRejectsNonNumericValue() { + param("port", "notanumber"); + + step3.validateLocalPort(); + + assertThat(responseBody.toString()) + .isEqualTo("Port must be greater than or equal to 1 and less than or equal to 65535"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateLocalPortRejectsOutOfRangeValue() { + param("port", "70000"); + + step3.validateLocalPort(); + + assertThat(responseBody.toString()) + .isEqualTo("Port must be greater than or equal to 1 and less than or equal to 65535"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateLocalPortAcceptsValueForExternalDataStore() { + sessionAttributes.put(SetupConstants.CONFIG_VAR_DATA_STORE, SetupConstants.SMS_DS_DATASTORE); + param("port", "1389"); + + step3.validateLocalPort(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get("configStorePort")).isEqualTo("1389"); + } + + @Test + public void validateLocalAdminPortAcceptsValueForExternalDataStore() { + sessionAttributes.put(SetupConstants.CONFIG_VAR_DATA_STORE, SetupConstants.SMS_DS_DATASTORE); + param("port", "4444"); + + step3.validateLocalAdminPort(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get("configStoreAdminPort")).isEqualTo("4444"); + } + + @Test + public void validateLocalJmxPortAcceptsValueForExternalDataStore() { + sessionAttributes.put(SetupConstants.CONFIG_VAR_DATA_STORE, SetupConstants.SMS_DS_DATASTORE); + param("port", "1689"); + + step3.validateLocalJmxPort(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get("configStoreJmxPort")).isEqualTo("1689"); + } + + // --- validateEncKey() ---------------------------------------------------------------------- + + @Test + public void validateEncKeyRejectsMissingValue() { + step3.validateEncKey(); + + assertThat(responseBody.toString()).isEqualTo("Missing Required Field"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateEncKeyRejectsShortKey() { + param("encKey", "short"); + + step3.validateEncKey(); + + assertThat(responseBody.toString()) + .isEqualTo("System can be insecured as key is less than 10 characters long."); + assertThat(sessionAttributes.get(SessionAttributeNames.ENCRYPTION_KEY)).isEqualTo("short"); + } + + @Test + public void validateEncKeyAcceptsLongEnoughKey() { + param("encKey", "longenoughkey123"); + + step3.validateEncKey(); + + assertThat(responseBody.toString()).isEqualTo("true"); + assertThat(sessionAttributes.get(SessionAttributeNames.ENCRYPTION_KEY)).isEqualTo("longenoughkey123"); + } + + // --- validateConfigStoreHost() ------------------------------------------------------------- + + @Test + public void validateConfigStoreHostRejectsMissingValue() { + step3.validateConfigStoreHost(); + + assertThat(responseBody.toString()).isEqualTo("Missing Required Field"); + assertThat(sessionAttributes).isEmpty(); + } + + @Test + public void validateConfigStoreHostAcceptsResolvableHost() { + param("configStoreHost", "localhost"); + + step3.validateConfigStoreHost(); + + assertThat(responseBody.toString()).isEqualTo("ok"); + assertThat(sessionAttributes.get("configStoreHost")).isEqualTo("localhost"); + } +} diff --git a/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java b/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java index e8805cff30..6b5be26846 100644 --- a/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java +++ b/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java @@ -35,11 +35,11 @@ /** * Routing behavior of {@link ConfiguratorServlet} against the real migrated-page registry - * (steps 1, 2, 4, 5, 6 as of this increment): a registered path dispatches {@code ?actionLink=} + * (steps 1, 2, 3, 4, 5, 6 as of this increment): a registered path dispatches {@code ?actionLink=} * requests to the matching {@code @ConfiguratorAction} method, and an unregistered path forwards, * by name, to the still-installed {@code click-servlet} - proving the mixed-engine fallback this - * whole approach depends on. {@code step3.htm} (not yet migrated - see - * docs/migration/click-to-freemarker/03-migration-plan.md, increment 3) stands in for "still on + * whole approach depends on. {@code step7.htm} (not yet migrated - see + * docs/migration/click-to-freemarker/03-migration-plan.md, increment 4) stands in for "still on * Click" below; update this to whichever page is next in line as increments land. */ public class ConfiguratorServletTest { @@ -95,7 +95,7 @@ public void unknownActionLinkOnRegisteredPageIsRejected() throws Exception { @Test public void unregisteredPathForwardsToClickByName() throws Exception { - when(request.getServletPath()).thenReturn("/config/wizard/step3.htm"); + when(request.getServletPath()).thenReturn("/config/wizard/step7.htm"); RequestDispatcher dispatcher = mock(RequestDispatcher.class); when(servletContext.getNamedDispatcher("click-servlet")).thenReturn(dispatcher); diff --git a/openam-server-only/src/main/webapp/config/wizard/step3.htm b/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step3.ftl similarity index 76% rename from openam-server-only/src/main/webapp/config/wizard/step3.htm rename to openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step3.ftl index 0d07ec6899..6d868d2ded 100644 --- a/openam-server-only/src/main/webapp/config/wizard/step3.htm +++ b/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step3.ftl @@ -2,19 +2,19 @@ var multiServer = "false"; var validServerURL = "true"; - + function configStoreServerValidated( response ) { - storeServerValidated( '$type', response ); + storeServerValidated( '${type}', response ); } function configStoreBaseDNValidated( response ) { - storeBaseDNValidated('$type', response ); + storeBaseDNValidated('${type}', response ); } function configStoreLoginIdValidated( response ) { - storeLoginIdValidated( '$type', response ); + storeLoginIdValidated( '${type}', response ); } function validateUserFields(response) { - if (response.responseText == "ok") { + if (response.responseText == "ok") { eval(field + "Valid = true;" ); $(field + 'Status').innerHTML = okString; allValid(); @@ -22,13 +22,13 @@ eval(field + "Valid = false;" ); $(field + 'Status').innerHTML = errorImage + '' + response.responseText + ''; - } + } field = ""; } function validateAdminPort() { field = "configStoreAdminPort"; - var callUrl = "$context$path?actionLink=validateLocalAdminPort"; + var callUrl = "${context}${path}?actionLink=validateLocalAdminPort"; var param = "&port=" + $('configStoreAdminPort').value; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; @@ -38,7 +38,7 @@ function validateJmxPort() { field = "configStoreJmxPort"; - var callUrl = "$context$path?actionLink=validateLocalJmxPort"; + var callUrl = "${context}${path}?actionLink=validateLocalJmxPort"; var param = "&port=" + $('configStoreJmxPort').value; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; @@ -50,7 +50,7 @@ $('nextTabButton').disabled = true; var value = encodeURIComponent($('encryptionKey').value); var callUrl = - "$context$path?actionLink=validateEncKey&encKey=" + value; + "${context}${path}?actionLink=validateEncKey&encKey=" + value; setTimeout("enableNextButton()", 500); AjaxUtils.call(callUrl, validateEncKeyResponse); } @@ -67,37 +67,37 @@ function validateConfigStoreSSL() { var value = ($('configStoreSSL').checked) ? "SSL" : "SIMPLE"; var callUrl = - "$context$path?actionLink=validateInput&key=configStoreSSL&value=" + + "${context}${path}?actionLink=validateInput&key=configStoreSSL&value=" + value; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call(callUrl, validateConfigSSL); - } + } function validateConfigSSL(response) { // no ops } function validateConfigStoreHost() { - var call = "$context$path?actionLink=validateConfigStoreHost"; - var hostname = "&configStoreHost=" + $('configStoreHost').value; + var call = "${context}${path}?actionLink=validateConfigStoreHost"; + var hostname = "&configStoreHost=" + $('configStoreHost').value; $('nextTabButton').disabled = true; field = "configStoreHost"; ie7fix++; call = call + "&ie7fix=" + ie7fix; - AjaxUtils.call(call+hostname, validateSMHost); + AjaxUtils.call(call+hostname, validateSMHost); } function validateConfigStorePort() { field = "configStorePort"; - var callUrl = "$context$path?actionLink=validateLocalPort"; + var callUrl = "${context}${path}?actionLink=validateLocalPort"; var param = "&port=" + $('configStorePort').value; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; setTimeout("enableNextButton()", 500); AjaxUtils.call(callUrl+param, validateUserFields); } - + function validateConfigStoreLoginId() { $('nextTabButton').disabled = true; field = "configStoreLoginId"; @@ -116,33 +116,33 @@ $('nextTabButton').disabled = true; ie7fix++; field = "rootSuffix"; - var callUrl = "$context$path?actionLink=validateRootSuffix&ie7fix=" + + var callUrl = "${context}${path}?actionLink=validateRootSuffix&ie7fix=" + ie7fix + "&rootSuffix=" + encodeURIComponent($(field).value); setTimeout("enableNextButton()", 500); AjaxUtils.call(callUrl, fieldValidated); } - function validateServerURL(response) { + function validateServerURL(response) { var resp = eval('('+response.responseText+')'); - var image = okImage; + var image = okImage; if (resp.code == "100") { // url was a valid OpenAM server - validServerURL = true; + validServerURL = true; document.getElementById("existingPort").disabled = true; document.getElementById("existingPort").value = resp.existingPort; if (resp.embedded == "true") { - document.getElementById("replicationPorts").style.display = ""; + document.getElementById("replicationPorts").style.display = ""; var message = ""; if (resp.replication == "true") { document.getElementById("existingRepPort").disabled = true; - message = '$page.getQuoteEscapedLocalizedString("existing.port.values.replication")'; + message = '${page.getQuoteEscapedLocalizedString('existing.port.values.replication')}'; } else { - document.getElementById("existingRepPort").disabled = false; - message='$page.getQuoteEscapedLocalizedString("existing.port.values.noreplication")'; - } + document.getElementById("existingRepPort").disabled = false; + message='${page.getQuoteEscapedLocalizedString('existing.port.values.noreplication')}'; + } document.getElementById("replicationMessage").innerHTML= message; document.getElementById("existingRepPort").value = resp.replicationPort; } else { @@ -159,8 +159,8 @@ } else{ // error handling - validServerURL = false; - image = errorImage; + validServerURL = false; + image = errorImage; document.getElementById("replicationPorts").style.display = "none"; document.getElementById("existingLDAP").style.display = "none"; @@ -169,10 +169,10 @@ $('existingHostStatus').innerHTML = image + '' + resp.message + ''; } - + function validateHostName() { - $('existingHostStatus').innerHTML = '$page.getQuoteEscapedLocalizedString("validating.url.string")'; - var call = "$context$path?actionLink=validateHostName"; + $('existingHostStatus').innerHTML = '${page.getQuoteEscapedLocalizedString('validating.url.string')}'; + var call = "${context}${path}?actionLink=validateHostName"; var hostname = "&hostName=" + $('existingHost').value; $('nextTabButton').disabled = true; ie7fix++; @@ -196,7 +196,7 @@ function validateLocalConfigPort() { field = "localConfigPort"; - var call = "$context$path?actionLink=validateLocalPort"; + var call = "${context}${path}?actionLink=validateLocalPort"; var portVal = "&port=" + $('localConfigPort').value; ie7fix++; call = call + "&ie7fix=" + ie7fix; @@ -205,7 +205,7 @@ function validateLocalConfigAdminPort() { field = "localConfigAdminPort"; - var call = "$context$path?actionLink=validateLocalAdminPort"; + var call = "${context}${path}?actionLink=validateLocalAdminPort"; var portVal = "&port=" + $('localConfigAdminPort').value; ie7fix++; call = call + "&ie7fix=" + ie7fix; @@ -214,13 +214,13 @@ function validateLocalConfigJmxPort() { field = "localConfigJmxPort"; - var call = "$context$path?actionLink=validateLocalJmxPort"; + var call = "${context}${path}?actionLink=validateLocalJmxPort"; var portVal = "&port=" + $('localConfigJmxPort').value; ie7fix++; call = call + "&ie7fix=" + ie7fix; AjaxUtils.call(call+portVal, localPortResponse); } - + function validateLocalRepPort() { field = "localRepPort"; validate(); @@ -244,14 +244,14 @@ $('tab4').style.color = "#D3D3D3"; $('tab6').style.color = "#D3D3D3"; nextTab = 5; - + document.getElementById("newInstanceOptions").style.display = "none"; document.getElementById("existingInstanceURL").style.display = ""; document.getElementById("replicationPorts").style.display = "none"; document.getElementById("existingLDAP").style.display = "none"; ie7fix++; - AjaxUtils.call("$context$path?actionLink=setReplication&multi=enable&ie7fix=" + ie7fix); + AjaxUtils.call("${context}${path}?actionLink=setReplication&multi=enable&ie7fix=" + ie7fix); } function disableExisting() { @@ -262,20 +262,20 @@ $('tab6').style.color = ""; $('tab4').style.color = ""; nextTab = 4; - + document.getElementById("newInstanceOptions").style.display = ""; document.getElementById("existingInstanceURL").style.display = "none"; document.getElementById("replicationPorts").style.display = "none"; document.getElementById("existingLDAP").style.display = "none"; ie7fix++; - AjaxUtils.call("$context$path?actionLink=setReplication&multi=disable&ie7fix=" + ie7fix); + AjaxUtils.call("${context}${path}?actionLink=setReplication&multi=disable&ie7fix=" + ie7fix); } function setExternalFieldsDisplay(display) { document.getElementById("login").style.display = display; document.getElementById("password").style.display = display; - + if (display == "none") { document.getElementById("configStoreSSL").disabled = true; document.getElementById("configStoreHost").disabled = true; @@ -306,7 +306,7 @@ userStore = "external"; enableNextButton(); ie7fix++; - AjaxUtils.call("$context$path?actionLink=setConfigType&type=remote&ie7fix=" + ie7fix); + AjaxUtils.call("${context}${path}?actionLink=setConfigType&type=remote&ie7fix=" + ie7fix); setExternalFieldsDisplay(""); } @@ -315,24 +315,24 @@ userStore = "embedded"; enableNextButton(); ie7fix++; - AjaxUtils.call("$context$path?actionLink=setConfigType&type=embedded&ie7fix=" + ie7fix); + AjaxUtils.call("${context}${path}?actionLink=setConfigType&type=embedded&ie7fix=" + ie7fix); } function initConfig() { - if ("$DATA_STORE" == 'embedded') { + if ("${DATA_STORE}" == 'embedded') { disableRemote(); } else { enableRemote(); } - if ("$FIRST_INSTANCE" == "1") { + if ("${FIRST_INSTANCE}" == "1") { enableExisting(); } } function enableNextButton() { if (userStore == "embedded") { - $('nextTabButton').disabled = + $('nextTabButton').disabled = !isFieldOK('configStoreHost') || !isFieldOK('configStorePort') || !isFieldOK('configStoreAdminPort') || @@ -348,7 +348,7 @@ (document.getElementById('configStorePassword').value != ''); if (allFieldsValid) { ie7fix++; - AjaxUtils.call("$context$path?actionLink=validateSMHost&ie7fix=" + ie7fix, + AjaxUtils.call("${context}${path}?actionLink=validateSMHost&ie7fix=" + ie7fix, validateSMHost); } else { $('nextTabButton').disabled = true; @@ -362,7 +362,7 @@ } function validateSMHost(response) { - if (response.responseText == "ok") { + if (response.responseText == "ok") { $('nextTabButton').disabled = false; $('configStoreHostStatusEx').innerHTML = ""; } else { @@ -374,7 +374,7 @@ } function isFieldOK(field) { - var x = $(field + 'Status').innerHTML; + var x = $(field + 'Status').innerHTML; return (x == '') || (x.indexOf('ok.jpg') != -1); } @@ -382,18 +382,18 @@

-

$page.getLocalizedString("step3.title")

-

$page.getLocalizedString("step3.description")

- - $page.getLocalizedString("create.new.instance") - - $page.getLocalizedString("add.existing.instance") - +

${page.getLocalizedString('step3.title')}

+

${page.getLocalizedString('step3.description')}

+ + ${page.getLocalizedString('create.new.instance')} + + ${page.getLocalizedString('add.existing.instance')} +

*  - $page.getLocalizedString("required.field.label")

+ ${page.getLocalizedString('required.field.label')}

@@ -401,117 +401,117 @@

$page.getLocalizedString("step3.title")
- $page.getLocalizedString("step3.sub.title") + ${page.getLocalizedString('step3.sub.title')}
- + -
+
- - $page.getLocalizedString("step3.embedded.option") - + ${page.getLocalizedString('step3.embedded.option')} + - $page.getLocalizedString("step3.external.option") + ${page.getLocalizedString('step3.external.option')}
- + + ${page.getLocalizedString('ssl.label')} + onClick="APP.callDelayed(this, validateConfigStoreSSL)" ${selectConfigStoreSSL} /> + ${page.getLocalizedString('host.name.label')} - + + ${page.getLocalizedString('port.label')} + ${page.getLocalizedString('admin.port.label')} + ${page.getLocalizedString('jmx.port.label')} - + + ${page.getLocalizedString('step2.encr.key')} - + + ${page.getLocalizedString('root.suffix.label')} + ${page.getLocalizedString('login.id.label')} + ${page.getLocalizedString('password.label')} - +
-
-
-
-
- - +
- @@ -519,151 +519,151 @@

$page.getLocalizedString("step3.title")

-
-
- +
+ - + + +

@@ -676,6 +676,3 @@

$page.getLocalizedString("step3.title")

- - - From 4f071571f0b564a5b6eed0d7de9a9dc8e287e7e2 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Tue, 7 Jul 2026 11:52:51 +0300 Subject: [PATCH 06/27] Add Windows guard to Step2Test for directory write permission validation --- .../click-to-freemarker/04-implementation-notes.md | 12 ++++++++++++ .../com/sun/identity/config/wizard/Step2Test.java | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/docs/migration/click-to-freemarker/04-implementation-notes.md b/docs/migration/click-to-freemarker/04-implementation-notes.md index 5aaa4ff7be..899707f22e 100644 --- a/docs/migration/click-to-freemarker/04-implementation-notes.md +++ b/docs/migration/click-to-freemarker/04-implementation-notes.md @@ -237,6 +237,18 @@ calling this method from an `openam-core` test throws `ExceptionInInitializerErr the browser) is the only current coverage for that handler, same category as the still-open "real FreeMarker render of `step1.ftl` isn't unit-tested" gap above. +### `Step2Test.validateConfigDirRejectsNoWritePermission` needed a Windows guard + +CI on a Windows agent failed this test: `File.setWritable(false)` on a directory isn't enforced +there (Windows doesn't honor the read-only attribute for writes *into* a directory, only for the +file itself), so `Step2.hasWritePermission()`'s `File.canWrite()` check kept returning `true` and +`validateConfigDir()` took the happy path instead of the rejection branch. Same underlying +category as this doc's other "not reliably reproducible across environments" gaps, just discovered +post-merge via CI rather than while writing the test. Fixed by checking whether `setWritable(false)` +actually took effect (`canWrite()` still `true` afterwards) and skipping via `SkipException` with an +explanatory message when it didn't — also covers a privileged/root POSIX test runner, which would +hit the same symptom for the same reason (permission checks bypassed). + ## Increment 3: Step3 + LDAPStoreWizardPage Step3 is the heaviest page so far (many public-field/session pre-fills, the `LDAPStore` object, diff --git a/openam-core/src/test/java/com/sun/identity/config/wizard/Step2Test.java b/openam-core/src/test/java/com/sun/identity/config/wizard/Step2Test.java index aaa11ce010..3c11bd712c 100644 --- a/openam-core/src/test/java/com/sun/identity/config/wizard/Step2Test.java +++ b/openam-core/src/test/java/com/sun/identity/config/wizard/Step2Test.java @@ -36,6 +36,7 @@ import com.sun.identity.config.SessionAttributeNames; import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.testng.SkipException; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -100,6 +101,17 @@ public void validateConfigDirRejectsMissingDir() { public void validateConfigDirRejectsNoWritePermission() throws IOException { tempDir = Files.createTempDirectory("step2-nowrite"); tempDir.toFile().setWritable(false); + if (tempDir.toFile().canWrite()) { + // File.setWritable(false) on a directory isn't enforced by every environment this + // suite runs in: Windows doesn't honor the read-only attribute for writes into a + // directory, and a privileged (e.g. root) test runner on POSIX bypasses permission + // checks entirely. Same "not reliably reproducible across environments" category as + // the other environment-dependent branches documented in + // docs/migration/click-to-freemarker/04-implementation-notes.md. + tempDir.toFile().setWritable(true); + throw new SkipException( + "Cannot make " + tempDir + " non-writable in this environment (Windows or privileged test runner)"); + } param("dir", tempDir.toString()); step2.validateConfigDir(); From 1aa3a9e49f52674f1a4889f8661474b01a4a3467 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Tue, 7 Jul 2026 14:12:52 +0300 Subject: [PATCH 07/27] Increment 4 migration --- .../04-implementation-notes.md | 78 +++++++- .../com/sun/identity/config/wizard/Step7.java | 27 ++- .../config/servlet/ConfiguratorServlet.java | 2 + .../sun/identity/config/wizard/Step7Test.java | 189 ++++++++++++++++++ .../servlet/ConfiguratorServletTest.java | 12 +- .../WEB-INF/templates/config/wizard/step7.ftl | 113 +++++++++++ .../src/main/webapp/config/wizard/step7.htm | 116 ----------- 7 files changed, 403 insertions(+), 134 deletions(-) create mode 100644 openam-core/src/test/java/com/sun/identity/config/wizard/Step7Test.java create mode 100644 openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step7.ftl delete mode 100644 openam-server-only/src/main/webapp/config/wizard/step7.htm diff --git a/docs/migration/click-to-freemarker/04-implementation-notes.md b/docs/migration/click-to-freemarker/04-implementation-notes.md index 899707f22e..1dd289939f 100644 --- a/docs/migration/click-to-freemarker/04-implementation-notes.md +++ b/docs/migration/click-to-freemarker/04-implementation-notes.md @@ -1,7 +1,7 @@ -# Implementation notes: increments 0-3 +# Implementation notes: increments 0-4 -Findings from actually building the pilot (0, 1), the Steps 2/4/5/6 batch (2), and Step3 + -LDAPStoreWizardPage (3), worth knowing before increments 4+. Background in +Findings from actually building the pilot (0, 1), the Steps 2/4/5/6 batch (2), Step3 + +LDAPStoreWizardPage (3), and Step7 (4), worth knowing before increments 5+. Background in `02-recommendation.md` / `03-migration-plan.md`. ## FreeMarker's `WebappTemplateLoader` cannot be used (javax vs jakarta) @@ -333,3 +333,75 @@ class: the apparent dead weight is original behavior, not migration residue. `ConfiguratorServletTest`'s "unregistered path forwards to Click" example was moved from `step3.htm` (now migrated) to `step7.htm` (still Click, next up in increment 4) — update this again whenever step7 lands. + +## Increment 4: Step7 (summary) + +The simplest page so far: `Step7.onInit()` only reads session attributes and `addModel`s a +display-only summary, and — confirmed by grepping `step7.htm` for `actionLink` before porting — +**the template never posts back to the page at all**. No `ActionLink`s, no +`@ConfiguratorAction` methods, nothing to dispatch. `ConfiguratorServlet`'s registry entry alone +(`Step7.class`) is the entire wiring; the page class itself only overrides `onSecurityCheck()` +(copied 1:1 from the other steps' `ProtectedPage` port) and `onInit()`. + +All three `SetupPage` helpers Step7 needs (`getAttribute`, `getHostName` transitively via +`getAvailablePort`, `getAvailablePort` itself) were already added in increments 2-3 — no new +`SetupPage` surface required for this increment. + +### `$embedded` in step7.htm was already dead before this port — a model-key/template-var mismatch, not a `!""` gap + +Different from every other "silently blank" finding in increments 2-3 (which were template vars +never `addModel`-ed at all): here **both sides exist, under different names**. `Step7.java` only +ever calls `add("isEmbedded", "1")`; `step7.htm`/`step7.ftl` only ever reads `$embedded`/ +`${embedded}`. Confirmed by grepping the whole `com.sun.identity.config` tree (and Click's own +`Page`/`AjaxPage`) for a public field or model key literally named `embedded` — there isn't one +reachable by Step7. Net effect, **unchanged by this port**: the two embedded-only summary rows +(local admin port, local JMX port) never render on the summary page, regardless of whether the +config store is actually embedded. Ported as `<#if embedded??>` (an existence check on a key that +is never populated, so always false) rather than inventing a real boolean — matches the "byte- +identical, bugs included" rule already applied to Step4's ODSEE checkbox bug and Step3's dead +`LDAPStore` machinery. `Step7Test.onInitSummarizesEmbeddedConfigStoreWithDefaultUserStore` asserts +`model.doesNotContainKey("embedded")` specifically so a future cleanup doesn't "fix" this by +accident without it being a deliberate, reviewed decision. + +### `getAvailablePort()` runs unconditionally on every `onInit()`, even when the session already has a value + +`getAttribute("configStorePort", getAvailablePort(50389))` (and the `configStoreAdminPort`/ +`configStoreJmxPort` siblings) evaluates the `getAvailablePort(...)` argument **eagerly**, in +plain Java, before `getAttribute` ever checks whether the session attribute exists — this was true +in the original Click `Step7.java` too, copied verbatim here. So every single render of the +summary page binds and releases up to three real local sockets via +`AMSetupUtils.getFirstUnusedPort`/`isPortInUse`, whether or not the result is actually used. Not a +regression (identical to pre-migration behavior) and not this increment's job to optimize, but +worth knowing: unlike Step3Test (which carefully picked external-data-store scenarios specifically +to avoid ever calling `isPortInUse`), `Step7Test` cannot avoid this — every test stubs +`request.getServerName()` to `"localhost"` purely to keep the resulting socket bind deterministic. + +### `EXT_DATA_STORE` NPE if Step7 is reached before Step4 ever ran for the session + +`Step7.onInit()`'s user-store branch does `tmp = ctx.getSessionAttribute(EXT_DATA_STORE); if +(tmp.equals("true"))` with no null guard — identical to the original Click page. `EXT_DATA_STORE` +is written only by `Step4.onInit()`. Checked whether this is reachable through the real UI: the +still-Click wizard shell (`wizard.htm`, increment 5) lazy-loads each `stepN.htm` fragment on +demand via `AjaxUtils.load` only when the user navigates to that tab (`showTab`/`nextTab`), so +normal next-button navigation always runs Step4's `onInit()` at least once before Step7 can be +reached. Not provably unreachable (a deep link or unusual navigation could in principle hit tab 7 +without tab 4), but not a behavior change either way — pre-existing in the Click version. +Locked in with a dedicated test (`Step7Test.onInitThrowsIfExtDataStoreNeverSet`) asserting the +`NullPointerException`, specifically so this doesn't get silently "fixed" as a side effect of an +unrelated future change to this method. + +### Real FreeMarker render of `step7.ftl` — checked out-of-band, not as a permanent test + +Same module-boundary gap as `step1.ftl` (the `.ftl` lives in `openam-server-only`, a WAR module +with no test tree; `ConfiguratorServlet` lives in `openam-core`). Rather than leave this fully +unverified before merging, the template was rendered directly with a throwaway FreeMarker 2.3.31 +harness (real `Configuration` + real `step7.ftl` from disk, stub `page.getLocalizedString`) +against three scenarios — minimal/embedded, fully-populated external config+user store+load +balancer, and the default-user-store branch — and confirmed no `InvalidReferenceException` in any +of them. This wasn't kept as a checked-in test (would need a cross-module test source set to load +the real production template, which is out of scope here, same call already made for `step1.ftl`); +re-run manually if `step7.ftl`'s var references change. + +`ConfiguratorServletTest`'s "unregistered path forwards to Click" example was moved from +`step7.htm` (now migrated) to `wizard.htm` (still Click, next up in increment 5) — update this +again whenever the wizard shell lands. diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Step7.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Step7.java index 1dd7506dd5..7fcb978555 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Step7.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Step7.java @@ -25,27 +25,37 @@ * $Id: Step7.java,v 1.15 2009/10/27 05:31:45 hengming Exp $ * * Portions Copyrighted 2010-2016 ForgeRock AS. + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.wizard; -import org.openidentityplatform.openam.click.Context; - import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; +import com.sun.identity.setup.AMSetupServlet; import com.sun.identity.setup.SetupConstants; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.openidentityplatform.openam.config.servlet.SetupPage; /** * This is the summary page for the values entered during the configuration * process. No actual work is done here except setting the page elements. */ -public class Step7 extends ProtectedPage { - - private static final String DISABLED = "Disabled"; - private static final String ENABLED = "Enabled"; +public class Step7 extends SetupPage { + + @Override + public boolean onSecurityCheck() { + // Ported from the old com.sun.identity.config.util.ProtectedPage: block re-entry once + // OpenAM has already been configured. + if (AMSetupServlet.isConfigured()) { + skipRender(); + return false; + } + return true; + } + @Override public void onInit() { - Context ctx = getContext(); + ConfiguratorContext ctx = getContext(); String tmp = getAttribute( SetupConstants.CONFIG_VAR_DATA_STORE, SetupConstants.SMS_EMBED_DATASTORE); @@ -141,7 +151,6 @@ public void onInit() { (String) ctx.getSessionAttribute( SessionAttributeNames.LB_PRIMARY_URL)); - // Initialize our Parent Object. super.onInit(); } diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java index 4b2ac50fba..bc06458b5f 100644 --- a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java +++ b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java @@ -36,6 +36,7 @@ import com.sun.identity.config.wizard.Step4; import com.sun.identity.config.wizard.Step5; import com.sun.identity.config.wizard.Step6; +import com.sun.identity.config.wizard.Step7; import com.sun.identity.shared.debug.Debug; import freemarker.template.Configuration; import freemarker.template.Template; @@ -71,6 +72,7 @@ public class ConfiguratorServlet extends HttpServlet { PAGES.put("/config/wizard/step4.htm", Step4.class); PAGES.put("/config/wizard/step5.htm", Step5.class); PAGES.put("/config/wizard/step6.htm", Step6.class); + PAGES.put("/config/wizard/step7.htm", Step7.class); } private volatile Configuration freemarkerConfig; diff --git a/openam-core/src/test/java/com/sun/identity/config/wizard/Step7Test.java b/openam-core/src/test/java/com/sun/identity/config/wizard/Step7Test.java new file mode 100644 index 0000000000..24bd06cf5d --- /dev/null +++ b/openam-core/src/test/java/com/sun/identity/config/wizard/Step7Test.java @@ -0,0 +1,189 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package com.sun.identity.config.wizard; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import com.sun.identity.config.SessionAttributeNames; +import com.sun.identity.setup.SetupConstants; +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Model-building coverage for the migrated Step7 (summary) page, matching the old Click + * ProtectedPage/Step7 behavior this was ported from. Step7 has no {@code ActionLink}/{@code + * @ConfiguratorAction} handlers at all - step7.htm never posts back to it - so, unlike every + * other migrated step, there is nothing to dispatch: coverage here is entirely about the {@code + * onInit()} model built for step7.ftl, exercised by calling it directly and inspecting {@code + * getModel()}. + * + *

Every {@code onInit()} call unconditionally computes {@code getAvailablePort(50389/4444/1689)} + * for the config-store port defaults, even when a session value already exists for that key + * (Java evaluates the {@code getAttribute(key, default)} default argument eagerly) - this binds + * real local sockets (see {@code AMSetupUtils.getFirstUnusedPort}), same as the original Click + * page. {@code request.getServerName()} is stubbed to {@code "localhost"} below purely to make + * that pre-existing side effect deterministic; see + * docs/migration/click-to-freemarker/04-implementation-notes.md. + * + *

The already-configured branch of {@code onSecurityCheck} isn't covered here either, same + * disproportionate-effort call as every other step since increment 1's Step1Test. + */ +public class Step7Test { + + private HttpServletRequest request; + private HttpServletResponse response; + private Map sessionAttributes; + private Step7 step7; + + @BeforeMethod + public void setup() throws Exception { + sessionAttributes = new HashMap<>(); + + HttpSession session = mock(HttpSession.class); + doAnswer(inv -> sessionAttributes.put(inv.getArgument(0), inv.getArgument(1))) + .when(session).setAttribute(anyString(), any()); + when(session.getAttribute(anyString())).thenAnswer(inv -> sessionAttributes.get(inv.getArgument(0))); + + request = mock(HttpServletRequest.class); + when(request.getSession()).thenReturn(session); + when(request.getSession(false)).thenReturn(session); + when(request.getParameter("locale")).thenReturn("en"); + when(request.getServerName()).thenReturn("localhost"); + + StringWriter responseBody = new StringWriter(); + response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(new PrintWriter(responseBody)); + + step7 = new Step7(); + step7.setContext(new ConfiguratorContext(request, response)); + } + + @Test + public void onInitSummarizesEmbeddedConfigStoreWithDefaultUserStore() { + // Realistic navigation order: the user already visited Step4, which always sets + // EXT_DATA_STORE before Step7 can be reached - see the dedicated NPE test below. + sessionAttributes.put(SessionAttributeNames.EXT_DATA_STORE, "false"); + + step7.onInit(); + + Map model = step7.getModel(); + assertThat(model.get("isEmbedded")).isEqualTo("1"); + // step7.htm/step7.ftl's embedded-only rows guard on "$embedded"/"embedded", a key Step7 + // never populates (it only ever sets "isEmbedded") - a pre-existing template/model key + // mismatch, not introduced by this port. See the implementation notes. + assertThat(model).doesNotContainKey("embedded"); + assertThat(model.get("configStoreHost")).isEqualTo("localhost"); + assertThat(model.get("displayConfigStoreSSL")).isEqualTo("No"); + assertThat(model.get("rootSuffix")).isEqualTo(Wizard.defaultRootSuffix); + assertThat(model.get("configStoreLoginId")).isEqualTo(Wizard.defaultUserName); + assertThat(model.get("firstInstance")).isEqualTo("1"); + assertThat(model).doesNotContainKey("configDirectory"); + assertThat(model).doesNotContainKey("displayUserHostName"); + assertThat(model).doesNotContainKey("loadBalancerHost"); + assertThat(model).doesNotContainKey("loadBalancerPort"); + } + + @Test + public void onInitSummarizesExternalConfigStoreAndSslEnabled() { + sessionAttributes.put(SessionAttributeNames.CONFIG_VAR_DATA_STORE, SetupConstants.SMS_DS_DATASTORE); + sessionAttributes.put("configStoreHost", "ext-config.example.com"); + sessionAttributes.put("configStoreSSL", "SSL"); + sessionAttributes.put(SessionAttributeNames.CONFIG_DIR, "/opt/openam/config"); + sessionAttributes.put(SessionAttributeNames.EXT_DATA_STORE, "false"); + + step7.onInit(); + + Map model = step7.getModel(); + assertThat(model).doesNotContainKey("isEmbedded"); + assertThat(model.get("configStoreHost")).isEqualTo("ext-config.example.com"); + assertThat(model.get("displayConfigStoreSSL")).isEqualTo("Yes"); + assertThat(model.get("configDirectory")).isEqualTo("/opt/openam/config"); + } + + @Test + public void onInitOmitsUserStoreSummaryWhenEmbeddedReplicationFlagSet() { + sessionAttributes.put(SetupConstants.DS_EMB_REPL_FLAG, SetupConstants.DS_EMP_REPL_FLAG_VAL); + + step7.onInit(); + + Map model = step7.getModel(); + assertThat(model).doesNotContainKey("firstInstance"); + assertThat(model).doesNotContainKey("displayUserHostName"); + } + + @Test + public void onInitSummarizesExternalUserStore() { + sessionAttributes.put(SessionAttributeNames.EXT_DATA_STORE, "true"); + sessionAttributes.put(SessionAttributeNames.USER_STORE_HOST, "userstore.example.com"); + sessionAttributes.put(SessionAttributeNames.USER_STORE_SSL, "SSL"); + sessionAttributes.put(SessionAttributeNames.USER_STORE_PORT, "1389"); + sessionAttributes.put(SessionAttributeNames.USER_STORE_ROOT_SUFFIX, "dc=example,dc=com"); + sessionAttributes.put(SessionAttributeNames.USER_STORE_LOGIN_ID, "cn=admin"); + sessionAttributes.put(SessionAttributeNames.USER_STORE_TYPE, "LDAPv3ForAD"); + + step7.onInit(); + + Map model = step7.getModel(); + assertThat(model.get("firstInstance")).isEqualTo("1"); + assertThat(model.get("displayUserHostName")).isEqualTo("userstore.example.com"); + assertThat(model.get("xuserHostSSL")).isEqualTo("Yes"); + assertThat(model.get("userHostPort")).isEqualTo("1389"); + assertThat(model.get("userRootSuffix")).isEqualTo("dc=example,dc=com"); + assertThat(model.get("userLoginID")).isEqualTo("cn=admin"); + assertThat(model.get("userStoreType")).isEqualTo("Active Directory with Host and Port"); + } + + @Test + public void onInitSummarizesLoadBalancerWhenConfigured() { + sessionAttributes.put(SessionAttributeNames.EXT_DATA_STORE, "false"); + sessionAttributes.put(SessionAttributeNames.LB_SITE_NAME, "site1"); + sessionAttributes.put(SessionAttributeNames.LB_PRIMARY_URL, "https://lb.example.com:8443"); + + step7.onInit(); + + Map model = step7.getModel(); + assertThat(model.get("loadBalancerHost")).isEqualTo("site1"); + assertThat(model.get("loadBalancerPort")).isEqualTo("https://lb.example.com:8443"); + } + + @Test + public void onInitThrowsIfExtDataStoreNeverSet() { + // Pre-existing behavior, not introduced by this port: EXT_DATA_STORE is only ever written + // by Step4.onInit(). If Step7 is somehow rendered before that has happened once for this + // session, `tmp.equals("true")` NPEs on the still-null session attribute. In real usage the + // wizard shell only lazy-loads step7.htm when the user reaches tab 7, by which point every + // earlier tab (including 4) has already run onInit() at least once - so this isn't reachable + // through normal next-button navigation. Locked in here so it isn't silently "fixed" as a + // side effect of some future change. See the implementation notes. + assertThatThrownBy(() -> step7.onInit()).isInstanceOf(NullPointerException.class); + } +} diff --git a/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java b/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java index 6b5be26846..3de8131364 100644 --- a/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java +++ b/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java @@ -35,11 +35,11 @@ /** * Routing behavior of {@link ConfiguratorServlet} against the real migrated-page registry - * (steps 1, 2, 3, 4, 5, 6 as of this increment): a registered path dispatches {@code ?actionLink=} - * requests to the matching {@code @ConfiguratorAction} method, and an unregistered path forwards, - * by name, to the still-installed {@code click-servlet} - proving the mixed-engine fallback this - * whole approach depends on. {@code step7.htm} (not yet migrated - see - * docs/migration/click-to-freemarker/03-migration-plan.md, increment 4) stands in for "still on + * (steps 1, 2, 3, 4, 5, 6, 7 as of this increment): a registered path dispatches {@code + * ?actionLink=} requests to the matching {@code @ConfiguratorAction} method, and an unregistered + * path forwards, by name, to the still-installed {@code click-servlet} - proving the mixed-engine + * fallback this whole approach depends on. {@code wizard.htm} (not yet migrated - see + * docs/migration/click-to-freemarker/03-migration-plan.md, increment 5) stands in for "still on * Click" below; update this to whichever page is next in line as increments land. */ public class ConfiguratorServletTest { @@ -95,7 +95,7 @@ public void unknownActionLinkOnRegisteredPageIsRejected() throws Exception { @Test public void unregisteredPathForwardsToClickByName() throws Exception { - when(request.getServletPath()).thenReturn("/config/wizard/step7.htm"); + when(request.getServletPath()).thenReturn("/config/wizard/wizard.htm"); RequestDispatcher dispatcher = mock(RequestDispatcher.class); when(servletContext.getNamedDispatcher("click-servlet")).thenReturn(dispatcher); diff --git a/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step7.ftl b/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step7.ftl new file mode 100644 index 0000000000..838586009b --- /dev/null +++ b/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/step7.ftl @@ -0,0 +1,113 @@ +

+

${page.getLocalizedString('summary.title')}

+

${page.getLocalizedString('summary.description')}

+ +
+ + + + + + +
${page.getLocalizedString('summary.title')}
+
+ + ${page.getLocalizedString('step3.sub.title')} + ${page.getLocalizedString('edit.label')} +
+ + + + + +
+ ${page.getLocalizedString('ssl.label')}
+ ${page.getLocalizedString('host.name.label')}
+ ${page.getLocalizedString('local.port.label')}
+ <#if embedded??> + ${page.getLocalizedString('local.admin.port.label')}
+ ${page.getLocalizedString('local.jmx.port.label')}
+ + ${page.getLocalizedString('root.suffix.label')}
+ ${page.getLocalizedString('user.name.label')}
+ ${page.getLocalizedString('directory.name.label')} +
+ ${displayConfigStoreSSL}
+ ${configStoreHost}
+ ${configStorePort}
+ <#if embedded??> + ${configStoreAdminPort}
+ ${configStoreJmxPort}
+ + ${rootSuffix}
+ ${configStoreLoginId}
+ ${configDirectory!""} +
+
+ + <#if firstInstance??> + ${page.getLocalizedString('step4.sub.title')} + ${page.getLocalizedString('edit.label')} +
+ + + <#if displayUserHostName??> + + + <#else> + + + +
+ ${page.getLocalizedString('ssl.label')}
+ ${page.getLocalizedString('host.name.label')}
+ ${page.getLocalizedString('local.port.label')}
+ ${page.getLocalizedString('root.suffix.label')}
+ ${page.getLocalizedString('user.name.label')}
+ ${page.getLocalizedString('store.type.label')} +
+ ${xuserHostSSL!""}
+ ${displayUserHostName}
+ ${userHostPort!""}
+ ${userRootSuffix!""}
+ ${userLoginID!""}
+ ${userStoreType!""} +
+ ${page.getLocalizedString('default.user.store')} +
+
+ + + ${page.getLocalizedString('step5.sub.title')} + ${page.getLocalizedString('edit.label')} +
+ + + <#if loadBalancerHost??> + + + <#else> + + + +
+ ${page.getLocalizedString('site.name.label')}
+ ${page.getLocalizedString('primary.url.label')}
+ ${page.getLocalizedString('session.ha.sfo.enabled.label')} +
+ ${loadBalancerHost}
+ ${loadBalancerPort!""} +
+ ${page.getLocalizedString('site.configure.label')} +
+
+
+
+ + + + + + +
+
diff --git a/openam-server-only/src/main/webapp/config/wizard/step7.htm b/openam-server-only/src/main/webapp/config/wizard/step7.htm deleted file mode 100644 index 26a7d7a0e9..0000000000 --- a/openam-server-only/src/main/webapp/config/wizard/step7.htm +++ /dev/null @@ -1,116 +0,0 @@ - - -
-

$page.getLocalizedString("summary.title")

-

$page.getLocalizedString("summary.description")

- -
- - - - - - -
$page.getLocalizedString("summary.title")
-
- - $page.getLocalizedString("step3.sub.title") - $page.getLocalizedString("edit.label") -
- - - - - -
- $page.getLocalizedString("ssl.label")
- $page.getLocalizedString("host.name.label")
- $page.getLocalizedString("local.port.label")
- #if ($embedded) - $page.getLocalizedString("local.admin.port.label")
- $page.getLocalizedString("local.jmx.port.label")
- #end - $page.getLocalizedString("root.suffix.label")
- $page.getLocalizedString("user.name.label")
- $page.getLocalizedString("directory.name.label") -
- $displayConfigStoreSSL
- $configStoreHost
- $configStorePort
- #if ($embedded) - $configStoreAdminPort
- $configStoreJmxPort
- #end - $rootSuffix
- $configStoreLoginId
- $configDirectory -
-
- - #if ($firstInstance) - $page.getLocalizedString("step4.sub.title") - $page.getLocalizedString("edit.label") -
- - - #if ($displayUserHostName) - - - #else - - #end - -
- $page.getLocalizedString("ssl.label")
- $page.getLocalizedString("host.name.label")
- $page.getLocalizedString("local.port.label")
- $page.getLocalizedString("root.suffix.label")
- $page.getLocalizedString("user.name.label")
- $page.getLocalizedString("store.type.label") -
- $xuserHostSSL
- $displayUserHostName
- $userHostPort
- $userRootSuffix
- $userLoginID
- $userStoreType -
- $page.getLocalizedString("default.user.store") -
-
- #end - - $page.getLocalizedString("step5.sub.title") - $page.getLocalizedString("edit.label") -
- - - #if ($loadBalancerHost) - - - #else - - #end - -
- $page.getLocalizedString("site.name.label")
- $page.getLocalizedString("primary.url.label")
- $page.getLocalizedString("session.ha.sfo.enabled.label") -
- $loadBalancerHost
- $loadBalancerPort -
- $page.getLocalizedString("site.configure.label") -
-
-
-
- - - - - - -
-
- From 5f009e9534b57071eae836475083b43c3e4eb6e3 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Tue, 7 Jul 2026 14:37:51 +0300 Subject: [PATCH 08/27] Increment 5 migration --- .../04-implementation-notes.md | 125 +++++++++++++++++- .../sun/identity/config/wizard/Wizard.java | 64 +++++---- .../config/servlet/ConfiguratorServlet.java | 2 + .../openam/config/servlet/SetupPage.java | 21 ++- .../identity/config/wizard/WizardTest.java | 117 ++++++++++++++++ .../servlet/ConfiguratorServletTest.java | 8 +- .../templates/config/wizard/wizard.ftl} | 100 +++++++------- 7 files changed, 354 insertions(+), 83 deletions(-) create mode 100644 openam-core/src/test/java/com/sun/identity/config/wizard/WizardTest.java rename openam-server-only/src/main/webapp/{config/wizard/wizard.htm => WEB-INF/templates/config/wizard/wizard.ftl} (83%) diff --git a/docs/migration/click-to-freemarker/04-implementation-notes.md b/docs/migration/click-to-freemarker/04-implementation-notes.md index 1dd289939f..ec4016bd98 100644 --- a/docs/migration/click-to-freemarker/04-implementation-notes.md +++ b/docs/migration/click-to-freemarker/04-implementation-notes.md @@ -1,8 +1,8 @@ -# Implementation notes: increments 0-4 +# Implementation notes: increments 0-5 Findings from actually building the pilot (0, 1), the Steps 2/4/5/6 batch (2), Step3 + -LDAPStoreWizardPage (3), and Step7 (4), worth knowing before increments 5+. Background in -`02-recommendation.md` / `03-migration-plan.md`. +LDAPStoreWizardPage (3), Step7 (4), and the wizard shell (5), worth knowing before increments 6+. +Background in `02-recommendation.md` / `03-migration-plan.md`. ## FreeMarker's `WebappTemplateLoader` cannot be used (javax vs jakarta) @@ -405,3 +405,122 @@ re-run manually if `step7.ftl`'s var references change. `ConfiguratorServletTest`'s "unregistered path forwards to Click" example was moved from `step7.htm` (now migrated) to `wizard.htm` (still Click, next up in increment 5) — update this again whenever the wizard shell lands. + +## Increment 5: the wizard shell (`Wizard.java` + `wizard.ftl`) + +The first page constructed from a class whose old Click version relied on **eager instance-field +initializers** (`private String hostName = getHostName();`, and three `getAvailablePort`-derived +`public String defaultPort/defaultAdminPort/defaultJmxPort` fields built from it) that themselves +call `getContext()`. This surfaced a real gap in the wrapper architecture, not just a per-page +porting detail. + +### `getContext()` is not safe in a field initializer or constructor under `ConfiguratorServlet` + +Old Click's `Page.getContext()` reads `Context.getThreadLocalContext()`, a `ThreadLocal` that +`ClickServlet` binds **before** it constructs the page instance (`newPageInstance(...)` runs after +the thread-local is already set) — so a Click page's field initializers, and even its constructor +body, can safely call `getContext()`. `ConfiguratorServlet.service()` does the opposite: +it calls `pageClass.getDeclaredConstructor().newInstance()` **first**, then +`page.setContext(new ConfiguratorContext(...))` afterwards. Any migrated page whose fields or +constructor call `getContext()`/`getHostName()`/etc. eagerly will NPE, because `context` is still +null at that point. + +None of Steps 1-7 hit this (none has a field initializer that touches the context). Fix applied +here: moved the equivalent computation into `Wizard.onInit()`, which `ConfiguratorServlet` always +calls (right after `onSecurityCheck()` passes) before either render or `?actionLink=` dispatch — +the same "runs unconditionally on every request" guarantee already established in increment 1's +`onInit()` note. This preserves the old behavior's actual observable effect: old Click reconstructed +a fresh (non-stateful — `Wizard` never calls `setStateful(true)`) page per request, so +`hostName`/`defaultPort`/`defaultAdminPort`/`defaultJmxPort` were recomputed, and fresh sockets +bound/released via `AMSetupUtils.getFirstUnusedPort`, on **every single request to `wizard.htm`** +regardless of whether it was a plain GET or an `?actionLink=` call — not just on first load. Moving +the computation to `onInit()` reproduces exactly that, just later in the call stack than field-init +time. **If a future page needs eager state that depends on the request/session, put it in +`onInit()`, never in a field initializer or the constructor.** + +### `SetupPage.configLocale` had to become `protected`, not stay `private` + +`Wizard.createConfig()`'s last step (`request.addParameter("locale", configLocale.toString())`) +reads the old `AjaxPage.configLocale` field directly — it was `protected` there, deliberately, for +subclass access. `SetupPage`'s port of the same field was `private`. `DefaultSummary.java` +(increment 6, not yet migrated) has the identical `configLocale.toString()` line, so this isn't +a Wizard-only fix: `SetupPage.configLocale` is now `protected`, matching `AjaxPage`'s original +visibility exactly. Worth checking for other `protected`-in-`AjaxPage`-but-`private`-in-`SetupPage` +fields before increment 6 lands, since Options/DefaultSummary are the last consumers of the old +base class's subclass-visible state. + +### `testNewInstanceUrl`/`pushConfig`: `ActionLink`s with no backing method at all — not the same as Step1/Step6's kept-but-unreferenced ones + +Old `Wizard.java` declared `testUrlLink`/`pushConfigLink` `ActionLink`s bound by name to +`testNewInstanceUrl()`/`pushConfig()` — but **neither method exists anywhere in `Wizard.java`**, +and `git log --follow -p` on the file shows they never did in this repo's history. This is different +from Step1's `checkAdminPassword`/`checkAgentPassword` or Step6's `checkAgentPassword` (increments 1 +and 2): those methods are real and correct, just not called by any template, so they were kept and +`@ConfiguratorAction`-annotated for URL parity. Here there is no method to annotate — Click's +`ActionLink.bindRequestValue()`/`dispatchActionEvent()` would reflectively look up `pushConfig`/ +`testNewInstanceUrl` on `Wizard` via `ClickUtils.invokeListener`, fail with +`NoSuchMethodException`, and rethrow as a `RuntimeException` ("Exception occurred invoking public +method"). Confirmed this is also unreachable through the actual UI: `wizard.htm` builds a +`pushConfigDialog` (`YAHOO.widget.SimpleDialog`) in `wizardInit()` but **never calls `.show()` on +it anywhere** — grepped the whole `webapp/` tree — so a user can never trigger +`pushNewInstanceConfig()`, the one JS function that posts `?actionLink=pushConfig`. +`testNewInstanceUrl` has no caller in the JS at all, commented or otherwise. Net effect: both +`ActionLink` fields were dropped with no replacement (nothing to port), leaving `createConfig` as +the sole `@ConfiguratorAction`. Direct-URL invocation of `?actionLink=pushConfig` now 404s instead +of 500ing — a different failure mode, but both are equally unreachable through the product, so this +is not a behavior change a real user or the IT suite can observe. + +### Two more pre-existing dead fields, left in place + +`cookieDomain` (a `private String` field, assigned `null` and never read again — shadowed by an +unrelated local variable of the same name inside `createConfig()`) and `dataStore` (a `private +String` field initialized to `SetupConstants.SMS_EMBED_DATASTORE` and never referenced again +anywhere in the class) are both fully dead, pre-existing this migration. Kept byte-for-byte, same +"port the bugs/dead code, don't clean up" rule already applied to Step3's `LDAPStore` machinery and +Step4/Step7's findings. + +### `startingTab`'s "user cookie" comment was already stale before this port + +`wizard.htm`'s JS comment (`// determined by Click Wizard.java control based on user cookie`) +doesn't match the actual code: `Wizard.java` has no cookie-reading logic anywhere, `startingTab` is +a plain `public int startingTab = 1;` field never reassigned except by client-side JS +(`startNewConfig()` sets the JS-local `startingTab = 2`, which never round-trips back to the +server). Only the now-actively-wrong framework reference ("Click Wizard.java") was edited out of +the ported comment in `wizard.ftl`; the "user cookie" claim was left as-is since it predates this +migration and isn't this increment's job to fix — noted here so it isn't mistaken for a port error. + +### `wizard.htm` has zero Velocity control-flow directives + +Grepped for `#if`/`#foreach`/`#set`/`#else`/`#end` — none. Only four distinct `$`-prefixed forms +appear (`$context`, `$path` is absent as a bare token — it's always paired with `$context` as +`$context$path` — `$startingTab`, `$page.getLocalizedString(...)`), all straight token-map +substitutions. The simplest template port of the six done so far. + +### Test coverage: `createConfig()` itself is not unit-tested, but a real Selenium IT test already covers it end-to-end + +`createConfig()` is the wizard's "execute" operation, but its only substantive logic is copying +session state into request parameters ahead of one direct call to the static +`AMSetupServlet.processRequest(request, response)`, which performs real configuration writes +(embedded/external OpenDJ, on-disk config) — out of proportion for an `openam-core` unit test, same +category of call already made for `AMSetupServlet.isConfigured()` in increment 1. Unlike earlier +"manual-smoke-only" gaps, this one already has automated e2e coverage: +`openam-server/.../test/integration/IT_SetupWithOpenDJ.java` is a Cargo-container Selenium test that +walks all seven wizard tabs via `nextTabButton` and clicks `writeConfigButton`, which is exactly +`Wizard.createConfig()`'s only trigger path. Worth running that IT suite (not attempted this session +— it needs a full container deploy, out of scope for the `openam-core`-only build used here) as the +real verification for this method, rather than adding static-mocking (e.g. PowerMock, already a test +dependency per increment 1's note) to fake it in a unit test — consistent with not introducing that +technique anywhere else in this migration so far. + +### `wizard.ftl` real-render check + +Same module-boundary gap as `step1.ftl`/`step7.ftl` (`.ftl` lives in `openam-server-only`, no test +tree; `ConfiguratorServlet` lives in `openam-core`). Rendered directly with a throwaway FreeMarker +2.3.31 harness (real `Configuration` + real `wizard.ftl` from disk, stub `page.getLocalizedString`) +with `context`/`path`/`startingTab` stubs; confirmed no `InvalidReferenceException` and spot-checked +that `${context}${path}?actionLink=createConfig` etc. resolve correctly. Not kept as a checked-in +test, same call as the prior two pages; re-run manually if `wizard.ftl`'s var references change. + +`ConfiguratorServletTest`'s "unregistered path forwards to Click" example was moved from +`wizard.htm` (now migrated) to `options.htm` (still Click, next up in increment 6) — update this +again whenever `Options.java`/`options.ftl` lands. diff --git a/openam-core/src/main/java/com/sun/identity/config/wizard/Wizard.java b/openam-core/src/main/java/com/sun/identity/config/wizard/Wizard.java index 24e87770d0..e504f5b286 100644 --- a/openam-core/src/main/java/com/sun/identity/config/wizard/Wizard.java +++ b/openam-core/src/main/java/com/sun/identity/config/wizard/Wizard.java @@ -25,7 +25,7 @@ * $Id: Wizard.java,v 1.27 2009/01/17 02:05:35 kevinserwin Exp $ * * Portions Copyrighted 2010-2016 ForgeRock AS. - * Portions Copyrighted 2025 3A Systems LLC. + * Portions Copyrighted 2025-2026 3A Systems LLC. */ package com.sun.identity.config.wizard; @@ -35,10 +35,10 @@ import jakarta.servlet.http.HttpServletRequest; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.SetupPage; import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.config.util.ProtectedPage; import com.sun.identity.setup.AMSetupServlet; import com.sun.identity.setup.AMSetupUtils; import com.sun.identity.setup.ConfiguratorException; @@ -47,37 +47,53 @@ import com.sun.identity.setup.SetupConstants; import com.sun.identity.shared.Constants; -public class Wizard extends ProtectedPage implements Constants { +public class Wizard extends SetupPage implements Constants { public int startingTab = 1; - public ActionLink createConfigLink = - new ActionLink("createConfig", this, "createConfig" ); - public ActionLink testUrlLink = - new ActionLink("testNewInstanceUrl", this, "testNewInstanceUrl" ); - public ActionLink pushConfigLink = - new ActionLink("pushConfig", this, "pushConfig" ); - private String cookieDomain = null; - private String hostName = getHostName(); + private String hostName; private String dataStore = SetupConstants.SMS_EMBED_DATASTORE; - + public static String defaultUserName = "cn=Directory Manager"; public static String defaultPassword = ""; public static String defaultRootSuffix = DEFAULT_ROOT_SUFFIX; - public String defaultPort = Integer.toString( - AMSetupUtils.getFirstUnusedPort(hostName, 50389, 1000)); - public String defaultAdminPort = Integer.toString( - AMSetupUtils.getFirstUnusedPort(hostName, 4444, 1000)); - public String defaultJmxPort = Integer.toString( - AMSetupUtils.getFirstUnusedPort(hostName, 1689, 1000)); - + public String defaultPort; + public String defaultAdminPort; + public String defaultJmxPort; + + @Override + public boolean onSecurityCheck() { + // Ported from the old com.sun.identity.config.util.ProtectedPage: block re-entry once + // OpenAM has already been configured. + if (AMSetupServlet.isConfigured()) { + skipRender(); + return false; + } + return true; + } + + @Override + public void onInit() { + super.onInit(); + // The old Click Wizard computed these in eager field initializers, which ran fine there + // because Click binds Context.getThreadLocalContext() before constructing the page. + // ConfiguratorContext is only attached after construction here (see ConfiguratorServlet), + // so the same "recompute on every request" behavior moves to onInit(), which runs + // unconditionally before both render and action dispatch. + hostName = getHostName(); + defaultPort = Integer.toString(AMSetupUtils.getFirstUnusedPort(hostName, 50389, 1000)); + defaultAdminPort = Integer.toString(AMSetupUtils.getFirstUnusedPort(hostName, 4444, 1000)); + defaultJmxPort = Integer.toString(AMSetupUtils.getFirstUnusedPort(hostName, 1689, 1000)); + } + /** - * This is the 'execute' operation for the entire wizard. This method - * aggregates all data submitted across the wizard pages here in one lump + * This is the 'execute' operation for the entire wizard. This method + * aggregates all data submitted across the wizard pages here in one lump * and hands it off to the back-end for processing. */ + @ConfiguratorAction public boolean createConfig() { HttpServletRequest req = getContext().getRequest(); @@ -302,10 +318,8 @@ public boolean createConfig() { } catch (ConfiguratorException cfe) { writeToResponse(cfe.getMessage()); } - - setPath(null); + return false; } - } diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java index bc06458b5f..78d3c024cc 100644 --- a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java +++ b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java @@ -37,6 +37,7 @@ import com.sun.identity.config.wizard.Step5; import com.sun.identity.config.wizard.Step6; import com.sun.identity.config.wizard.Step7; +import com.sun.identity.config.wizard.Wizard; import com.sun.identity.shared.debug.Debug; import freemarker.template.Configuration; import freemarker.template.Template; @@ -73,6 +74,7 @@ public class ConfiguratorServlet extends HttpServlet { PAGES.put("/config/wizard/step5.htm", Step5.class); PAGES.put("/config/wizard/step6.htm", Step6.class); PAGES.put("/config/wizard/step7.htm", Step7.class); + PAGES.put("/config/wizard/wizard.htm", Wizard.class); } private volatile Configuration freemarkerConfig; diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java index 21c722883f..4d9602ca86 100644 --- a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java +++ b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/SetupPage.java @@ -56,7 +56,10 @@ public abstract class SetupPage { private final Map model = new HashMap<>(); private ConfiguratorContext context; private boolean skipRender = false; - private java.util.Locale configLocale; + // Protected, not private: matches the old AjaxPage's visibility. Wizard.createConfig() (and + // DefaultSummary, not yet migrated) read this field directly to stamp the "locale" request + // parameter sent to AMSetupServlet.processRequest(). + protected java.util.Locale configLocale; private ResourceBundle rb; private String hostName; @@ -229,6 +232,22 @@ protected String getCookieDomain() { return getHostName(); } + protected String getHostName(String serverUrl, String defaultHostName) { + try { + return new java.net.URL(serverUrl).getHost(); + } catch (java.net.MalformedURLException mue) { + return defaultHostName; + } + } + + protected int getServerPort(String serverUrl, int defaultPort) { + try { + return new java.net.URL(serverUrl).getPort(); + } catch (java.net.MalformedURLException mue) { + return defaultPort; + } + } + protected String getAvailablePort(int portNumber) { return Integer.toString(AMSetupUtils.getFirstUnusedPort(getHostName(), portNumber, 1000)); } diff --git a/openam-core/src/test/java/com/sun/identity/config/wizard/WizardTest.java b/openam-core/src/test/java/com/sun/identity/config/wizard/WizardTest.java new file mode 100644 index 0000000000..9b95e2f86d --- /dev/null +++ b/openam-core/src/test/java/com/sun/identity/config/wizard/WizardTest.java @@ -0,0 +1,117 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package com.sun.identity.config.wizard; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * Coverage for the migrated wizard shell page, matching the old Click {@code ProtectedPage}/ + * {@code Wizard} behavior this was ported from. + * + *

{@code onInit()} unconditionally recomputes {@code hostName}/{@code defaultPort}/{@code + * defaultAdminPort}/{@code defaultJmxPort} on every request via {@code + * AMSetupUtils.getFirstUnusedPort}, binding real local sockets - same as the old Click page, + * which did this in eager field initializers at page-construction time (see the implementation + * notes for why that moved to {@code onInit()} here). {@code request.getServerName()} is stubbed + * to {@code "localhost"} below purely to make that side effect deterministic; the assertions below + * don't pin exact port numbers since the actual free port depends on the test host's environment. + * + *

{@code createConfig()} - the wizard's "execute" operation - is not unit-tested: its only + * substantive logic is aggregating session state into request parameters ahead of a direct call to + * the static {@code AMSetupServlet.processRequest(...)}, which performs real configuration writes + * (LDAP, on-disk config) that are out of scope for an {@code openam-core} unit test - same + * disproportionate-effort call already made for {@code AMSetupServlet.isConfigured()} in + * increment 1. The migration plan's own end-to-end verification step (walk the wizard through a + * browser and click "Create Configuration") is the coverage for this method; the existing Selenium + * IT test ({@code IT_SetupWithOpenDJ}) already drives this exact flow. See the implementation + * notes. + * + *

The already-configured branch of {@code onSecurityCheck} isn't covered here either, same + * disproportionate-effort call as every other step since increment 1's Step1Test. + */ +public class WizardTest { + + private HttpServletRequest request; + private HttpServletResponse response; + private Map sessionAttributes; + private Wizard wizard; + + @BeforeMethod + public void setup() throws Exception { + sessionAttributes = new HashMap<>(); + + HttpSession session = mock(HttpSession.class); + doAnswer(inv -> sessionAttributes.put(inv.getArgument(0), inv.getArgument(1))) + .when(session).setAttribute(anyString(), any()); + when(session.getAttribute(anyString())).thenAnswer(inv -> sessionAttributes.get(inv.getArgument(0))); + + request = mock(HttpServletRequest.class); + when(request.getSession()).thenReturn(session); + when(request.getSession(false)).thenReturn(session); + when(request.getParameter("locale")).thenReturn("en"); + when(request.getServerName()).thenReturn("localhost"); + + StringWriter responseBody = new StringWriter(); + response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(new PrintWriter(responseBody)); + + wizard = new Wizard(); + wizard.setContext(new ConfiguratorContext(request, response)); + } + + @Test + public void onInitComputesHostNameAndDefaultPorts() { + wizard.onInit(); + + assertThat(wizard.startingTab).isEqualTo(1); + assertThat(wizard.defaultPort).isNotNull(); + assertThat(Integer.parseInt(wizard.defaultPort)).isGreaterThan(0); + assertThat(Integer.parseInt(wizard.defaultAdminPort)).isGreaterThan(0); + assertThat(Integer.parseInt(wizard.defaultJmxPort)).isGreaterThan(0); + } + + @Test + public void onInitRecomputesDefaultPortsOnEveryCall() { + // Matches the old Click page's per-request eager-field-init behavior (see the class + // javadoc): every onInit() call binds fresh sockets, it never reuses a cached value. + wizard.onInit(); + String firstPort = wizard.defaultPort; + + wizard.defaultPort = null; + wizard.onInit(); + + assertThat(wizard.defaultPort).isNotNull(); + assertThat(wizard.defaultPort).isEqualTo(firstPort); + } +} diff --git a/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java b/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java index 3de8131364..70f718adcb 100644 --- a/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java +++ b/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java @@ -35,11 +35,11 @@ /** * Routing behavior of {@link ConfiguratorServlet} against the real migrated-page registry - * (steps 1, 2, 3, 4, 5, 6, 7 as of this increment): a registered path dispatches {@code + * (steps 1-7 and the wizard shell as of this increment): a registered path dispatches {@code * ?actionLink=} requests to the matching {@code @ConfiguratorAction} method, and an unregistered * path forwards, by name, to the still-installed {@code click-servlet} - proving the mixed-engine - * fallback this whole approach depends on. {@code wizard.htm} (not yet migrated - see - * docs/migration/click-to-freemarker/03-migration-plan.md, increment 5) stands in for "still on + * fallback this whole approach depends on. {@code options.htm} (not yet migrated - see + * docs/migration/click-to-freemarker/03-migration-plan.md, increment 6) stands in for "still on * Click" below; update this to whichever page is next in line as increments land. */ public class ConfiguratorServletTest { @@ -95,7 +95,7 @@ public void unknownActionLinkOnRegisteredPageIsRejected() throws Exception { @Test public void unregisteredPathForwardsToClickByName() throws Exception { - when(request.getServletPath()).thenReturn("/config/wizard/wizard.htm"); + when(request.getServletPath()).thenReturn("/config/options.htm"); RequestDispatcher dispatcher = mock(RequestDispatcher.class); when(servletContext.getNamedDispatcher("click-servlet")).thenReturn(dispatcher); diff --git a/openam-server-only/src/main/webapp/config/wizard/wizard.htm b/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/wizard.ftl similarity index 83% rename from openam-server-only/src/main/webapp/config/wizard/wizard.htm rename to openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/wizard.ftl index 034fad0d5d..a6d8927760 100644 --- a/openam-server-only/src/main/webapp/config/wizard/wizard.htm +++ b/openam-server-only/src/main/webapp/WEB-INF/templates/config/wizard/wizard.ftl @@ -5,7 +5,7 @@ //convenient alias: var $ = YAHOO.util.Dom.get; - var startingTab = $startingTab; //determined by Click Wizard.java control based on user cookie + var startingTab = ${startingTab}; //determined by Wizard.java based on user cookie var currentTab = startingTab; var previousTab = 1; @@ -32,7 +32,7 @@ var field = ""; function validate() { - var callUrl = "$context$path?actionLink=validateInput"; + var callUrl = "${context}${path}?actionLink=validateInput"; var key = "&key=" + field; var value = "&value=" + encodeURIComponent($(field).value); ie7fix++; @@ -41,7 +41,7 @@ } function validatePost() { - var callUrl = "$context$path?actionLink=validateInput"; + var callUrl = "${context}${path}?actionLink=validateInput"; var key = "key=" + field; var value = "&value=" + encodeURIComponent($(field).value); ie7fix++; @@ -51,14 +51,14 @@ } function fieldValidated(response) { - if (response.responseText == "true") { + if (response.responseText == "true") { eval(field + "Valid = true;" ); $(field + 'Status').innerHTML = okString; } else { eval(field + "Valid = false;" ); $(field + 'Status').innerHTML = errorImage + '' + response.responseText + ''; - } + } field = ""; } @@ -89,7 +89,7 @@ } // helper function - do not call directly. - // change the references from tab6 to tab5 if skipping agent + // change the references from tab6 to tab5 if skipping agent function _tabLoaded( tabNum ) { $('wizardStep' + currentTab).style.display = "none"; $('wizardStep' + tabNum).style.display = ""; @@ -117,7 +117,7 @@ tabNum = rangeCheck(tabNum); var callback = new Function( _tabLoaded( tabNum ) ); var tab = 'wizardStep' + tabNum; - var url = "$context/config/wizard/step" + tabNum + ".htm?" + getLocale(); + var url = "${context}/config/wizard/step" + tabNum + ".htm?" + getLocale(); AjaxUtils.load(tab, url, callback); } @@ -154,29 +154,29 @@ YAHOO.sun.identity.config.options.wizard.pushConfigDialog.hide(); YAHOO.sun.identity.config.options.wizard.pushingConfig.show(); ie7fix++; - AjaxUtils.call( "$context$path?actionLink=pushConfig&ie7fix=" + ie7fix, + AjaxUtils.call( "${context}${path}?actionLink=pushConfig&ie7fix=" + ie7fix, onPushNewInstanceConfigResponse ); } function cancelPushNewInstanceConfig() { - document.location = "$context/commonTasks.htm"; + document.location = "${context}/commonTasks.htm"; } function writeConfigurationAsync() { ie7fix++; - AjaxUtils.call("$context$path?actionLink=createConfig&ie7fix=" + ie7fix, + AjaxUtils.call("${context}${path}?actionLink=createConfig&ie7fix=" + ie7fix, writeConfigResponse ); } - + function writeConfiguration() { document.getElementById("returnToConfig").style.display = "none"; document.getElementById("setupMessage").innerHTML = ""; YAHOO.sun.identity.config.options.inProgress.show(); - + var fr1 = window.frames['progressIframe']; if ( fr1 ) { - fr1.location = "$context/setup/setSetupProgress"; + fr1.location = "${context}/setup/setSetupProgress"; } - setTimeout('writeConfigurationAsync()', 2000); + setTimeout('writeConfigurationAsync()', 2000); } /* ============================= @@ -244,7 +244,7 @@ function storeServerValidated( type, response ) { if( response.responseText == "true" ) { eval( type + "StoreHostValid = true;" ); - $(type + 'StoreHostStatus').innerHTML = ' ' + '$page.getLocalizedString("ok.string")'; + $(type + 'StoreHostStatus').innerHTML = ' ' + '${page.getLocalizedString('ok.string')}'; } else { eval( type + "StoreHostValid = true;" ); $(type + 'StoreHostStatus').innerHTML = response.responseText; @@ -255,8 +255,8 @@ function storeBaseDNValidated( type, response ) { if( response.responseText == "true" ) { eval( type + "StoreBaseDNValid = true;" ); - $(type + 'StoreBaseDNStatus').innerHTML = ' ' + - '$page.getLocalizedString("ok.string")'; + $(type + 'StoreBaseDNStatus').innerHTML = ' ' + + '${page.getLocalizedString('ok.string')}'; } else { eval( type + "StoreBaseDNValid = true;" ); $(type + 'StoreBaseDNStatus').innerHTML = response.responseText; @@ -267,8 +267,8 @@ function storeLoginIdValidated( type, response ) { if( response.responseText == "true" ) { eval( type + "StoreLoginIdValid = true;" ); - $(type + 'StoreLoginIdStatus').innerHTML = ' ' + - '$page.getLocalizedString("ok.string")'; + $(type + 'StoreLoginIdStatus').innerHTML = ' ' + + '${page.getLocalizedString('ok.string')}'; } else { eval( type + "StoreLoginIdValid = true;" ); $(type + 'StoreLoginIdStatus').innerHTML = response.responseText; @@ -279,7 +279,7 @@ function toPath( type ) { return "/config/wizard/step" + (type == 'config' ? 3 : 4 ) + ".htm"; } - + function clearStore( type ) { $(type + 'StoreHost').value = null; @@ -296,7 +296,7 @@ $(type + 'StoreLoginIdStatus').innerHTML = ""; eval( type + "StoreLoginIdValid = false;" ); - var callUrl = "$context" + toPath(type) + "?actionLink=clearStore"; + var callUrl = "${context}" + toPath(type) + "?actionLink=clearStore"; ie7fix++; callUrl = callUrl + "&ie7fix=" + ie7fix; AjaxUtils.call( callUrl ); @@ -307,23 +307,23 @@ } function wizardAcceptLicense() { - YAHOO.util.Dom.addClass('wizard', 'license-accepted'); + YAHOO.util.Dom.addClass('wizard', 'license-accepted'); } function wizardInit() { - //YAHOO.util.Dom.removeClass('wizard', 'license-accepted'); - //AjaxUtils.simpleCall("$context/legal-notices/license.html", renderLicenseWizard); + //YAHOO.util.Dom.removeClass('wizard', 'license-accepted'); + //AjaxUtils.simpleCall("${context}/legal-notices/license.html", renderLicenseWizard); YAHOO.sun.identity.config.options.wizard.writeConf = new YAHOO.widget.Panel("writeConf", { width:"240px", fixedcenter:true, close:false, draggable:false, zindex:4, modal:true, visible:false }); YAHOO.sun.identity.config.options.wizard.writeConf.setHeader("Writing configuration. Please wait..."); - YAHOO.sun.identity.config.options.wizard.writeConf.setBody(''); + YAHOO.sun.identity.config.options.wizard.writeConf.setBody(''); YAHOO.sun.identity.config.options.wizard.writeConf.render(document.body); YAHOO.sun.identity.config.options.wizard.pushingConfig = new YAHOO.widget.Panel("pushingConfig", { width:"260px", fixedcenter:true, close:false, draggable:false, zindex:4, modal:true, visible:false }); YAHOO.sun.identity.config.options.wizard.pushingConfig.setHeader("Pushing configuration to new instance. Please wait..."); - YAHOO.sun.identity.config.options.wizard.pushingConfig.setBody(''); + YAHOO.sun.identity.config.options.wizard.pushingConfig.setBody(''); YAHOO.sun.identity.config.options.wizard.pushingConfig.render(document.body); YAHOO.sun.identity.config.options.wizard.pushConfigDialog = new YAHOO.widget.SimpleDialog("pushConfigDialog", { @@ -353,19 +353,19 @@ YAHOO.util.Event.addListener("wizardCancelButton", "click", cancelWizard); var showCurrentTab = new Function( showTab(currentTab) ); - AjaxUtils.load('wizardStep' + currentTab, "$context/config/wizard/step" + currentTab + ".htm", showCurrentTab); + AjaxUtils.load('wizardStep' + currentTab, "${context}/config/wizard/step" + currentTab + ".htm", showCurrentTab); } YAHOO.util.Event.onDOMReady(wizardInit); - +

-
$page.getLocalizedString("configurator.main.title")
-
$page.getLocalizedString("configurator.custom.title")
+
${page.getLocalizedString('configurator.main.title')}
+
${page.getLocalizedString('configurator.custom.title')}
-
 
+
 
@@ -375,13 +375,13 @@
    -
  1. $page.getLocalizedString("general.tab")
  2. -
  3. $page.getLocalizedString("server.tab")
  4. -
  5. $page.getLocalizedString("configuration.tab")
  6. -
  7. $page.getLocalizedString("user.tab")
  8. -
  9. $page.getLocalizedString("site.tab")
  10. -
  11. $page.getLocalizedString("agent.tab")
  12. -
  13. $page.getLocalizedString("summary.tab")
  14. +
  15. ${page.getLocalizedString('general.tab')}
  16. +
  17. ${page.getLocalizedString('server.tab')}
  18. +
  19. ${page.getLocalizedString('configuration.tab')}
  20. +
  21. ${page.getLocalizedString('user.tab')}
  22. +
  23. ${page.getLocalizedString('site.tab')}
  24. +
  25. ${page.getLocalizedString('agent.tab')}
  26. +
  27. ${page.getLocalizedString('summary.tab')}
 
@@ -402,34 +402,34 @@
-
-
 
+
+
 
- - - - + + + +
- +
- +
From 44d4a7ec5fc76ad6be32d0fc5156ac54092eae5f Mon Sep 17 00:00:00 2001 From: maximthomas Date: Tue, 7 Jul 2026 15:05:59 +0300 Subject: [PATCH 09/27] Increment 6 migration --- .../click-to-freemarker/03-migration-plan.md | 3 +- .../04-implementation-notes.md | 153 +++++++- .../sun/identity/config/DefaultSummary.java | 26 +- .../java/com/sun/identity/config/Options.java | 44 +-- .../identity/config/util/TemplatedPage.java | 98 ----- .../config/servlet/ConfiguratorServlet.java | 4 + .../identity/config/DefaultSummaryTest.java | 71 ++++ .../com/sun/identity/config/OptionsTest.java | 82 +++++ .../servlet/ConfiguratorServletTest.java | 13 +- .../templates/config/defaultSummary.ftl} | 64 ++-- .../templates/config/options.ftl} | 342 ++++++++++++------ 11 files changed, 608 insertions(+), 292 deletions(-) delete mode 100644 openam-core/src/main/java/com/sun/identity/config/util/TemplatedPage.java create mode 100644 openam-core/src/test/java/com/sun/identity/config/DefaultSummaryTest.java create mode 100644 openam-core/src/test/java/com/sun/identity/config/OptionsTest.java rename openam-server-only/src/main/webapp/{config/defaultSummary.htm => WEB-INF/templates/config/defaultSummary.ftl} (80%) rename openam-server-only/src/main/webapp/{config/options.htm => WEB-INF/templates/config/options.ftl} (53%) diff --git a/docs/migration/click-to-freemarker/03-migration-plan.md b/docs/migration/click-to-freemarker/03-migration-plan.md index 75dc0fd1b1..257944fabd 100644 --- a/docs/migration/click-to-freemarker/03-migration-plan.md +++ b/docs/migration/click-to-freemarker/03-migration-plan.md @@ -212,7 +212,8 @@ Within each increment, one page = one commit where practical, so review stays pa under `/config/auth/default` and `/config/federation`.) - [ ] Delete `openam-core/src/main/java/org/openidentityplatform/openam/click/` (57 files) and the `org.openidentityplatform.openam.velocity` fork. -- [ ] Delete `com/sun/identity/config/util/AjaxPage.java`, `ProtectedPage.java`, `TemplatedPage.java` +- [x] `TemplatedPage.java` deleted in increment 6 (its only subclass, `Options`, migrated off it). +- [ ] Delete `com/sun/identity/config/util/AjaxPage.java`, `ProtectedPage.java` (all logic now in `SetupPage`/`SetupUtils`); fold `SetupUtils` back inline if the split is no longer worth it. - [ ] Delete `WEB-INF/click.xml` and `WEB-INF/classes/click-page.properties`. diff --git a/docs/migration/click-to-freemarker/04-implementation-notes.md b/docs/migration/click-to-freemarker/04-implementation-notes.md index ec4016bd98..a27f201758 100644 --- a/docs/migration/click-to-freemarker/04-implementation-notes.md +++ b/docs/migration/click-to-freemarker/04-implementation-notes.md @@ -1,8 +1,8 @@ -# Implementation notes: increments 0-5 +# Implementation notes: increments 0-6 Findings from actually building the pilot (0, 1), the Steps 2/4/5/6 batch (2), Step3 + -LDAPStoreWizardPage (3), Step7 (4), and the wizard shell (5), worth knowing before increments 6+. -Background in `02-recommendation.md` / `03-migration-plan.md`. +LDAPStoreWizardPage (3), Step7 (4), the wizard shell (5), and Options + DefaultSummary (6), worth +knowing before increment 7+. Background in `02-recommendation.md` / `03-migration-plan.md`. ## FreeMarker's `WebappTemplateLoader` cannot be used (javax vs jakarta) @@ -524,3 +524,150 @@ test, same call as the prior two pages; re-run manually if `wizard.ftl`'s var re `ConfiguratorServletTest`'s "unregistered path forwards to Click" example was moved from `wizard.htm` (now migrated) to `options.htm` (still Click, next up in increment 6) — update this again whenever `Options.java`/`options.ftl` lands. + +## Increment 6: Options + DefaultSummary + +The first increment to migrate a **full top-level HTML page**, not a fragment. Every page in +increments 1-5 (steps 1-7, the wizard shell) is loaded as an AJAX fragment into some container — +`options.htm` is that container. It's requested directly by the browser and, uniquely among all +12 old templates, is the *only* page whose Click page class overrides `getTemplate()` (via +`TemplatedPage`) to point at a **second, wrapping** template (`assets/templates/main.html`), which +Velocity's `#parse($path)` then used to splice `options.htm`'s own content into the middle of a +shared HTML/``/copyright-footer shell. `defaultSummary.htm` (the other page in this +increment) has no such wrapping — like every fragment page before it, its class extends +`ProtectedPage`, not `TemplatedPage`, so it never went through `main.html` at all. + +### `ConfiguratorServlet` has no decorator/layout support, so `main.html` had to be inlined into `options.ftl` + +Click's two-level template resolution (page template, wrapped by a layout template that +`#parse`s it back in) has no equivalent in `ConfiguratorServlet.render()`, which loads and +processes exactly one named template per URL. Building generic decorator support for this one +remaining full-page use case would be speculative infrastructure for a single caller. Instead, +`options.ftl` is `main.html` and the old `options.htm` **merged into one file**: the full +``/``/table-layout shell from `main.html`, with `options.htm`'s content (its own +``/` - +
-
$page.getLocalizedString("fam.configurator.title")
-
$page.getLocalizedString("default.config.title")
+
${page.getLocalizedString('fam.configurator.title')}
+
${page.getLocalizedString('default.config.title')}
 
@@ -110,26 +110,26 @@
    -
  1. $page.getLocalizedString("passwords.tab")
  2. +
  3. ${page.getLocalizedString('passwords.tab')}
 
 
-

$page.getLocalizedString("default.config.subtitle")

-

$page.getLocalizedString("default.config.description")

+

${page.getLocalizedString('default.config.subtitle')}

+

${page.getLocalizedString('default.config.description')}

-

* $page.getLocalizedString("required.field.label")

+

* ${page.getLocalizedString('required.field.label')}

-
$page.getLocalizedString("step1.subtitle")
+
${page.getLocalizedString('step1.subtitle')}
- + - + - + \n"); - } - - // Render JavaScript form validation code - if (isJavaScriptValidation()) { - buffer.append("\n"); - } - } - - /** - * Render the given list of Buttons to the string buffer. - * - * @param buffer the StringBuffer to render to - */ - protected void renderButtons(HtmlStringBuffer buffer) { - - List\n"); - - buffer.append("
$page.getLocalizedString("default.user.name")${page.getLocalizedString('default.user.name')}
 * $page.getLocalizedString("password.label") * ${page.getLocalizedString('password.label')} @@ -137,7 +137,7 @@

$page.getLocalizedString("default.config.subtitle")

 * $page.getLocalizedString("confirm.label") * ${page.getLocalizedString('confirm.label')} $page.getLocalizedString("default.config.subtitle") -
$page.getLocalizedString("agent.step.subtitle")
+
${page.getLocalizedString('agent.step.subtitle')}
- + - + - + \n"); - } - - /** - * Render the specified controls of the container to the string buffer. - *

- * fieldWidths is a map specifying the width for specific fields contained - * in the list of controls. The fieldWidths map is keyed on field name. - * - * @param buffer the StringBuffer to render to - * @param container the container which controls to render - * @param controls the controls to render - * @param fieldWidths a map of field widths keyed on field name - * @param columns the number of form layout table columns - */ - protected void renderControls(HtmlStringBuffer buffer, Container container, - List controls, Map fieldWidths, int columns) { - - buffer.append("

$page.getLocalizedString("agent.user.name")${page.getLocalizedString('agent.user.name')}
 * $page.getLocalizedString("password.label") * ${page.getLocalizedString('password.label')} $page.getLocalizedString("default.config.subtitle")
 * $page.getLocalizedString("confirm.label") * ${page.getLocalizedString('confirm.label')} $page.getLocalizedString("default.config.subtitle") -
+
- +
- +
diff --git a/openam-server-only/src/main/webapp/config/options.htm b/openam-server-only/src/main/webapp/WEB-INF/templates/config/options.ftl similarity index 53% rename from openam-server-only/src/main/webapp/config/options.htm rename to openam-server-only/src/main/webapp/WEB-INF/templates/config/options.ftl index 10900fb255..3984d2550e 100644 --- a/openam-server-only/src/main/webapp/config/options.htm +++ b/openam-server-only/src/main/webapp/WEB-INF/templates/config/options.ftl @@ -1,24 +1,109 @@ - + + + + OpenAM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
  
  + + + + + +
+ + +
+
+
+
- +
- #if ( $upgrade || $upgradeCompleted) -

$page.getLocalizedString("upgrade.available")

- $page.getLocalizedString("upgrade.available.option"):
$currentVersion
- #else -

$page.getLocalizedString("configuration.options.title")

-

$page.getLocalizedString("configuration.options.subtitle")

- #end - - #if ($upgrade || $upgradeCompleted) + <#if upgrade || upgradeCompleted> +

${page.getLocalizedString('upgrade.available')}

+ ${page.getLocalizedString('upgrade.available.option')}:
${currentVersion!""}
+ <#else> +

${page.getLocalizedString('configuration.options.title')}

+

${page.getLocalizedString('configuration.options.subtitle')}

+ + + <#if upgrade || upgradeCompleted>
-

$page.getLocalizedString("upgrade.title")

- $page.getLocalizedString("upgrade.description") - $page.getLocalizedString("upgrade.link") - #else +

${page.getLocalizedString('upgrade.title')}

+ ${page.getLocalizedString('upgrade.description')} + ${page.getLocalizedString('upgrade.link')} + <#else>
-

$page.getLocalizedString("configuration.options.option1.title")

- $page.getLocalizedString("configuration.options.option1.description") +

${page.getLocalizedString('configuration.options.option1.title')}

+ ${page.getLocalizedString('configuration.options.option1.description')}

- $page.getLocalizedString("configuration.options.option1.link") + ${page.getLocalizedString('configuration.options.option1.link')}

- #end +
- #if($upgrade || $upgradeCompleted) - #else + <#if upgrade || upgradeCompleted> + <#else>
-

$page.getLocalizedString("configuration.options.option2.title")

- $page.getLocalizedString("configuration.options.option2.description") +

${page.getLocalizedString('configuration.options.option2.title')}

+ ${page.getLocalizedString('configuration.options.option2.description')}

- $page.getLocalizedString("configuration.options.option2.link") + ${page.getLocalizedString('configuration.options.option2.link')}

- #end +
- #if ( $upgrade || $upgradeCompleted) + <#if upgrade || upgradeCompleted>
- #else + <#else>
- #end + + +
+
+
 
  +
+ ${page.getLocalizedString('product.copyrights')} +
+
 
+ + + From 82c8b2bdb2c74eb7d3e2277a1cfbe0e49f84a0b0 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Tue, 7 Jul 2026 15:33:02 +0300 Subject: [PATCH 10/27] Increment 7 migration --- .../04-implementation-notes.md | 129 ++++++++++++++++++ .../servlet/ConfiguratorPageProvider.java | 38 ++++++ .../config/servlet/ConfiguratorServlet.java | 9 ++ .../servlet/ConfiguratorServletTest.java | 28 +++- .../templates/config/upgrade/upgrade.ftl} | 44 +++--- .../sun/identity/config/upgrade/Upgrade.java | 31 +++-- .../config/upgrade/UpgradePageProvider.java | 39 ++++++ ...am.config.servlet.ConfiguratorPageProvider | 1 + .../identity/config/upgrade/UpgradeTest.java | 69 ++++++++++ ...ConfiguratorServletUpgradeRoutingTest.java | 92 +++++++++++++ 10 files changed, 440 insertions(+), 40 deletions(-) create mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorPageProvider.java rename openam-server-only/src/main/webapp/{config/upgrade/upgrade.htm => WEB-INF/templates/config/upgrade/upgrade.ftl} (75%) create mode 100644 openam-upgrade/src/main/java/com/sun/identity/config/upgrade/UpgradePageProvider.java create mode 100644 openam-upgrade/src/main/resources/META-INF/services/org.openidentityplatform.openam.config.servlet.ConfiguratorPageProvider create mode 100644 openam-upgrade/src/test/java/com/sun/identity/config/upgrade/UpgradeTest.java create mode 100644 openam-upgrade/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletUpgradeRoutingTest.java diff --git a/docs/migration/click-to-freemarker/04-implementation-notes.md b/docs/migration/click-to-freemarker/04-implementation-notes.md index a27f201758..8bea8c8e50 100644 --- a/docs/migration/click-to-freemarker/04-implementation-notes.md +++ b/docs/migration/click-to-freemarker/04-implementation-notes.md @@ -671,3 +671,132 @@ references change. `ConfiguratorServletTest`'s "unregistered path forwards to Click" example was moved from `options.htm` (now migrated) to `upgrade.htm` (still Click, next up in increment 7) — update this again whenever `Upgrade.java`/`upgrade.ftl` lands. + +## Increment 7: Upgrade (`openam-upgrade` module) + +The last real page. This is also the first (and, per the plan's increment order, only) migrated +page that doesn't live in `openam-core` — and that broke an assumption every prior increment's +registry entry relied on without ever stating it. + +### `Upgrade.java` lives downstream of `ConfiguratorServlet`, so it can't be a compile-time registry entry + +`openam-upgrade` depends on `openam-core` (for `openam-core`'s huge shared surface generally, not +specifically for anything configurator-related) — not the other way round. `ConfiguratorServlet` +(in `openam-core`) adding `PAGES.put("/config/upgrade/upgrade.htm", Upgrade.class)` the way every +prior increment did for its own page would need a compile-time import of `Upgrade`, which would +make `openam-core` depend on `openam-upgrade` too: a Maven cycle. Nothing in `03-migration-plan.md` +anticipated this because every other page (`Step1`-`Step7`, `Wizard`, `Options`, `DefaultSummary`) +already lived in `openam-core`. + +Fix: a small `ConfiguratorPageProvider` SPI (`getPath()` / `getPageClass()`) in +`org.openidentityplatform.openam.config.servlet`, discovered via `ServiceLoader` — +`ConfiguratorServlet`'s static initializer now also runs +`ServiceLoader.load(ConfiguratorPageProvider.class)` and merges whatever it finds into `PAGES`. +`Upgrade` itself doesn't implement the SPI (it's a `SetupPage`, not a registration descriptor); a +tiny sibling class, `com.sun.identity.config.upgrade.UpgradePageProvider`, does, and +`openam-upgrade/src/main/resources/META-INF/services/org.openidentityplatform.openam.config.servlet.ConfiguratorPageProvider` +names it. `openam-upgrade` can freely implement an interface declared in `openam-core` (that's the +dependency direction that already exists), so this needs no new Maven dependency in either +direction — genuine decoupling, not just a workaround. + +This isn't a novel idiom for this codebase: `com.sun.identity.setup.SetupListener` (also in +`openam-core`) is discovered the exact same way (`ServiceLoader.load(SetupListener.class)` in +`AMSetupServlet.registerListeners()`), with implementations from several downstream modules +(`openam-scripting`, `openam-radius`, `openam-audit-configuration`, ...) each dropping a +`META-INF/services` file. `ConfiguratorPageProvider` follows that established pattern rather than +inventing a new one (e.g. a startup `ServletContextListener` + a public +`ConfiguratorServlet.registerPage(...)` API, which was considered and rejected: it would need a new +per-increment `web.xml` listener entry, breaking the "pure Java, wired once in increment 1" +invariant the whole registry design is built on). + +**Consequence for test coverage, worth knowing before trusting `ConfiguratorServletTest` at face +value:** `openam-core`'s own test classpath never has `openam-upgrade` on it, so +`ServiceLoader.load(ConfiguratorPageProvider.class)` always finds zero providers *there* — meaning +`ConfiguratorServletTest`'s `unregisteredPathForwardsToClickByName` test, which still uses +`/config/upgrade/upgrade.htm`, keeps passing after this increment, but for a different reason than +before (module-boundary artifact of the test run, not because the page is actually unmigrated in +the real, assembled WAR). There is no longer any real un-migrated page left to use as that example +— increment 7 was the last one before final removal. The real end-to-end proof that the +ServiceLoader wiring works lives in `openam-upgrade`'s own test tree instead +(`ConfiguratorServletUpgradeRoutingTest`, package `org.openidentityplatform.openam.config.servlet`), +which — unlike `openam-core` — can see both `ConfiguratorServlet` and `Upgrade`/ +`UpgradePageProvider` on the same classpath. It drives a real `ConfiguratorServlet.service()` call +for `?actionLink=doUpgrade` and asserts the failure it gets back (a `ServletException` wrapping a +`NullPointerException` — see below) is the one that proves routing reached the real +`Upgrade.doUpgrade()`, while separately asserting the Click named-dispatcher was never invoked. This +is the first increment with a dedicated routing test living outside `openam-core`, and it's the +template to reuse if a future page ever migrates from a different downstream module. + +### `onRender()` has no equivalent in `ConfiguratorServlet` — ported to `onGet()` instead + +Unlike every previous page, the old `Upgrade.java` builds its template model in `onRender()`, not +`onInit()` or a Click `onGet()`/`onPost()` override, with an explicit comment explaining why: +`onRender()` only runs when Click is actually about to render the page, never on an `ActionLink` +dispatch (`doUpgrade`/`saveReport` both `return false` with no further Click processing) — so +`generateShortUpgradeReport(...)` (a real report-generation call, not free) is not redone on every +upgrade-progress poll or report download. + +`SetupPage.onRender()` exists (copied into the increment-1 scaffolding for Click-lifecycle parity) +but grepping `ConfiguratorServlet.service()` confirms it is **never called** — the servlet's actual +flow is `onSecurityCheck()` -> `onInit()` -> (`actionLink` present: `invokeAction()` and stop) -> +else `onGet()` -> render if not skipped. `onGet()` is the hook that actually reproduces the "only +when about to render, not on actionLink dispatch" guarantee the old `onRender()` provided — so the +model-building logic moved there instead. `Upgrade` is the first page in this migration to actually +override `onGet()` for that reason; every earlier page either had no such optimization to preserve +or built its model unconditionally in `onInit()`. If a future page is ever found overriding +`onRender()` expecting it to run, that expectation is already wrong under `ConfiguratorServlet` — +worth deleting `SetupPage.onRender()` itself in the final increment's cleanup if nothing ever ends +up using it. + +### `isLicenseAccepted()`: ported off `Context.getRequestParameter`, not `SetupPage.toString()` + +The old code reads `getContext().getRequestParameter(SetupConstants.ACCEPT_LICENSE_PARAM)`, which — +checked directly against the Click fork's `Context.getRequestParameter()` source — is a straight +`request.getParameter(name)` (plus a null-name guard that's irrelevant for a constant). Ported as +`getContext().getRequest().getParameter(...)`, **not** `SetupPage.toString(paramName)` (used +elsewhere in this migration for the same kind of read): `toString()` trims whitespace and maps +`""`/blank to `null` before `Boolean.parseBoolean` would see it, which is extra normalization the +old code never applied here. Using it would have been a silent, if extremely unlikely to matter in +practice, behavior change — flagged here as the reason this one call site looks inconsistent with +the rest of the file's style. + +### `doUpgrade()`'s pre-existing "no `error` guard" NPE — kept, locked in with a test + +If `Upgrade`'s constructor fails (`error = true`, `upgrade` stays `null`) and `doUpgrade()` is +invoked anyway, `upgrade.upgrade(...)` NPEs — uncaught, since the method only catches +`UpgradeException`. This is original Click behavior, not introduced by this port. Checked +reachability the same way as `Wizard`'s dead `ActionLink`s and `Step7`'s `EXT_DATA_STORE` NPE: +`upgrade.ftl`'s `<#if error??>` branch (ported 1:1 from Velocity's `#if ($error)`) never renders the +`upgradeButton`/`doUpgrade()` JS function when `error` is true, so normal navigation can't trigger +it — but a direct `?actionLink=doUpgrade` request still would. Locked in with +`UpgradeTest.doUpgradeThrowsIfSubsystemFailedToInitialize` (and exercised for real, end-to-end, +through the servlet by `ConfiguratorServletUpgradeRoutingTest` above) so a future refactor doesn't +silently "fix" this without it being a deliberate, reviewed decision — same category and same +rationale as `Step7Test.onInitThrowsIfExtDataStoreNeverSet`. + +### `UpgradeTest` environment gap — same category as `OptionsTest`, but this time the gap *is* the tested behavior + +`Upgrade`'s constructor calls `AdminTokenAction.getInstance()` / `UpgradeServices.getInstance()`, +both needing a real bootstrapped OpenAM environment (SSOToken infrastructure, SMS-backed config) to +succeed. In a bare `openam-upgrade` unit-test JVM this always fails, and the constructor's broad +`catch (Exception ue)` turns that into `error = true` — exactly like the old Click page did. Unlike +every prior "can't test this branch" gap in this migration, here the untestable-without-a-real- +environment branch **is** the one branch this test JVM can reliably exercise: `onGet()`'s `error` +path and the resulting NPE in `doUpgrade()`. The happy path (`error == false`, a real +`generateShortUpgradeReport`/`upgrade()` call) is manual-smoke/IT-only, same as +`Wizard.createConfig()`/`DefaultSummary.createDefaultConfig()`. + +### `upgrade.ftl` real-render check + +Same module-boundary gap as every prior page (`.ftl` lives in `openam-server-only`, no test tree; +`ConfiguratorServlet` lives in `openam-core`). Rendered directly with a throwaway FreeMarker 2.3.31 +harness (real `Configuration` + real `upgrade.ftl` from disk, stub `page.getLocalizedString`) +against both the `error` and normal (`currentVersion`/`newVersion`/`changelist` populated) branches; +confirmed no `InvalidReferenceException` in either. The `$var`-vs-`addModel` diff found no gaps this +time — `currentVersion`/`newVersion`/`changelist` are always set together in the one branch that +reads them, and `error` is read via `<#if error??>` (an existence check, not a boolean read), +matching the idiom already established for `Step7.ftl`'s `<#if embedded??>` for a key that's only +ever added when true. Not kept as a checked-in test, same call as every prior page's real-render +check; re-run manually if `upgrade.ftl`'s var references change. + +This was the last page. What's left is increment 8, the final removal. diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorPageProvider.java b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorPageProvider.java new file mode 100644 index 0000000000..9c41daab43 --- /dev/null +++ b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorPageProvider.java @@ -0,0 +1,38 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package org.openidentityplatform.openam.config.servlet; + +/** + * Lets a {@link SetupPage} that lives in a module downstream of {@code openam-core} (one that + * depends on {@code openam-core}, so cannot be depended on back) register itself into + * {@link ConfiguratorServlet}'s migrated-page registry without {@code openam-core} needing a + * compile-time reference to it. + * + *

Discovered via {@link java.util.ServiceLoader}, the same {@code META-INF/services} + * self-registration idiom already used in this codebase by + * {@link com.sun.identity.setup.SetupListener}: implement this interface in the downstream + * module and list the implementation's fully-qualified name in a + * {@code META-INF/services/org.openidentityplatform.openam.config.servlet.ConfiguratorPageProvider} + * resource file. {@code openam-core}'s own isolated test runs simply see zero providers - a + * graceful no-op, not a failure. + */ +public interface ConfiguratorPageProvider { + + /** The {@code *.htm} servlet path this provider's page handles, e.g. {@code "/config/upgrade/upgrade.htm"}. */ + String getPath(); + + Class getPageClass(); +} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java index eeae289709..55686506c0 100644 --- a/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java +++ b/openam-core/src/main/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServlet.java @@ -23,6 +23,7 @@ import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; +import java.util.ServiceLoader; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; @@ -79,6 +80,14 @@ public class ConfiguratorServlet extends HttpServlet { PAGES.put("/config/wizard/wizard.htm", Wizard.class); PAGES.put("/config/options.htm", Options.class); PAGES.put("/config/defaultSummary.htm", DefaultSummary.class); + + // Pages that live in a module downstream of openam-core (e.g. Upgrade, in openam-upgrade, + // which depends on openam-core and so cannot be depended on back without a Maven cycle) + // self-register via ConfiguratorPageProvider instead of a compile-time class reference + // here - same ServiceLoader/META-INF/services idiom as com.sun.identity.setup.SetupListener. + for (ConfiguratorPageProvider provider : ServiceLoader.load(ConfiguratorPageProvider.class)) { + PAGES.put(provider.getPath(), provider.getPageClass()); + } } private volatile Configuration freemarkerConfig; diff --git a/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java b/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java index ddbe084840..a0be54a9ae 100644 --- a/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java +++ b/openam-core/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletTest.java @@ -35,13 +35,25 @@ /** * Routing behavior of {@link ConfiguratorServlet} against the real migrated-page registry - * (steps 1-7, the wizard shell, {@code Options} and {@code DefaultSummary} as of this increment): - * a registered path dispatches {@code ?actionLink=} requests to the matching - * {@code @ConfiguratorAction} method, and an unregistered path forwards, by name, to the - * still-installed {@code click-servlet} - proving the mixed-engine fallback this whole approach - * depends on. {@code upgrade.htm} (not yet migrated - see - * docs/migration/click-to-freemarker/03-migration-plan.md, increment 7) stands in for "still on - * Click" below; update this to whichever page is next in line as increments land. + * (steps 1-7, the wizard shell, {@code Options}, {@code DefaultSummary} and, as of increment 7, + * {@code Upgrade} - every real configurator page as of this increment): a registered path + * dispatches {@code ?actionLink=} requests to the matching {@code @ConfiguratorAction} method, and + * an unregistered path forwards, by name, to the still-installed {@code click-servlet} - proving + * the mixed-engine fallback this whole approach depends on. + * + *

{@code upgrade.htm} still stands in for "unregistered" below, but for a different reason than + * every prior increment: {@code Upgrade} lives in {@code openam-upgrade}, which depends on + * {@code openam-core} (not the other way round), so it registers itself into + * {@code ConfiguratorServlet.PAGES} via {@link ConfiguratorPageProvider}/{@link + * java.util.ServiceLoader} rather than a compile-time entry in this module - see + * {@code docs/migration/click-to-freemarker/04-implementation-notes.md}. {@code openam-core}'s own + * test classpath never has {@code openam-upgrade} on it, so the ServiceLoader here always finds + * zero providers and {@code upgrade.htm} genuinely falls back to Click in this specific test run, + * even though it is fully migrated in the real, assembled WAR. The real end-to-end routing (through + * the actual ServiceLoader-discovered {@code Upgrade} page) is covered instead by a test in + * {@code openam-upgrade}, which - unlike this module - can see both classes. There is no longer any + * real, still-on-Click page left to use as the "unregistered" example here; increment 7 was the + * last one before final removal (see the migration plan's increment 8). */ public class ConfiguratorServletTest { @@ -96,6 +108,8 @@ public void unknownActionLinkOnRegisteredPageIsRejected() throws Exception { @Test public void unregisteredPathForwardsToClickByName() throws Exception { + // See the class Javadoc: this path is only "unregistered" from openam-core's own test + // classpath, which never has the openam-upgrade ServiceLoader provider on it. when(request.getServletPath()).thenReturn("/config/upgrade/upgrade.htm"); RequestDispatcher dispatcher = mock(RequestDispatcher.class); when(servletContext.getNamedDispatcher("click-servlet")).thenReturn(dispatcher); diff --git a/openam-server-only/src/main/webapp/config/upgrade/upgrade.htm b/openam-server-only/src/main/webapp/WEB-INF/templates/config/upgrade/upgrade.ftl similarity index 75% rename from openam-server-only/src/main/webapp/config/upgrade/upgrade.htm rename to openam-server-only/src/main/webapp/WEB-INF/templates/config/upgrade/upgrade.ftl index 8dac4989bf..a5320b6869 100644 --- a/openam-server-only/src/main/webapp/config/upgrade/upgrade.htm +++ b/openam-server-only/src/main/webapp/WEB-INF/templates/config/upgrade/upgrade.ftl @@ -12,7 +12,7 @@ ~ the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL ~ Header, with the fields enclosed by brackets [] replaced by your own identifying ~ information: "Portions copyright [year] [name of copyright owner]". - ~ + ~ ~ Portions Copyrighted 2015 Nomura Research Institute, Ltd. --> @@ -21,20 +21,20 @@ //convenient alias: var $ = YAHOO.util.Dom.get; - + function cancelUpgrade() { YAHOO.sun.identity.config.options.upgrade.hide(); } function saveReport() { - window.open("$context$path?actionLink=saveReport", "Download"); + window.open("${context}${path}?actionLink=saveReport", "Download"); } function writeUpgradeAsync() { ie7fix++; var licenseAccepted = document.getElementById("upgrade-accept-check").checked ? "true" : "false"; - AjaxUtils.call("$context$path?actionLink=doUpgrade&ie7fix=" + ie7fix + "&acceptLicense=" + licenseAccepted, writeConfigResponse); + AjaxUtils.call("${context}${path}?actionLink=doUpgrade&ie7fix=" + ie7fix + "&acceptLicense=" + licenseAccepted, writeConfigResponse); } function doUpgrade() { @@ -44,7 +44,7 @@ var fr1 = window.frames['progressIframe']; if (fr1) { - fr1.location = "$context/upgrade/setUpgradeProgress"; + fr1.location = "${context}/upgrade/setUpgradeProgress"; } setTimeout('writeUpgradeAsync()', 2000); } @@ -54,21 +54,21 @@ } function upgradeAcceptLicense() { - YAHOO.util.Dom.addClass('upgrade', 'license-accepted'); + YAHOO.util.Dom.addClass('upgrade', 'license-accepted'); }

-
$page.getLocalizedString("upgrade.main.title")
- #if ($error) +
${page.getLocalizedString('upgrade.main.title')}
+ <#if error??>
-

$page.getLocalizedString("upgrade.init.error")

- +

${page.getLocalizedString('upgrade.init.error')}

+
- #else + <#else>
-
 
+
 
@@ -76,32 +76,32 @@
- $page.getLocalizedString("upgrade.current.version") $currentVersion
- $page.getLocalizedString("upgrade.new.version") $newVersion + ${page.getLocalizedString('upgrade.current.version')} ${currentVersion}
+ ${page.getLocalizedString('upgrade.new.version')} ${newVersion}
- $changelist + ${changelist}
- +
 
-
+
- +
- +
- +
- #end -
\ No newline at end of file + + diff --git a/openam-upgrade/src/main/java/com/sun/identity/config/upgrade/Upgrade.java b/openam-upgrade/src/main/java/com/sun/identity/config/upgrade/Upgrade.java index b54bc5dc15..17075a9df0 100644 --- a/openam-upgrade/src/main/java/com/sun/identity/config/upgrade/Upgrade.java +++ b/openam-upgrade/src/main/java/com/sun/identity/config/upgrade/Upgrade.java @@ -21,6 +21,7 @@ * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * + * Portions Copyrighted 2026 3A Systems LLC. */ package com.sun.identity.config.upgrade; @@ -28,13 +29,13 @@ import com.iplanet.am.util.SystemProperties; import com.iplanet.sso.SSOToken; -import com.sun.identity.config.util.AjaxPage; import com.sun.identity.security.AdminTokenAction; import com.sun.identity.setup.SetupConstants; import com.sun.identity.shared.Constants; import com.sun.identity.shared.debug.Debug; import java.security.AccessController; -import org.openidentityplatform.openam.click.control.ActionLink; +import org.openidentityplatform.openam.config.servlet.ConfiguratorAction; +import org.openidentityplatform.openam.config.servlet.SetupPage; import org.forgerock.openam.upgrade.UpgradeException; import org.forgerock.openam.upgrade.UpgradeProgress; import org.forgerock.openam.upgrade.UpgradeServices; @@ -44,15 +45,18 @@ /** * OpenAM upgrade page. */ -public class Upgrade extends AjaxPage { +public class Upgrade extends SetupPage { private UpgradeServices upgrade; private SSOToken adminToken; private Debug debug = UpgradeUtils.debug; - public ActionLink doUpgradeLink = new ActionLink("doUpgrade", this, "doUpgrade"); - public ActionLink saveReportLink = new ActionLink("saveReport", this, "saveReport"); private boolean error = false; + // No onSecurityCheck() override: the old Upgrade extended AjaxPage directly (not + // ProtectedPage), so it stayed reachable regardless of AMSetupServlet.isConfigured() - that's + // how the options page (increment 6) reaches it to run an upgrade on an already-configured + // install. SetupPage's default onSecurityCheck() (always true) already matches that. + public Upgrade() { try { debug.message("Initializing upgrade subsystem."); @@ -65,11 +69,16 @@ public Upgrade() { } /** - * Creating the UI models in the #onRender method makes sure that these fields are not always reinitialized when an - * actionLink is clicked. This is necessary since Click always creates new instances for actionLink events. + * Building the UI model in {@code onGet()} (called only when the request is actually going + * to render, not on an {@code ?actionLink=} dispatch - see + * {@code org.openidentityplatform.openam.config.servlet.ConfiguratorServlet}) makes sure + * {@code generateShortUpgradeReport} is not redone on every {@code doUpgrade}/ + * {@code saveReport} call. This is necessary since Click always creates new instances for + * actionLink events, and the old code relied on {@code onRender()} for the identical reason - + * {@code onRender()} itself has no equivalent in {@code ConfiguratorServlet}'s lifecycle. */ @Override - public void onRender() { + public void onGet() { if (error) { addModel("error", true); } else { @@ -79,6 +88,7 @@ public void onRender() { } } + @ConfiguratorAction public boolean doUpgrade() { try { SystemProperties.initializeProperties(Constants.SYS_PROPERTY_INSTALL_TIME, "true"); @@ -89,19 +99,18 @@ public boolean doUpgrade() { writeToResponse(ue.getMessage()); debug.error("Error occured while upgrading OpenAM", ue); } finally { - setPath(null); UpgradeProgress.closeOutputStream(); } return false; } + @ConfiguratorAction public boolean saveReport() { getContext().getResponse().setContentType("application/force-download; charset=\"UTF-8\""); getContext().getResponse().setHeader( "Content-Disposition", "attachment; filename=\"upgradereport." + currentTimeMillis() + "\""); getContext().getResponse().setHeader("Content-Description", "File Transfer"); writeToResponse(upgrade.generateDetailedUpgradeReport(adminToken, false)); - setPath(null); return false; } @@ -111,6 +120,6 @@ public boolean saveReport() { * @return true if the license acceptance parameter is present and correct, otherwise false. */ private boolean isLicenseAccepted() { - return Boolean.parseBoolean(getContext().getRequestParameter(SetupConstants.ACCEPT_LICENSE_PARAM)); + return Boolean.parseBoolean(getContext().getRequest().getParameter(SetupConstants.ACCEPT_LICENSE_PARAM)); } } diff --git a/openam-upgrade/src/main/java/com/sun/identity/config/upgrade/UpgradePageProvider.java b/openam-upgrade/src/main/java/com/sun/identity/config/upgrade/UpgradePageProvider.java new file mode 100644 index 0000000000..d0da7bd4c8 --- /dev/null +++ b/openam-upgrade/src/main/java/com/sun/identity/config/upgrade/UpgradePageProvider.java @@ -0,0 +1,39 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package com.sun.identity.config.upgrade; + +import org.openidentityplatform.openam.config.servlet.ConfiguratorPageProvider; +import org.openidentityplatform.openam.config.servlet.SetupPage; + +/** + * Registers {@link Upgrade} with {@code ConfiguratorServlet} via {@link java.util.ServiceLoader} + * (see {@code META-INF/services/org.openidentityplatform.openam.config.servlet.ConfiguratorPageProvider} + * in this module's resources). {@code openam-upgrade} depends on {@code openam-core}, not the + * other way round, so this self-registration is what lets {@code Upgrade} join the migrated-page + * registry without {@code openam-core} needing a compile-time reference to it. + */ +public class UpgradePageProvider implements ConfiguratorPageProvider { + + @Override + public String getPath() { + return "/config/upgrade/upgrade.htm"; + } + + @Override + public Class getPageClass() { + return Upgrade.class; + } +} diff --git a/openam-upgrade/src/main/resources/META-INF/services/org.openidentityplatform.openam.config.servlet.ConfiguratorPageProvider b/openam-upgrade/src/main/resources/META-INF/services/org.openidentityplatform.openam.config.servlet.ConfiguratorPageProvider new file mode 100644 index 0000000000..dc3ed513a1 --- /dev/null +++ b/openam-upgrade/src/main/resources/META-INF/services/org.openidentityplatform.openam.config.servlet.ConfiguratorPageProvider @@ -0,0 +1 @@ +com.sun.identity.config.upgrade.UpgradePageProvider diff --git a/openam-upgrade/src/test/java/com/sun/identity/config/upgrade/UpgradeTest.java b/openam-upgrade/src/test/java/com/sun/identity/config/upgrade/UpgradeTest.java new file mode 100644 index 0000000000..9ed32a91e0 --- /dev/null +++ b/openam-upgrade/src/test/java/com/sun/identity/config/upgrade/UpgradeTest.java @@ -0,0 +1,69 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package com.sun.identity.config.upgrade; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.openidentityplatform.openam.config.servlet.ConfiguratorContext; +import org.testng.annotations.Test; + +/** + * {@code Upgrade}'s constructor calls {@code AdminTokenAction.getInstance()} / + * {@code UpgradeServices.getInstance()}, both of which need a fully bootstrapped OpenAM + * environment (real SSOToken infrastructure, SMS-backed config) to succeed - unavailable in a bare + * unit-test JVM, same category of environment-coupling gap as + * {@code OptionsTest}/{@code Step2Test} document elsewhere in this migration. The constructor + * catches this broadly and sets {@code error = true}, exactly like the old Click page did, so that + * branch - and the real, pre-existing (not introduced by this port) NPE that follows from it if + * {@code doUpgrade()}/{@code saveReport()} are ever invoked anyway - is exactly what's testable + * here without a real environment. + */ +public class UpgradeTest { + + @Test + public void onGetAddsErrorToModelWhenUpgradeSubsystemFailedToInitialize() { + // No real OpenAM bootstrap in this test JVM, so the constructor's try/catch always takes + // its failure branch here - see the class Javadoc. + Upgrade page = new Upgrade(); + + page.onGet(); + + assertThat(page.getModel()).containsEntry("error", true); + assertThat(page.getModel()).doesNotContainKeys("currentVersion", "newVersion", "changelist"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void doUpgradeThrowsIfSubsystemFailedToInitialize() { + // Pre-existing behavior, ported byte-for-byte: doUpgrade() calls upgrade.upgrade(...) + // unconditionally, with no "error" guard - identical to the old Click Upgrade.java. Not + // reachable through the real UI in this state, since upgrade.htm/upgrade.ftl never renders + // the "doUpgrade" button when $error/${error??} is true - but a direct ?actionLink=doUpgrade + // request would still hit this. Locked in with a dedicated test, same category as + // Step7Test.onInitThrowsIfExtDataStoreNeverSet, so a future refactor doesn't silently "fix" + // this without it being a deliberate, reviewed decision. + Upgrade page = new Upgrade(); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + page.setContext(new ConfiguratorContext(request, response)); + + page.doUpgrade(); + } +} diff --git a/openam-upgrade/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletUpgradeRoutingTest.java b/openam-upgrade/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletUpgradeRoutingTest.java new file mode 100644 index 0000000000..079b4ebe23 --- /dev/null +++ b/openam-upgrade/src/test/java/org/openidentityplatform/openam/config/servlet/ConfiguratorServletUpgradeRoutingTest.java @@ -0,0 +1,92 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyrighted [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems LLC. + */ +package org.openidentityplatform.openam.config.servlet; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** + * openam-core's own {@code ConfiguratorServletTest} cannot exercise real routing to {@code Upgrade} + * (see its class Javadoc): its test classpath never has {@code openam-upgrade} on it, so + * {@code ServiceLoader.load(ConfiguratorPageProvider.class)} always finds zero providers there. + * This module's test classpath has both {@code ConfiguratorServlet} (openam-core) and + * {@code Upgrade}/{@code UpgradePageProvider} (openam-upgrade), so this is the one place that can + * actually prove the {@code META-INF/services} self-registration wires up end-to-end. + */ +public class ConfiguratorServletUpgradeRoutingTest { + + private ConfiguratorServlet servlet; + private ServletContext servletContext; + private HttpServletRequest request; + private HttpServletResponse response; + + @BeforeMethod + public void setup() throws Exception { + servlet = new ConfiguratorServlet(); + + servletContext = mock(ServletContext.class); + ServletConfig servletConfig = mock(ServletConfig.class); + when(servletConfig.getServletContext()).thenReturn(servletContext); + when(servletConfig.getServletName()).thenReturn("configurator-servlet"); + servlet.init(servletConfig); + + HttpSession session = mock(HttpSession.class); + request = mock(HttpServletRequest.class); + when(request.getSession()).thenReturn(session); + when(request.getSession(false)).thenReturn(session); + when(request.getParameter("locale")).thenReturn("en"); + + StringWriter responseBody = new StringWriter(); + response = mock(HttpServletResponse.class); + when(response.getWriter()).thenReturn(new PrintWriter(responseBody)); + } + + @Test + public void upgradeActionLinkRoutesToServiceLoaderDiscoveredPageInsteadOfClick() throws Exception { + RequestDispatcher clickDispatcher = mock(RequestDispatcher.class); + when(servletContext.getNamedDispatcher("click-servlet")).thenReturn(clickDispatcher); + + when(request.getServletPath()).thenReturn("/config/upgrade/upgrade.htm"); + when(request.getParameter("actionLink")).thenReturn("doUpgrade"); + + // No real OpenAM bootstrap in this test JVM, so Upgrade's constructor sets error = true and + // doUpgrade() NPEs on the still-null `upgrade` field (see UpgradeTest) - that NPE, wrapped + // by ConfiguratorServlet.invokeAction(), is itself the proof that routing reached the real + // Upgrade.doUpgrade(), rather than a 404 (unknown action) or a silent Click forward. + assertThatThrownBy(() -> servlet.service(request, response)) + .isInstanceOf(ServletException.class) + .hasCauseInstanceOf(NullPointerException.class); + + verify(clickDispatcher, never()).forward(request, response); + } +} From 27311c2007e2b5d22382f1bfd2399f24d7dc1087 Mon Sep 17 00:00:00 2001 From: maximthomas Date: Tue, 7 Jul 2026 17:25:00 +0300 Subject: [PATCH 11/27] Increment 8 migration --- .../sun/identity/config/util/AjaxPage.java | 341 -- .../identity/config/util/ProtectedPage.java | 39 - .../openam/click/ActionEventDispatcher.java | 620 --- .../openam/click/ActionListener.java | 72 - .../openam/click/ActionResult.java | 580 --- .../openam/click/Behavior.java | 74 - .../openam/click/ClickRequestWrapper.java | 288 -- .../openam/click/ClickServlet.java | 2305 ----------- .../openam/click/Context.java | 973 ----- .../openam/click/Control.java | 496 --- .../openam/click/ControlRegistry.java | 525 --- .../openam/click/Page.java | 1362 ------- .../openam/click/PageInterceptor.java | 238 -- .../openam/click/ajax/AjaxBehavior.java | 108 - .../click/control/AbstractContainer.java | 409 -- .../openam/click/control/AbstractControl.java | 1121 ------ .../openam/click/control/AbstractLink.java | 818 ---- .../openam/click/control/ActionLink.java | 517 --- .../openam/click/control/Button.java | 219 -- .../openam/click/control/Column.java | 1596 -------- .../openam/click/control/Container.java | 114 - .../openam/click/control/Decorator.java | 58 - .../openam/click/control/Field.java | 1288 ------- .../openam/click/control/FileField.java | 358 -- .../openam/click/control/Form.java | 3175 ---------------- .../openam/click/control/HiddenField.java | 381 -- .../openam/click/control/Label.java | 121 - .../openam/click/control/Radio.java | 404 -- .../openam/click/control/RadioGroup.java | 563 --- .../openam/click/control/Renderable.java | 39 - .../openam/click/control/Table.java | 2214 ----------- .../openam/click/control/TablePaginator.java | 218 -- .../openam/click/control/TextArea.java | 428 --- .../openam/click/control/TextField.java | 409 -- .../openam/click/element/CssImport.java | 273 -- .../openam/click/element/CssStyle.java | 466 --- .../openam/click/element/Element.java | 260 -- .../openam/click/element/JsImport.java | 269 -- .../openam/click/element/JsScript.java | 559 --- .../openam/click/element/ResourceElement.java | 411 -- .../click/service/ClickResourceService.java | 317 -- .../openam/click/service/ConfigService.java | 364 -- .../click/service/ConsoleLogService.java | 244 -- .../service/DefaultMessagesMapService.java | 67 - .../openam/click/service/DeployUtils.java | 555 --- .../click/service/FileUploadService.java | 65 - .../openam/click/service/LogService.java | 171 - .../click/service/MessagesMapService.java | 87 - .../openam/click/service/ResourceService.java | 84 - .../openam/click/service/TemplateService.java | 97 - .../service/VelocityTemplateService.java | 785 ---- .../click/service/XmlConfigService.java | 2241 ----------- .../openam/click/util/ClickUtils.java | 3354 ----------------- .../openam/click/util/ContainerUtils.java | 1431 ------- .../openam/click/util/ErrorPage.java | 196 - .../openam/click/util/ErrorReport.java | 729 ---- .../openam/click/util/HtmlStringBuffer.java | 568 --- .../openam/click/util/PageImports.java | 626 --- .../openam/click/util/SessionMap.java | 319 -- .../tools/view/WebappResourceLoader.java | 286 -- .../WEB-INF/classes/click-page.properties | 23 - .../src/main/webapp/WEB-INF/click.xml | 23 - .../src/main/webapp/click/error.htm | 21 - .../src/main/webapp/click/index.html | 0 .../src/main/webapp/click/not-found.htm | 18 - 65 files changed, 37380 deletions(-) delete mode 100644 openam-core/src/main/java/com/sun/identity/config/util/AjaxPage.java delete mode 100644 openam-core/src/main/java/com/sun/identity/config/util/ProtectedPage.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/ActionEventDispatcher.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/ActionListener.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/ActionResult.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/Behavior.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/ClickRequestWrapper.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/ClickServlet.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/Context.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/Control.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/ControlRegistry.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/Page.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/PageInterceptor.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/ajax/AjaxBehavior.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractContainer.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractControl.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractLink.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/ActionLink.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/Button.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/Column.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/Container.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/Decorator.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/Field.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/FileField.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/Form.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/HiddenField.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/Label.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/Radio.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/RadioGroup.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/Renderable.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/Table.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/TablePaginator.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/TextArea.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/control/TextField.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/element/CssImport.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/element/CssStyle.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/element/Element.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/element/JsImport.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/element/JsScript.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/element/ResourceElement.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/ClickResourceService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/ConfigService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/ConsoleLogService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/DefaultMessagesMapService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/DeployUtils.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/FileUploadService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/LogService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/MessagesMapService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/ResourceService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/TemplateService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/VelocityTemplateService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/service/XmlConfigService.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/util/ClickUtils.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/util/ContainerUtils.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/util/ErrorPage.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/util/ErrorReport.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/util/HtmlStringBuffer.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/util/PageImports.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/click/util/SessionMap.java delete mode 100644 openam-core/src/main/java/org/openidentityplatform/openam/velocity/tools/view/WebappResourceLoader.java delete mode 100644 openam-server-only/src/main/webapp/WEB-INF/classes/click-page.properties delete mode 100644 openam-server-only/src/main/webapp/WEB-INF/click.xml delete mode 100644 openam-server-only/src/main/webapp/click/error.htm delete mode 100644 openam-server-only/src/main/webapp/click/index.html delete mode 100644 openam-server-only/src/main/webapp/click/not-found.htm diff --git a/openam-core/src/main/java/com/sun/identity/config/util/AjaxPage.java b/openam-core/src/main/java/com/sun/identity/config/util/AjaxPage.java deleted file mode 100644 index 19f2e52eb4..0000000000 --- a/openam-core/src/main/java/com/sun/identity/config/util/AjaxPage.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2007 Sun Microsystems Inc. All Rights Reserved - * - * The contents of this file are subject to the terms - * of the Common Development and Distribution License - * (the License). You may not use this file except in - * compliance with the License. - * - * You can obtain a copy of the License at - * https://opensso.dev.java.net/public/CDDLv1.0.html or - * opensso/legal/CDDLv1.0.txt - * See the License for the specific language governing - * permission and limitations under the License. - * - * When distributing Covered Code, include this CDDL - * Header Notice in each file and include the License file - * at opensso/legal/CDDLv1.0.txt. - * If applicable, add the following below the CDDL Header, - * with the fields enclosed by brackets [] replaced by - * your own identifying information: - * "Portions Copyrighted [year] [name of copyright owner]" - * - * $Id: AjaxPage.java,v 1.24 2010/01/04 19:15:16 veiming Exp $ - * - * Portions Copyrighted 2011-2016 ForgeRock AS. - * Portions Copyrighted 2025 3A Systems LLC. - */ - -package com.sun.identity.config.util; - -import com.sun.identity.config.SessionAttributeNames; -import com.sun.identity.setup.AMSetupServlet; -import com.sun.identity.setup.AMSetupUtils; -import com.sun.identity.setup.SetupConstants; -import com.sun.identity.shared.debug.Debug; -import com.sun.identity.shared.locale.Locale; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.GeneralSecurityException; -import java.util.MissingResourceException; -import java.util.ResourceBundle; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.openidentityplatform.openam.click.Page; -import org.openidentityplatform.openam.click.control.ActionLink; -import org.openidentityplatform.openam.config.servlet.SetupUtils; -import org.forgerock.opendj.ldap.Connection; -import org.forgerock.opendj.ldap.LdapException; -import org.forgerock.opendj.ldap.ResultCode; - -public abstract class AjaxPage extends Page { - - public ActionLink checkPasswordsLink = - new ActionLink("checkPasswords", this, "checkPasswords"); - - public ActionLink validateInputLink = - new ActionLink("validateInput", this, "validateInput" ); - - public ActionLink resetSessionAttributesLink = - new ActionLink("resetSessionAttributes", this, - "resetSessionAttributes"); - - public static final String OLD_RESPONSE_TEMPLATE = "{\"isValid\":${isValid}, \"errorMessage\":\"${errorMessage}\"}"; - - private boolean rendering = false; - private String hostName; - protected java.util.Locale configLocale = null; - - // localization properties - protected ResourceBundle rb = null; - protected static final String RB_NAME = "amConfigurator"; - - public String responseString = "true"; - public static Debug debug = Debug.getInstance("amConfigurator"); - - public AjaxPage() { - } - - @Override - public void onInit() { - super.onInit(); - initializeResourceBundle(); - addModel("page", this); - } - - public boolean isRendering() { - return rendering; - } - - protected String toString( String paramName ) { - String value = getContext().getRequest().getParameter( paramName ); - value = ( value != null ? value.trim() : null ); - value = ("".equals(value) ? null : value ); - return value; - } - - protected boolean toBoolean(String paramName) { - return SetupUtils.parseBoolean(toString(paramName)); - } - - protected int toInt( String paramName ) { - return SetupUtils.parseInt(toString(paramName)); - } - - protected void writeValid() { - writeValid( null ); - } - protected void writeValid( String message ) { - String out = ( message != null ? message : "" ); - writeJsonResponse(true, out); - } - protected void writeInvalid( String message ) { - String out = ( message != null ? message : "" ); - writeJsonResponse( false, out ); - } - - protected void writeJsonResponse(String valid, String responseBody) { - writeToResponse(SetupUtils.jsonResponse(valid, responseBody)); - } - protected void writeJsonResponse(boolean valid, String responseBody) { - writeToResponse(SetupUtils.jsonResponse(valid, responseBody)); - } - - protected Connection getConnection(String host, int port, String bindDN, char[] bindPwd, int timeout, boolean isSSl) - throws GeneralSecurityException, LdapException { - return SetupUtils.getConnection(host, port, bindDN, bindPwd, timeout, isSSl); - } - - protected boolean writeErrorToResponse(ResultCode resultCode) { - String msg = SetupUtils.getMessage(resultCode); - if (msg != null) { - writeToResponse(getLocalizedString(msg)); - return true; - } - return false; - } - - protected void writeToResponse( String text ) { - try { - // Note: this writer is obtained from the Apache Click context. Should this be triggered by a JSP this - // may not be in compliance with the JSP spec regarding mandatory use of the buffered JspWriter. - getContext().getResponse().getWriter().write( text ); - this.rendering = true; - } catch ( IOException e ) { - throw new RuntimeException( e ); - } - } - - public void initializeResourceBundle() { - HttpServletRequest req = - (HttpServletRequest)getContext().getRequest(); - HttpServletResponse res = - (HttpServletResponse)getContext().getResponse(); - - setLocale(req); - res.setContentType("text/html; charset=UTF-8"); - } - - private void setLocale (HttpServletRequest request) { - if (request != null) { - String superLocale = request.getParameter("locale"); - if (superLocale != null && superLocale.length() > 0) { - configLocale = Locale.getLocaleObjFromAcceptLangHeader( - superLocale); - } else { - String acceptLangHeader = - (String)request.getHeader("Accept-Language"); - if ((acceptLangHeader != null) && - (acceptLangHeader.length() > 0)) - { - configLocale = Locale.getLocaleObjFromAcceptLangHeader( - acceptLangHeader); - } else { - configLocale = java.util.Locale.getDefault(); - } - } - try { - rb = ResourceBundle.getBundle(RB_NAME, configLocale); - } catch (MissingResourceException mre) { - // do nothing - } - } - } - - public String getQuoteEscapedLocalizedString(String i18nKey) { - String value = getLocalizedString(i18nKey); - return value.replace("'", "\\'"); - } - - public String getLocalizedString(String i18nKey) { - if (rb == null) { - initializeResourceBundle(); - } - - String localizedValue = null; - try { - localizedValue = Locale.getString(rb, i18nKey, debug); - } catch (MissingResourceException mre) { - // do nothing - } - return (localizedValue == null) ? i18nKey : localizedValue; - } - - public String getHostName() { - if (hostName == null) { - hostName = getContext().getRequest().getServerName(); - } - return hostName; - } - - public String getHostName(String serverUrl, String defaultHostName) { - URL url = null; - - try { - url = new URL(serverUrl); - } catch (MalformedURLException mue) { - return defaultHostName; - } - - return url.getHost(); - } - - public int getServerPort(String serverUrl, int defaultPort) { - URL url = null; - - try { - url = new URL(serverUrl); - } catch (MalformedURLException mue) { - return defaultPort; - } - - return url.getPort(); - } - - public String getBaseDir(HttpServletRequest req) { - String basedir = AMSetupServlet.getPresetConfigDir(); - if ((basedir == null) || (basedir.length() == 0)) { - String tmp = System.getProperty("user.home"); - if (File.separatorChar == '\\') { - tmp = tmp.replace('\\', '/'); - } - String uri = req.getRequestURI(); - int idx = uri.indexOf("/", 1); - if (idx != -1) { - uri = uri.substring(0, idx); - } - - basedir = (tmp.endsWith("/")) ? tmp.substring(0, tmp.length()-1) : - tmp; - basedir += uri; - } - - return basedir; - } - - public String getCookieDomain() { - return getHostName(); - } - - public boolean validateInput() { - String key = toString("key"); - String value = toString("value"); - - if (value == null) { - responseString = "missing.required.field"; - } else { - getContext().setSessionAttribute(key, value); - } - - writeToResponse(getLocalizedString(responseString)); - setPath(null); - return false; - } - - public boolean resetSessionAttributes() { - try { - Field[] fields = SessionAttributeNames.class.getDeclaredFields(); - for (int i = 0; i < fields.length; i++) { - try { - getContext().removeSessionAttribute( - (String)fields[i].get(null)); - } catch (IllegalAccessException e) { - //ingore - } - } - } catch (SecurityException e) { - writeToResponse(e.getMessage()); - } - setPath(null); - return false; - } - - public String getAttribute(String attr, String defaultValue) { - String value = (String)getContext().getSessionAttribute(attr); - return (value != null) ? value : defaultValue; - } - - public String getAvailablePort(int portNumber) { - return Integer.toString( - AMSetupUtils.getFirstUnusedPort(getHostName(), portNumber, 1000)); - } - - public boolean checkPasswords() { - String confirm = toString("confirm"); - String password = toString("password"); - String otherPassword = toString("otherPassword"); - String type = toString("type"); - - if (password == null) { - responseString = getLocalizedString("missing.password"); - } else if (password.length() < SetupUtils.MIN_PASSWORD_SIZE) { - responseString = getLocalizedString("password.size.invalid"); - } else if (confirm == null) { - responseString = getLocalizedString("missing.confirm.password"); - } else if (confirm.length() < SetupUtils.MIN_PASSWORD_SIZE) { - responseString = getLocalizedString("password.size.invalid"); - } else if (!password.equals(confirm)) { - responseString = getLocalizedString("password.dont.match"); - } else if ((otherPassword != null) && (otherPassword.equals(password))) { - responseString = getLocalizedString("agent.admin.password.same"); - } else { - if (type.equals("agent")) { - type = SessionAttributeNames.CONFIG_VAR_AMLDAPUSERPASSWD; - } else { - type = SetupConstants.CONFIG_VAR_ADMIN_PWD; - } - getContext().setSessionAttribute(type, password); - } - - writeToResponse(responseString); - setPath(null); - return false; - } -} diff --git a/openam-core/src/main/java/com/sun/identity/config/util/ProtectedPage.java b/openam-core/src/main/java/com/sun/identity/config/util/ProtectedPage.java deleted file mode 100644 index e20733a8fe..0000000000 --- a/openam-core/src/main/java/com/sun/identity/config/util/ProtectedPage.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * The contents of this file are subject to the terms of the Common Development and - * Distribution License (the License). You may not use this file except in compliance with the - * License. - * - * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the - * specific language governing permission and limitations under the License. - * - * When distributing Covered Software, include this CDDL Header Notice in each file and include - * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL - * Header, with the fields enclosed by brackets [] replaced by your own identifying - * information: "Portions Copyrighted [year] [name of copyright owner]". - * - * Copyright 2014 ForgeRock AS. - */ - -package com.sun.identity.config.util; - -import com.sun.identity.setup.AMSetupServlet; - -/** - * Any page that needs to be protected post OpenAM setup should extend this class, for example, all the Config Wizard - * pages. - */ -public class ProtectedPage extends AjaxPage { - - @Override - public boolean onSecurityCheck() { - - // If we have already been configured then return false to trigger the Click framework to - // not allow access to the page. - if (AMSetupServlet.isConfigured()) { - setPath(null); - return false; - } else { - return true; - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionEventDispatcher.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionEventDispatcher.java deleted file mode 100644 index 6081167fdc..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionEventDispatcher.java +++ /dev/null @@ -1,620 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedHashSet; - -import java.util.List; -import java.util.Set; - -import org.openidentityplatform.openam.click.ajax.AjaxBehavior; -import org.openidentityplatform.openam.click.service.ConfigService; -import org.openidentityplatform.openam.click.service.LogService; -import org.apache.click.util.HtmlStringBuffer; -import org.apache.commons.lang.ClassUtils; -import org.apache.commons.lang.Validate; - -/** - * Provides a control ActionListener and AjaxBehavior dispatcher. The - * ClickServlet will dispatch registered ActionListeners and AjaxBehaviors after - * page controls have been processed. - * - *

Example Usage

- * The following example shows how to register an ActionListener with a custom - * Control: - * - *
- * public class MyControl extends AbstractControl {
- *     ...
- *
- *     public boolean onProcess() {
- *         bindRequestValue();
- *
- *         if (isClicked()) {
- *             // Dispatch an action listener event for invocation after
- *             // control processing has finished
- *             dispatchActionEvent();
- *         }
- *
- *         return true;
- *     }
- * } 
- * - * When the link is clicked it invokes the method - * {@link org.openidentityplatform.openam.click.control.AbstractControl#dispatchActionEvent()}. - * This method registers the Control's action listener with the - * ActionEventDispatcher. The ClickServlet will subsequently invoke the registered - * {@link ActionListener#onAction(Control)} method after all the Page controls - * onProcess() method have been invoked. - */ -public class ActionEventDispatcher { - - // Constants -------------------------------------------------------------- - - /** The thread local dispatcher holder. */ - private static final ThreadLocal THREAD_LOCAL_DISPATCHER_STACK - = new ThreadLocal<>(); - - // Variables -------------------------------------------------------------- - - /** The list of registered event sources. */ - List eventSourceList; - - /** The list of registered event listeners. */ - List eventListenerList; - - /** The set of Controls with attached AjaxBehaviors. */ - Set ajaxBehaviorSourceSet; - - /** - * The {@link org.apache.click.ActionResult} to render. This action result is - * returned from the target Behavior. - */ - ActionResult actionResult; - - /** The application log service. */ - LogService logger; - - // Constructors ----------------------------------------------------------- - - /** - * Construct the ActionEventDispatcher with the given ConfigService. - * - * @param configService the click application configuration service - */ - public ActionEventDispatcher(ConfigService configService) { - this.logger = configService.getLogService(); - } - - // Public Methods --------------------------------------------------------- - - /** - * Register the event source and event ActionListener to be fired by the - * ClickServlet once all the controls have been processed. - * - * @param source the action event source - * @param listener the event action listener - */ - public static void dispatchActionEvent(Control source, ActionListener listener) { - Validate.notNull(source, "Null source parameter"); - Validate.notNull(listener, "Null listener parameter"); - - ActionEventDispatcher instance = getThreadLocalDispatcher(); - instance.registerActionEvent(source, listener); - } - - /** - * Register the source control which AjaxBehaviors should be fired by the - * ClickServlet. - * - * @param source the source control which behaviors should be fired - */ - public static void dispatchAjaxBehaviors(Control source) { - Validate.notNull(source, "Null source parameter"); - - ActionEventDispatcher instance = getThreadLocalDispatcher(); - instance.registerAjaxBehaviorSource(source); - } - - /** - * Return the thread local ActionEventDispatcher instance. - * - * @return the thread local ActionEventDispatcher instance. - * @throws RuntimeException if an ActionEventDispatcher is not available on - * the thread - */ - public static ActionEventDispatcher getThreadLocalDispatcher() { - return getDispatcherStack().peek(); - } - - /** - * Returns true if an ActionEventDispatcher instance is available on the - * current thread, false otherwise. - *

- * Unlike {@link #getThreadLocalDispatcher()} this method can safely be used - * and will not throw an exception if an ActionEventDispatcher is not - * available on the current thread. - * - * @return true if an ActionEventDispatcher instance is available on the - * current thread, false otherwise - */ - public static boolean hasThreadLocalDispatcher() { - DispatcherStack dispatcherStack = THREAD_LOCAL_DISPATCHER_STACK.get(); - if (dispatcherStack == null) { - return false; - } - return !dispatcherStack.isEmpty(); - } - - /** - * Fire all the registered action events after the Page Controls have been - * processed and return true if the page should continue processing. - * - * @param context the request context - * - * @return true if the page should continue processing, false otherwise - */ - public boolean fireActionEvents(Context context) { - - if (!hasActionEvents()) { - return true; - } - - return fireActionEvents(context, getEventSourceList(), getEventListenerList()); - } - - /** - * Fire all the registered AjaxBehaviors and return true if the page should - * continue processing, false otherwise. - * - * @see #fireAjaxBehaviors(Context, java.util.Set) - * - * @param context the request context - * - * @return true if the page should continue processing, false otherwise - */ - public boolean fireAjaxBehaviors(Context context) { - - if (!hasAjaxBehaviorSourceSet()) { - return true; - } - - return fireAjaxBehaviors(context, getAjaxBehaviorSourceSet()); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Allow the dispatcher to handle the error that occurred. - * - * @param throwable the error which occurred during processing - */ - protected void errorOccurred(Throwable throwable) { - // Clear the control listeners and behaviors from the dispatcher - clear(); - } - - /** - * Fire the actions for the given listener list and event source list which - * return true if the page should continue processing. - *

- * This method can be overridden if you need to customize the way events - * are fired. - * - * @param context the request context - * @param eventSourceList the list of source controls - * @param eventListenerList the list of listeners to fire - * - * @return true if the page should continue processing or false otherwise - */ - protected boolean fireActionEvents(Context context, - List eventSourceList, List eventListenerList) { - - boolean continueProcessing = true; - - for (int i = 0, size = eventSourceList.size(); i < size; i++) { - Control source = eventSourceList.remove(0); - ActionListener listener = eventListenerList.remove(0); - - if (!fireActionEvent(context, source, listener)) { - continueProcessing = false; - } - } - - return continueProcessing; - } - - /** - * Fire the action for the given listener and event source which - * return true if the page should continue processing. - *

- * This method can be overridden if you need to customize the way events - * are fired. - * - * @param context the request context - * @param source the source control - * @param listener the listener to fire - * - * @return true if the page should continue processing, false otherwise - */ - protected boolean fireActionEvent(Context context, Control source, - ActionListener listener) { - return listener.onAction(source); - } - - /** - * Fire the AjaxBehaviors for the given control set and return true if the page - * should continue processing, false otherwise. - *

- * This method can be overridden if you need to customize the way - * AjaxBehaviors are fired. - * - * @see #fireAjaxBehaviors(Context, Control) - * - * @param context the request context - * @param ajaxBbehaviorSourceSet the set of controls with attached AjaxBehaviors - * - * @return true if the page should continue processing, false otherwise - */ - protected boolean fireAjaxBehaviors(Context context, Set ajaxBbehaviorSourceSet) { - - boolean continueProcessing = true; - - for (Iterator it = ajaxBbehaviorSourceSet.iterator(); it.hasNext();) { - Control source = it.next(); - - // Pop the first entry in the set - it.remove(); - - if (!fireAjaxBehaviors(context, source)) { - continueProcessing = false; - } - } - - return continueProcessing; - } - - /** - * Fire the AjaxBehaviors for the given control and return true if the - * page should continue processing, false otherwise. AjaxBehaviors will - * only fire if their {@link org.openidentityplatform.openam.click.ajax.AjaxBehavior#isAjaxTarget(Context) isAjaxTarget()} - * method returns true. - *

- * This method can be overridden if you need to customize the way - * AjaxBehaviors are fired. - * - * @param context the request context - * @param source the control which attached behaviors should be fired - * - * @return true if the page should continue processing, false otherwise - */ - protected boolean fireAjaxBehaviors(Context context, Control source) { - - boolean continueProcessing = true; - - if (logger.isTraceEnabled()) { - String sourceClassName = ClassUtils.getShortClassName(source.getClass()); - HtmlStringBuffer buffer = new HtmlStringBuffer(); - buffer.append(" processing AjaxBehaviors for control: '"); - buffer.append(source.getName()).append("' "); - buffer.append(sourceClassName); - logger.trace(buffer.toString()); - } - - for (Behavior behavior : source.getBehaviors()) { - - if (behavior instanceof AjaxBehavior) { - AjaxBehavior ajaxBehavior = (AjaxBehavior) behavior; - - boolean isAjaxTarget = ajaxBehavior.isAjaxTarget(context); - - if (logger.isTraceEnabled()) { - String behaviorClassName = ClassUtils.getShortClassName( - ajaxBehavior.getClass()); - HtmlStringBuffer buffer = new HtmlStringBuffer(); - buffer.append(" invoked: "); - buffer.append(behaviorClassName); - buffer.append(".isAjaxTarget() : "); - buffer.append(isAjaxTarget); - logger.trace(buffer.toString()); - } - - if (isAjaxTarget) { - - // The first non-null ActionResult returned will be rendered, other - // ActionResult instances are ignored - ActionResult behaviorActionResult = - ajaxBehavior.onAction(source); - if (actionResult == null && behaviorActionResult != null) { - actionResult = behaviorActionResult; - } - - if (logger.isTraceEnabled()) { - String behaviorClassName = ClassUtils.getShortClassName( - ajaxBehavior.getClass()); - String actionResultClassName = null; - - if (behaviorActionResult != null) { - actionResultClassName = ClassUtils.getShortClassName( - behaviorActionResult.getClass()); - } - - HtmlStringBuffer buffer = new HtmlStringBuffer(); - buffer.append(" invoked: "); - buffer.append(behaviorClassName); - buffer.append(".onAction() : "); - buffer.append(actionResultClassName); - - if (actionResult == behaviorActionResult - && behaviorActionResult != null) { - buffer.append(" (ActionResult will be rendered)"); - } else { - if (behaviorActionResult == null) { - buffer.append(" (ActionResult is null and will be ignored)"); - } else { - buffer.append(" (ActionResult will be ignored since another AjaxBehavior already retuned a non-null ActionResult)"); - } - } - - logger.trace(buffer.toString()); - } - - continueProcessing = false; - break; - } - } - } - - if (logger.isTraceEnabled()) { - - // continueProcessing is true if no AjaxBehavior was the target - // of the request - if (continueProcessing) { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - String sourceClassName = ClassUtils.getShortClassName( - source.getClass()); - buffer.append(" *no* target AjaxBehavior found for '"); - buffer.append(source.getName()).append("' "); - buffer.append(sourceClassName); - buffer.append(" - invoking AjaxBehavior.isAjaxTarget() returned false for all AjaxBehaviors"); - logger.trace(buffer.toString()); - } - } - - // Ajax requests stops further processing - return continueProcessing; - } - - // Package Private Methods ------------------------------------------------ - - /** - * Register the event source and event ActionListener. - * - * @param source the action event source - * @param listener the event action listener - */ - void registerActionEvent(Control source, ActionListener listener) { - Validate.notNull(source, "Null source parameter"); - Validate.notNull(listener, "Null listener parameter"); - - getEventSourceList().add(source); - getEventListenerList().add(listener); - } - - /** - * Register the AjaxBehavior source control. - * - * @param source the AjaxBehavior source control - */ - void registerAjaxBehaviorSource(Control source) { - Validate.notNull(source, "Null source parameter"); - - getAjaxBehaviorSourceSet().add(source); - } - - /** - * Checks if any Action Events have been registered. - * - * @return true if the dispatcher has any Action Events registered - */ - boolean hasActionEvents() { - if (eventListenerList == null || eventListenerList.isEmpty()) { - return false; - } - return true; - } - - /** - * Return the list of event listeners. - * - * @return list of event listeners - */ - List getEventListenerList() { - if (eventListenerList == null) { - eventListenerList = new ArrayList(); - } - return eventListenerList; - } - - /** - * Return the list of event sources. - * - * @return list of event sources - */ - List getEventSourceList() { - if (eventSourceList == null) { - eventSourceList = new ArrayList(); - } - return eventSourceList; - } - - /** - * Clear the events and behaviors. - */ - void clear() { - if (hasActionEvents()) { - getEventSourceList().clear(); - getEventListenerList().clear(); - } - - if (hasAjaxBehaviorSourceSet()) { - getAjaxBehaviorSourceSet().clear(); - } - } - - /** - * Return the Behavior's action result or null if no behavior was dispatched. - * - * @return the Behavior's action result or null if no behavior was dispatched - */ - ActionResult getActionResult() { - return actionResult; - } - - /** - * Return true if a control with AjaxBehaviors was registered, false otherwise. - * - * @return true if a control with AjaxBehaviors was registered, false otherwise. - */ - boolean hasAjaxBehaviorSourceSet() { - if (ajaxBehaviorSourceSet == null || ajaxBehaviorSourceSet.isEmpty()) { - return false; - } - return true; - } - - /** - * Return the set of controls with attached AjaxBehaviors. - * - * @return set of control with attached AjaxBehaviors - */ - Set getAjaxBehaviorSourceSet() { - if (ajaxBehaviorSourceSet == null) { - ajaxBehaviorSourceSet = new LinkedHashSet(); - } - return ajaxBehaviorSourceSet; - } - - /** - * Adds the specified ActionEventDispatcher on top of the dispatcher stack. - * - * @param actionEventDispatcher the ActionEventDispatcher to add - */ - static void pushThreadLocalDispatcher(ActionEventDispatcher actionEventDispatcher) { - getDispatcherStack().push(actionEventDispatcher); - } - - /** - * Remove and return the actionEventDispatcher instance on top of the - * dispatcher stack. - * - * @return the actionEventDispatcher instance on top of the dispatcher stack - */ - static ActionEventDispatcher popThreadLocalDispatcher() { - DispatcherStack dispatcherStack = getDispatcherStack(); - ActionEventDispatcher actionEventDispatcher = dispatcherStack.pop(); - - if (dispatcherStack.isEmpty()) { - THREAD_LOCAL_DISPATCHER_STACK.set(null); - } - - return actionEventDispatcher; - } - - /** - * Return the stack data structure where ActionEventDispatchers are stored. - * - * @return stack data structure where ActionEventDispatcher are stored - */ - static ActionEventDispatcher.DispatcherStack getDispatcherStack() { - DispatcherStack dispatcherStack = THREAD_LOCAL_DISPATCHER_STACK.get(); - - if (dispatcherStack == null) { - dispatcherStack = new ActionEventDispatcher.DispatcherStack(2); - THREAD_LOCAL_DISPATCHER_STACK.set(dispatcherStack); - } - - return dispatcherStack; - } - - // Inner Classes ---------------------------------------------------------- - - /** - * Provides an unsynchronized Stack. - */ - static class DispatcherStack extends ArrayList { - - /** Serialization version indicator. */ - private static final long serialVersionUID = 1L; - - /** - * Create a new DispatcherStack with the given initial capacity. - * - * @param initialCapacity specify initial capacity of this stack - */ - private DispatcherStack(int initialCapacity) { - super(initialCapacity); - } - - /** - * Pushes the ActionEventDispatcher onto the top of this stack. - * - * @param actionEventDispatcher the ActionEventDispatcher to push onto this stack - * @return the ActionEventDispatcher pushed on this stack - */ - private ActionEventDispatcher push(ActionEventDispatcher actionEventDispatcher) { - add(actionEventDispatcher); - - return actionEventDispatcher; - } - - /** - * Removes and return the ActionEventDispatcher at the top of this stack. - * - * @return the ActionEventDispatcher at the top of this stack - */ - private ActionEventDispatcher pop() { - ActionEventDispatcher actionEventDispatcher = peek(); - - remove(size() - 1); - - return actionEventDispatcher; - } - - /** - * Looks at the ActionEventDispatcher at the top of this stack without - * removing it. - * - * @return the ActionEventDispatcher at the top of this stack - */ - private ActionEventDispatcher peek() { - int length = size(); - - if (length == 0) { - String msg = "No ActionEventDispatcher available on ThreadLocal Dispatcher Stack"; - throw new RuntimeException(msg); - } - - return get(length - 1); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionListener.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionListener.java deleted file mode 100644 index a7ca79b482..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionListener.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.Serializable; - -/** - * Provides a listener interface for receiving control action events. - * The usage model is similar to the java.awt.event.ActionListener - * interface. - *

- * The class that is interested in processing an action event - * implements this interface, and the object created with that class is - * registered with a control, using the control's setActionListener - * method. When the action event occurs, that object's onAction method - * is invoked. - * - *

Listener Example

- * - * An ActionListener example is provided below: - * - *
- * public MyPage extends Page {
- *
- *    public ActionLink link = new ActionLink();
- *
- *    public MyPage() {
- *
- *       link.setActionListener(new ActionListener() {
- *           public boolean onAction(Control source) {
- *               return onLinkClick();
- *           }
- *        });
- *    }
- *
- *    public boolean onLinkClick() {
- *       ..
- *       return true;
- *    }
- * }
- * 
- */ -public interface ActionListener extends Serializable { - - /** - * Return true if the control and page processing should continue, or false - * otherwise. - * - * @param source the source of the action event - * @return true if control and page processing should continue or false - * otherwise. - */ - public boolean onAction(Control source); - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionResult.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionResult.java deleted file mode 100644 index 60ac4b9c70..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ActionResult.java +++ /dev/null @@ -1,580 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Reader; -import java.io.StringReader; -import java.io.Writer; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import jakarta.servlet.http.HttpServletResponse; - -import org.openidentityplatform.openam.click.util.ClickUtils; - -/** - * Provides an ActionResult that is returned by Page Actions and AjaxBehaviors. - * ActionResults are often used to return a partial response to the browser - * instead of the full page content. - *

- * An ActionResult can consist of a String (HTML, JSON, XML, plain text) or a byte - * array (jpg, gif, png, pdf or excel documents). The ActionResult {@link #contentType} - * must be set appropriately in order for the browser to recognize the action result. - *

- * ActionResults are returned by {@link org.apache.click.ajax.AjaxBehavior Ajax Behaviors} - * and Page Action methods. - * - *

Ajax Behavior

- * - * Ajax requests are handled by adding an {@link org.apache.click.ajax.AjaxBehavior Ajax Behavior} - * to a control. The AjaxBehavior {@link org.apache.click.ajax.AjaxBehavior#onAction(org.apache.click.Control) onAction} - * method will handle the request and return a ActionResult instance that contains - * the response, thus bypassing the rendering of the Page template. For example: - * - *
- * private ActionLink link = new ActionLink("link");
- *
- * public void onInit() {
- *     addControl(link);
- *
- *     link.addBehavior(new AjaxBehavior() {
- *
- *         // The onAction method must return a ActionResult
- *         public ActionResult onAction(Control source) {
- *             // Create a new action result containing an HTML snippet and HTML content type
- *             ActionResult actionResult = new ActionResult("<span>Hello World</span>", ActionResult.HTML);
- *             return actionResult;
- *         }
- *     });
- * } 
- * - *

Page Action

- * - * A Page Action is a method on a Page that can be invoked directly - * from the browser. The Page Action method returns an ActionResult instance that - * is rendered to the browser, thus bypassing the rendering of the Page template. - * - *
- * private ActionLink link = new ActionLink("link");
- *
- * public void onInit() {
- *     link.addControl(link);
- *
- *     // A "pageAction" is set as a parameter on the link. The "pageAction"
- *     // value is set to the Page method: "renderHelloWorld"
- *     link.setParameter(PAGE_ACTION, "renderHelloWorld");
- * }
- *
- * /**
- *  * This is a "pageAction" method that will render an HTML response.
- *  *
- *  * Note the signature of the pageAction: a public, no-argument method
- *  * returning a ActionResult instance.
- *  */
- * public ActionResult renderHelloWorld() {
- *     ActionResult actionResult = new ActionResult("<span>Hello World</span>", ActionResult.HTML);
- *     return actionResult;
- * } 
- * - *

Content Type

- * - * The {@link #contentType} of the ActionResult must be set to the appropriate type - * in order for the client to recognize the response. ActionResult provides constants - * for some of the common content types, including: {@link #XML text/xml}, - * {@link #HTML text/html}, {@link #JSON application/json}, {@link #TEXT text/plain}. - *

- * For example: - *

- * ActionResult actionResult = new ActionResult("alert('hello world');", ActionResult.JAVASCRIPT);
- *
- * ...
- *
- * // content type can also be set through the setContentType method
- * actionResult.setContentType(ActionResult.JAVASCRIPT);
- *
- * ...
- * 
- * - * More content types can be retrieved through {@link org.apache.click.util.ClickUtils#getMimeType(java.lang.String)}: - *
- * // lookup content type for PNG
- * String contentType = ClickUtils.getMimeType("png");
- * actionResult.setContentType(contentType); 
- */ -public class ActionResult { - - // Constants -------------------------------------------------------------- - - /** The plain text content type constant: text/plain. */ - public static final String TEXT = "text/plain"; - - /** The html content type constant: text/html. */ - public static final String HTML = "text/html"; - - /** The The xhtml content type constant: application/xhtml+xml. */ - public static final String XHTML = "application/xhtml+xml"; - - /** The json content type constant: text/json. */ - public static final String JSON = "application/json"; - - /** The javascript content type constant: text/javascript. */ - public static final String JAVASCRIPT = "text/javascript"; - - /** The xml content type constant: text/xml. */ - public static final String XML = "text/xml"; - - /** The ActionResult writer buffer size. */ - private static final int WRITER_BUFFER_SIZE = 256; // For text, set a small response size - - /** The ActionResult output buffer size. */ - private static final int OUTPUT_BUFFER_SIZE = 4 * 1024; // For binary, set a a large response size - - // Variables -------------------------------------------------------------- - - /** The content to render. */ - private String content; - - /** The content as a byte array. */ - private byte[] bytes; - - /** The servlet response reader. */ - private Reader reader; - - /** The servlet response input stream. */ - private InputStream inputStream; - - /** The response content type. */ - private String contentType; - - /** The response character encoding. */ - private String characterEncoding; - - /** Indicates whether the ActionResult should be cached by browser. */ - private boolean cacheActionResult = false; - - /** The path of the actionResult template to render. */ - private String template; - - /** The model for the ActionResult {@link #template}. */ - private Map model; - - // Constructors ----------------------------------------------------------- - - /** - * Construct the ActionResult for the given template and model. - *

- * When the ActionResult is rendered the template and model will be merged and - * the result will be streamed back to the client. - *

- * For example: - *

-     * public class MyPage extends Page {
-     *     public void onInit() {
-     *
-     *         Behavior behavior = new DefaultAjaxBehavior() {
-     *
-     *             public ActionResult onAction() {
-     *
-     *                 Map model = new HashMap();
-     *                 model.put("id", "link");
-     *
-     *                 // Note: we set XML as the content type
-     *                 ActionResult actionResult = new ActionResult("/js/actionResult.xml", model, ActionResult.XML);
-     *
-     *                 return actionResult;
-     *             }
-     *         }
-     *     }
-     * } 
- * - * @param template the template to render and stream back to the client - * @param model the template data model - * @param contentType the response content type - */ - public ActionResult(String template, Map model, String contentType) { - this.template = template; - this.model = model; - this.contentType = contentType; - } - - /** - * Construct the ActionResult for the given reader and content type. - * - * @param reader the reader which characters must be streamed back to the - * client - * @param contentType the response content type - */ - public ActionResult(Reader reader, String contentType) { - this.reader = reader; - this.contentType = contentType; - } - - /** - * Construct the ActionResult for the given inputStream and content type. - * - * @param inputStream the input stream to stream back to the client - * @param contentType the response content type - */ - public ActionResult(InputStream inputStream, String contentType) { - this.inputStream = inputStream; - this.contentType = contentType; - } - - /** - * Construct the ActionResult for the given String content and content type. - * - * @param content the String content to stream back to the client - * @param contentType the response content type - */ - public ActionResult(String content, String contentType) { - this.content = content; - this.contentType = contentType; - } - - /** - * Construct the ActionResult for the given byte array and content type. - * - * @param bytes the byte array to stream back to the client - * @param contentType the response content type - */ - public ActionResult(byte[] bytes, String contentType) { - this.bytes = bytes; - this.contentType = contentType; - } - - /** - * Construct the ActionResult for the given content. The - * {@link jakarta.servlet.http.HttpServletResponse#setContentType(java.lang.String) response content type} - * will default to {@link #TEXT}, unless overridden. - * - * @param content the content to stream back to the client - */ - public ActionResult(String content) { - this.content = content; - } - - /** - * Construct a new empty ActionResult. The - * {@link jakarta.servlet.http.HttpServletResponse#setContentType(java.lang.String) response content type} - * will default to {@link #TEXT}, unless overridden. - */ - public ActionResult() { - } - - // Public Methods --------------------------------------------------------- - - /** - * Set whether the action result should be cached by the client browser or - * not. - *

- * If false, Click will set the following headers to prevent browsers - * from caching the result: - *

-     * response.setHeader("Pragma", "no-cache");
-     * response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
-     * response.setDateHeader("Expires", new Date(1L).getTime()); 
- * - * @param cacheActionResult indicates whether the action result should be cached - * by the client browser or not - */ - public void setCacheActionResult(boolean cacheActionResult) { - this.cacheActionResult = cacheActionResult; - } - - /** - * Return true if the action result should be cached by the client browser, - * defaults to false. It is highly unlikely that you would turn action result - * caching on. - * - * @return true if the action result should be cached by the client browser, - * false otherwise - */ - public boolean isCacheActionRestul() { - return cacheActionResult; - } - - /** - * Return the action result character encoding. If no character encoding is specified - * the request character encoding will be used. - * - * @return the action result character encoding - */ - public String getCharacterEncoding() { - return characterEncoding; - } - - /** - * Set the action result character encoding. If no character encoding is set the - * request character encoding will be used. - * - * @param characterEncoding the action result character encoding - */ - public void setCharacterEncoding(String characterEncoding) { - this.characterEncoding = characterEncoding; - } - - /** - * Set the action result response content type. If no content type is set it will - * default to {@value #TEXT}. - * - * @param contentType the action result response content type - */ - public void setContentType(String contentType) { - this.contentType = contentType; - } - - /** - * Return the action result content type, default is {@value #TEXT}. - * - * @return the response content type - */ - public String getContentType() { - if (contentType == null) { - contentType = TEXT; - } - return contentType; - } - - /** - * Set the content String to stream back to the client. - * - * @param content the content String to stream back to the client - */ - public void setContent(String content) { - this.content = content; - } - - /** - * Return the content String to stream back to the client. - * - * @return the content String to stream back to the client - */ - public String getContent() { - return content; - } - - /** - * Set the byte array to stream back to the client. - * - * @param bytes the byte array to stream back to the client - */ - public void setBytes(byte[] bytes, String contentType) { - this.bytes = bytes; - this.contentType = contentType; - } - - /** - * Return the byte array to stream back to the client. - * - * @return the byte array to stream back to the client - */ - public byte[] getBytes() { - return bytes; - } - - /** - * Set the content to stream back to the client. - * - * @param inputStream the inputStream to stream back to the client - */ - public void setInputStream(InputStream inputStream) { - this.inputStream = inputStream; - } - - /** - * Return the inputStream to stream back to the client. - * - * @return the inputStream to stream back to the client - */ - public InputStream getInputStream() { - return inputStream; - } - - /** - * Set the reader which characters are streamed back to the client. - * - * @param reader the reader which characters are streamed back to the client. - */ - public void setReader(Reader reader) { - this.reader = reader; - } - - /** - * Return the reader which characters are streamed back to the client. - * - * @return the reader which characters are streamed back to the client. - */ - public Reader getReader() { - return reader; - } - - /** - * Return the data model for the ActionResult {@link #template}. - * - * @return the data model for the ActionResult template - */ - public Map getModel() { - if (model == null) { - model = new HashMap(); - } - return model; - } - - /** - * Set the model of the ActionResult template to render. - *

- * If the {@link #template} property is set, the template and {@link #model} - * will be merged and the result will be streamed back to the client. - * - * @param model the model of the template to render - */ - public void setModel(Map model) { - this.model = model; - } - - /** - * Return the template to render for this ActionResult. - * - * @return the template to render for this ActionResult - */ - public String getTemplate() { - return template; - } - - /** - * Set the template to render for this ActionResult. - * - * @param template the template to render for this ActionResult - */ - public void setTemplate(String template) { - this.template = template; - } - - /** - * Render the ActionResult to the client. - * - * @param context the request context - */ - public final void render(Context context) { - prepare(context); - renderActionResult(context); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Render the ActionResult to the client. This method can be overridden - * by subclasses if custom rendering or direct access to the - * HttpServletResponse is required. - * - * @param context the request context - */ - protected void renderActionResult(Context context) { - - HttpServletResponse response = context.getResponse(); - - Reader localReader = getReader(); - InputStream localInputStream = getInputStream(); - - try { - String localContent = getContent(); - byte[] localBytes = getBytes(); - - String localTemplate = getTemplate(); - if (localTemplate != null) { - Map templateModel = getModel(); - if (templateModel == null) { - templateModel = new HashMap(); - } - String result = context.renderTemplate(localTemplate, templateModel); - localReader = new StringReader(result); - - } else if (localContent != null) { - localReader = new StringReader(localContent); - } else if (localBytes != null) { - localInputStream = new ByteArrayInputStream(localBytes); - } - - if (localReader != null) { - Writer writer = response.getWriter(); - char[] buffer = new char[WRITER_BUFFER_SIZE]; - int len = 0; - while (-1 != (len = localReader.read(buffer))) { - writer.write(buffer, 0, len); - } - writer.flush(); - writer.close(); - - } else if (localInputStream != null) { - byte[] buffer = new byte[OUTPUT_BUFFER_SIZE]; - int len = 0; - OutputStream outputStream = response.getOutputStream(); - while (-1 != (len = localInputStream.read(buffer))) { - outputStream.write(buffer, 0, len); - } - outputStream.flush(); - outputStream.close(); - } - - } catch (Exception e) { - throw new RuntimeException(e); - - } finally { - ClickUtils.close(localInputStream); - ClickUtils.close(localReader); - } - } - - // Private Methods -------------------------------------------------------- - - /** - * Prepare the ActionResult for rendering. - * - * @param context the request context - */ - private void prepare(Context context) { - HttpServletResponse response = context.getResponse(); - - if (!isCacheActionRestul()) { - // Set headers to disable cache - response.setHeader("Pragma", "no-cache"); - response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0"); - response.setDateHeader("Expires", new Date(1L).getTime()); - } - - String localContentType = getContentType(); - - if (getCharacterEncoding() == null) { - - // Fallback to request character encoding - if (context.getRequest().getCharacterEncoding() != null) { - response.setContentType(localContentType + "; charset=" - + context.getRequest().getCharacterEncoding()); - } else { - response.setContentType(localContentType); - } - - } else { - response.setContentType(localContentType + "; charset=" + getCharacterEncoding()); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/Behavior.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/Behavior.java deleted file mode 100644 index 58ac8527e0..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/Behavior.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -/** - * Behaviors provide a mechanism for changing how Controls behave at runtime. - * Behaviors are added to a Control and provides interceptor methods to decorate - * and enhance the source Control. - *

- * Behaviors provide interceptor methods for specific Control life cycle events. - * These interceptor methods can be implemented to further process and decorate - * the control or its children. - *

- * The following interceptor methods are defined: - * - *

    - *
  • preResponse - occurs before the control markup is written to the response
  • - *
  • preRenderHeadElements - occurs after preResponse but before the control - * {@link Control#getHeadElements() HEAD elements} are written to the response
  • - *
  • preDestroy - occurs before the Control {@link Control#onDestroy() onDestroy} - * event handler.
  • - *
- * - * These interceptor methods allow the Behavior to decorate a control, - * for example: - * - *
    - *
  • add or remove Control HEAD elements such as JavaScript and CSS dependencies - * and setup scripts
  • - *
  • add or remove Control attributes such as "class", "style" etc.
  • - *
- */ -public interface Behavior { - - /** - * This event occurs before the markup is written to the HttpServletResponse. - * - * @param source the control the behavior is registered with - */ - public void preResponse(Control source); - - /** - * This event occurs after {@link #preResponse(Control)}, - * but before the Control's {@link Control#getHeadElements()} is called. - * - * @param source the control the behavior is registered with - */ - public void preRenderHeadElements(Control source); - - /** - * This event occurs before the Control {@link Control#onDestroy() onDestroy} - * event handler. This event allows the behavior to cleanup or store Control - * state in the Session. - * - * @param source the control the behavior is registered with - */ - public void preDestroy(Control source); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickRequestWrapper.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickRequestWrapper.java deleted file mode 100644 index 482c832c77..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickRequestWrapper.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; - -import org.openidentityplatform.openam.click.service.FileUploadService; -import org.openidentityplatform.openam.click.util.ClickUtils; - -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileUploadException; - -/** - * Provides a custom HttpServletRequest class for shielding users from - * multipart request parameters. Thus calling request.getParameter(String) - * will still work properly. - */ -class ClickRequestWrapper extends HttpServletRequestWrapper { - - /** - * The FileItem objects for "multipart" POST requests. - */ - private final Map fileItemMap; - - /** The request is a multi-part file upload POST request. */ - private final boolean isMultipartRequest; - - /** The map of "multipart" request parameter values. */ - private final Map multipartParameterMap; - - /** The wrapped servlet request. */ - private final HttpServletRequest request; - - // Constructors ----------------------------------------------------------- - - /** - * @see HttpServletRequestWrapper(HttpServletRequest) - */ - ClickRequestWrapper(final HttpServletRequest request, - final FileUploadService fileUploadService) { - super(request); - - this.isMultipartRequest = ClickUtils.isMultipartRequest(request); - this.request = request; - - if (isMultipartRequest) { - - Map requestParams = new HashMap(); - Map fileItems = new HashMap(); - - try { - List itemsList = new ArrayList(); - - try { - - itemsList = fileUploadService.parseRequest(request); - - } catch (FileUploadException fue) { - request.setAttribute(FileUploadService.UPLOAD_EXCEPTION, fue); - } - - for (FileItem fileItem : itemsList) { - String name = fileItem.getFieldName(); - String value = null; - - // Form fields are placed in the request parameter map, - // while file uploads are placed in the file item map. - if (fileItem.isFormField()) { - - if (request.getCharacterEncoding() == null) { - value = fileItem.getString(); - - } else { - try { - value = fileItem.getString(request.getCharacterEncoding()); - - } catch (UnsupportedEncodingException ex) { - throw new RuntimeException(ex); - } - } - - // Add the form field value to the parameters. - addToMapAsString(requestParams, name, value); - - } else { - // Add the file item to the list of file items. - addToMapAsFileItem(fileItems, name, fileItem); - } - } - - } catch (Throwable t) { - - // Don't throw error here as it will break Context creation. - // Instead add the error as a request attribute. - request.setAttribute(Context.CONTEXT_FATAL_ERROR, t); - - } finally { - fileItemMap = Collections.unmodifiableMap(fileItems); - multipartParameterMap = Collections.unmodifiableMap(requestParams); - } - - } else { - fileItemMap = Collections.emptyMap(); - multipartParameterMap = Collections.emptyMap(); - } - } - - // Public Methods --------------------------------------------------------- - - /** - * Returns a map of FileItem arrays keyed on request parameter - * name for "multipart" POST requests (file uploads). Thus each map entry - * will consist of one or more FileItem objects. - * - * @return map of FileItem arrays keyed on request parameter name - * for "multipart" POST requests - */ - public Map getFileItemMap() { - return fileItemMap; - } - - /** - * @see jakarta.servlet.ServletRequest#getParameter(String) - */ - @Override - public String getParameter(String name) { - if (isMultipartRequest) { - Object value = getMultipartParameterMap().get(name); - - if (value instanceof String) { - return (String) value; - } - - if (value instanceof String[]) { - String[] array = (String[]) value; - if (array.length >= 1) { - return array[0]; - } else { - return null; - } - } - - return (value == null ? null : value.toString()); - - } else { - return request.getParameter(name); - } - } - - /** - * @see jakarta.servlet.ServletRequest#getParameterNames() - */ - @Override - @SuppressWarnings("unchecked") - public Enumeration getParameterNames() { - if (isMultipartRequest) { - return Collections.enumeration(getMultipartParameterMap().keySet()); - - } else { - return request.getParameterNames(); - } - } - - /** - * @see jakarta.servlet.ServletRequest#getParameterValues(String) - */ - @Override - public String[] getParameterValues(String name) { - if (isMultipartRequest) { - Object values = getMultipartParameterMap().get(name); - if (values instanceof String) { - return new String[] { values.toString() }; - } - if (values instanceof String[]) { - return (String[]) values; - } else { - return null; - } - - } else { - return request.getParameterValues(name); - } - } - - /** - * @see jakarta.servlet.ServletRequest#getParameterMap() - */ - @Override - @SuppressWarnings("unchecked") - public Map getParameterMap() { - if (isMultipartRequest) { - return getMultipartParameterMap(); - } else { - return request.getParameterMap(); - } - } - - // Package Private Methods ------------------------------------------------ - - /** - * Return the map of "multipart" request parameter map. - * - * @return the "multipart" request parameter map - */ - @SuppressWarnings("unchecked") - Map getMultipartParameterMap() { - if (request.getAttribute(ClickServlet.MOCK_MODE_ENABLED) == null) { - return multipartParameterMap; - } else { - // In mock mode return the request parameter map. This ensures - // calling request.setParameter(x,y) works for both normal and - // multipart requests. - return request.getParameterMap(); - } - } - - // Private Methods -------------------------------------------------------- - - /** - * Stores the specified value in a FileItem array in the map, under the - * specified name. Thus two values stored under the same name will be - * stored in the same array. - * - * @param map the map to add the specified name and value to - * @param name the name of the map key - * @param value the value to add to the FileItem array - */ - private void addToMapAsFileItem(Map map, String name, FileItem value) { - FileItem[] oldValues = map.get(name); - FileItem[] newValues = null; - if (oldValues == null) { - newValues = new FileItem[] {value}; - } else { - newValues = new FileItem[oldValues.length + 1]; - System.arraycopy(oldValues, 0, newValues, 0, oldValues.length); - newValues[oldValues.length] = value; - } - map.put(name, newValues); - } - - /** - * Stores the specified value in an String array in the map, under the - * specified name. Thus two values stored under the same name will be - * stored in the same array. - * - * @param map the map to add the specified name and value to - * @param name the name of the map key - * @param value the value to add to the string array - */ - private void addToMapAsString(Map map, String name, String value) { - String[] oldValues = map.get(name); - String[] newValues = null; - if (oldValues == null) { - newValues = new String[] {value}; - } else { - newValues = new String[oldValues.length + 1]; - System.arraycopy(oldValues, 0, newValues, 0, oldValues.length); - newValues[oldValues.length] = value; - } - map.put(name, newValues); - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickServlet.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickServlet.java deleted file mode 100644 index e8ff6b2fe8..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ClickServlet.java +++ /dev/null @@ -1,2305 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Writer; -import java.lang.reflect.Field; -import java.util.Collections; -import java.util.Date; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import jakarta.servlet.RequestDispatcher; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.UnavailableException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; - -import ognl.DefaultMemberAccess; -import ognl.MemberAccess; -import ognl.Ognl; -import ognl.OgnlException; -import ognl.TypeConverter; - -import org.openidentityplatform.openam.click.service.ConfigService; -import org.openidentityplatform.openam.click.service.LogService; -import org.openidentityplatform.openam.click.service.ResourceService; -import org.apache.click.service.TemplateException; -import org.openidentityplatform.openam.click.service.XmlConfigService; -import org.openidentityplatform.openam.click.service.ConfigService.AutoBinding; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.ErrorPage; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.openidentityplatform.openam.click.util.PageImports; -import org.apache.click.util.PropertyUtils; -import org.apache.click.util.RequestTypeConverter; -import org.apache.commons.lang.ClassUtils; -import org.apache.commons.lang.StringUtils; - -/** - * Provides the Click application HttpServlet. - *

- * Generally developers will simply configure the ClickServlet and - * will not use it directly in their code. For a Click web application to - * function the ClickServlet must be configured in the web - * application's /WEB-INF/web.xml file. A simple web application which - * maps all *.htm requests to a ClickServlet is provided below. - * - *

- * <web-app>
- *    <servlet>
- *       <servlet-name>click-servlet</servlet-name>
- *       <servlet-class>org.apache.click.ClickServlet</servlet-class>
- *       <load-on-startup>0</load-on-startup>
- *    </servlet>
- *    <servlet-mapping>
- *       <servlet-name>click-servlet</servlet-name>
- *       <url-pattern>*.htm</url-pattern>
- *    </servlet-mapping>
- * </web-app> 
- * - * By default the ClickServlet will attempt to load an application - * configuration file using the path:   /WEB-INF/click.xml - * - *

Servlet Mapping

- * By convention all Click page templates should have a .htm extension, and - * the ClickServlet should be mapped to process all *.htm URL requests. With - * this convention you have all the static HTML pages use a .html extension - * and they will not be processed as Click pages. - * - *

Load On Startup

- * Note you should always set load-on-startup element to be 0 so the - * servlet is initialized when the server is started. This will prevent any - * delay for the first client which uses the application. - *

- * The ClickServlet performs as much work as possible at startup to - * improve performance later on. The Click start up and caching strategy is - * configured with the Click application mode in the "click.xml" file. - * See the User Guide for information on how to configure the application mode. - * - *

ConfigService

- * - * A single application {@link ConfigService} instance is created by the ClickServlet at - * startup. Once the ConfigService has been initialized it is stored in the - * ServletContext using the key {@value org.apache.click.service.ConfigService#CONTEXT_NAME}. - */ -public class ClickServlet extends HttpServlet { - - // -------------------------------------------------------------- Constants - - private static final long serialVersionUID = 1L; - - /** - * The mock page reference request attribute: key:   - * mock_page_reference. - *

- * This attribute stores the each Page instance as a request attribute. - *

- * Note: a page is only stored as a request attribute - * if the {@link #MOCK_MODE_ENABLED} attribute is set. - */ - static final String MOCK_PAGE_REFERENCE = "mock_page_reference"; - - /** - * The mock mode request attribute: key:   - * mock_mode_enabled. - *

- * If this attribute is set (the value does not matter) certain features - * will be enabled which is needed for running Click in a mock environment. - */ - static final String MOCK_MODE_ENABLED = "mock_mode_enabled"; - - /** - * The click application configuration service classname init parameter name: - *   "config-service-class". - */ - protected final static String CONFIG_SERVICE_CLASS = "config-service-class"; - - /** - * The custom TypeConverter classname as an init parameter name: - * &nbps; "type-converter-class". - */ - protected final static String TYPE_CONVERTER_CLASS = "type-converter-class"; - - /** - * The forwarded request marker attribute:   "click-forward". - */ - protected final static String CLICK_FORWARD = "click-forward"; - - /** - * The Page to forward to request attribute:   "click-page". - */ - protected final static String FORWARD_PAGE = "forward-page"; - - // ----------------------------------------------------- Instance Variables - - /** The click application configuration service. */ - protected ConfigService configService; - - /** The application log service. */ - protected LogService logger; - - /** The OGNL member access handler. */ - protected MemberAccess memberAccess; - - /** The application resource service. */ - protected ResourceService resourceService; - - /** The request parameters OGNL type converter. */ - protected TypeConverter typeConverter; - - /** The thread local page listeners. */ - private static final ThreadLocal> - THREAD_LOCAL_INTERCEPTORS = new ThreadLocal<>(); - - // --------------------------------------------------------- Public Methods - - /** - * Initialize the Click servlet and the Velocity runtime. - * - * @see jakarta.servlet.GenericServlet#init() - * - * @throws ServletException if the application configuration service could - * not be initialized - */ - @Override - public void init() throws ServletException { - - try { - - // Create and initialize the application config service - configService = createConfigService(getServletContext()); - initConfigService(getServletContext()); - logger = configService.getLogService(); - - if (logger.isInfoEnabled()) { - logger.info("Click " + ClickUtils.getClickVersion() - + " initialized in " + configService.getApplicationMode() - + " mode"); - } - - resourceService = configService.getResourceService(); - - } catch (Throwable e) { - // In mock mode this exception can occur if click.xml is not - // available. - if (getServletContext().getAttribute(MOCK_MODE_ENABLED) != null) { - return; - } - - e.printStackTrace(); - - String msg = "error while initializing Click servlet; throwing " - + "jakarta.servlet.UnavailableException"; - - log(msg, e); - - throw new UnavailableException(e.toString()); - } - } - - /** - * @see jakarta.servlet.GenericServlet#destroy() - */ - @Override - public void destroy() { - - try { - - // Destroy the application config service - destroyConfigService(getServletContext()); - - } catch (Throwable e) { - // In mock mode this exception can occur if click.xml is not - // available. - if (getServletContext().getAttribute(MOCK_MODE_ENABLED) != null) { - return; - } - - e.printStackTrace(); - - String msg = "error while destroying Click servlet, throwing " - + "jakarta.servlet.UnavailableException"; - - log(msg, e); - - } finally { - // Dereference the application config service - configService = null; - } - - super.destroy(); - } - - // ------------------------------------------------------ Protected Methods - - /** - * Handle HTTP GET requests. This method will delegate the request to - * {@link #handleRequest(HttpServletRequest, HttpServletResponse, boolean)}. - * - * @see HttpServlet#doGet(HttpServletRequest, HttpServletResponse) - * - * @param request the servlet request - * @param response the servlet response - * @throws ServletException if click app has not been initialized - * @throws IOException if an I/O error occurs - */ - @Override - protected void doGet(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - - handleRequest(request, response, false); - } - - /** - * Handle HTTP POST requests. This method will delegate the request to - * {@link #handleRequest(HttpServletRequest, HttpServletResponse, boolean)}. - * - * @see HttpServlet#doPost(HttpServletRequest, HttpServletResponse) - * - * @param request the servlet request - * @param response the servlet response - * @throws ServletException if click app has not been initialized - * @throws IOException if an I/O error occurs - */ - @Override - protected void doPost(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - - handleRequest(request, response, true); - } - - /** - * Handle the given servlet request and render the results to the - * servlet response. - *

- * If an exception occurs within this method the exception will be delegated - * to: - *

- * {@link #handleException(HttpServletRequest, HttpServletResponse, boolean, Throwable, Class)} - * - * @param request the servlet request to process - * @param response the servlet response to render the results to - * @param isPost determines whether the request is a POST - * @throws IOException if resource request could not be served - */ - protected void handleRequest(HttpServletRequest request, - HttpServletResponse response, boolean isPost) throws IOException { - - // Handle requests for click resources, i.e. CSS, JS and image files - if (resourceService.isResourceRequest(request)) { - resourceService.renderResource(request, response); - return; - } - - long startTime = System.currentTimeMillis(); - - if (logger.isDebugEnabled()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(200); - buffer.append(request.getMethod()); - buffer.append(" "); - buffer.append(request.getRequestURL()); - logger.debug(buffer); - } - - // Handle click page requests - Page page = null; - try { - - ActionEventDispatcher eventDispatcher = createActionEventDispatcher(); - // Bind ActionEventDispatcher to current thread - ActionEventDispatcher.pushThreadLocalDispatcher(eventDispatcher); - - ControlRegistry controlRegistry = createControlRegistry(); - // Bind ControlRegistry to current thread - ControlRegistry.pushThreadLocalRegistry(controlRegistry); - - Context context = createContext(request, response, isPost); - // Bind context to current thread - Context.pushThreadLocalContext(context); - - // Check for fatal error that occurred while creating Context - Throwable error = (Throwable) request.getAttribute(Context.CONTEXT_FATAL_ERROR); - - if (error != null) { - // Process exception through Click's exception handler. - if (error instanceof Exception) { - throw (Exception) error; - } - // Errors are not handled by Click, let the server handle it - if (error instanceof Error) { - throw (Error) error; - } else { - // Throwables are not handled by Click, let the server handle it - throw new RuntimeException(error); - } - } - - page = createPage(context); - - // If no page created, then an PageInterceptor has aborted processing - if (page == null) { - return; - } - - if (page.isStateful()) { - synchronized (page) { - processPage(page); - processPageOnDestroy(page, startTime); - // Mark page as already destroyed for finally block - page = null; - } - - } else { - processPage(page); - } - - } catch (Exception e) { - Class pageClass = - configService.getPageClass(ClickUtils.getResourcePath(request)); - - handleException(request, response, isPost, e, pageClass); - - } catch (ExceptionInInitializerError eiie) { - Throwable cause = eiie.getException(); - cause = (cause != null) ? cause : eiie; - - Class pageClass = - configService.getPageClass(ClickUtils.getResourcePath(request)); - - handleException(request, response, isPost, cause, pageClass); - - } finally { - - try { - if (page != null) { - if (page.isStateful()) { - synchronized (page) { - processPageOnDestroy(page, startTime); - } - - } else { - processPageOnDestroy(page, startTime); - } - } - - for (PageInterceptor interceptor : getThreadLocalInterceptors()) { - interceptor.postDestroy(page); - } - - setThreadLocalInterceptors(null); - - } finally { - // Only clear the context when running in normal mode. - if (request.getAttribute(MOCK_MODE_ENABLED) == null) { - Context.popThreadLocalContext(); - } - ControlRegistry.popThreadLocalRegistry(); - ActionEventDispatcher.popThreadLocalDispatcher(); - } - } - } - - /** - * Provides the application exception handler. The application exception - * will be delegated to the configured error page. The default error page is - * {@link ErrorPage} and the page template is "click/error.htm"

- * Applications which wish to provide their own customized error handling - * must subclass ErrorPage and specify their page in the - * "/WEB-INF/click.xml" application configuration file. For example: - * - *

-     *  <page path="click/error.htm" classname="com.mycorp.util.ErrorPage"/>
-     * 
- * - * If the ErrorPage throws an exception, it will be logged as an error and - * then be rethrown nested inside a RuntimeException. - * - * @param request the servlet request with the associated error - * @param response the servlet response - * @param isPost boolean flag denoting the request method is "POST" - * @param exception the error causing exception - * @param pageClass the page class with the error - */ - protected void handleException(HttpServletRequest request, - HttpServletResponse response, boolean isPost, Throwable exception, - Class pageClass) { - - if (isAjaxRequest(request)) { - handleAjaxException(request, response, isPost, exception, pageClass); - // Exit after handling ajax exception - return; - } - - if (exception instanceof TemplateException) { - TemplateException te = (TemplateException) exception; - if (!te.isParseError()) { - logger.error("handleException: ", exception); - } - - } else { - logger.error("handleException: ", exception); - } - - ErrorPage finalizeRef = null; - try { - final ErrorPage errorPage = createErrorPage(pageClass, exception); - - finalizeRef = errorPage; - - errorPage.setError(exception); - if (errorPage.getFormat() == null) { - errorPage.setFormat(configService.createFormat()); - } - errorPage.setHeaders(configService.getPageHeaders(ConfigService.ERROR_PATH)); - errorPage.setMode(configService.getApplicationMode()); - errorPage.setPageClass(pageClass); - errorPage.setPath(ConfigService.ERROR_PATH); - - processPageFields(errorPage, new ClickServlet.FieldCallback() { - public void processField(String fieldName, Object fieldValue) { - if (fieldValue instanceof Control) { - Control control = (Control) fieldValue; - if (control.getName() == null) { - control.setName(fieldName); - } - - if (!errorPage.getModel().containsKey(control.getName())) { - errorPage.addControl(control); - } - } - } - }); - - if (errorPage.isStateful()) { - synchronized (errorPage) { - processPage(errorPage); - processPageOnDestroy(errorPage, 0); - // Mark page as already destroyed for finally block - finalizeRef = null; - } - - } else { - processPage(errorPage); - } - - } catch (Exception ex) { - String message = - "handleError: " + ex.getClass().getName() - + " thrown while handling " + exception.getClass().getName() - + ". Now throwing RuntimeException."; - - logger.error(message, ex); - - throw new RuntimeException(ex); - - } finally { - if (finalizeRef != null) { - if (finalizeRef.isStateful()) { - synchronized (finalizeRef) { - processPageOnDestroy(finalizeRef, 0); - } - - } else { - processPageOnDestroy(finalizeRef, 0); - } - } - } - } - - /** - * Process the given page invoking its "on" event callback methods - * and directing the response. - *

- * This method does not invoke the "onDestroy()" callback method. - * - * @see #processPageEvents(Page, Context) - * - * @param page the Page to process - * @throws Exception if an error occurs - */ - @SuppressWarnings("deprecation") - protected void processPage(Page page) throws Exception { - - final Context context = page.getContext(); - - PageImports pageImports = createPageImports(page); - page.setPageImports(pageImports); - - if (context.isAjaxRequest()) { - processAjaxPageEvents(page, context); - } else { - processPageEvents(page, context); - } - } - - /** - * Process the given page events, invoking the "on" event callback methods - * and directing the response. - *

- * This method does not invoke the "onDestroy()" callback method. - * - * @param page the Page which events to process - * @param context the request context - * @throws Exception if an error occurs - */ - protected void processPageEvents(Page page, Context context) throws Exception { - - ActionEventDispatcher eventDispatcher = ActionEventDispatcher.getThreadLocalDispatcher(); - ControlRegistry controlRegistry = ControlRegistry.getThreadLocalRegistry(); - - boolean errorOccurred = page instanceof ErrorPage; - // Support direct access of click-error.htm - if (errorOccurred) { - ErrorPage errorPage = (ErrorPage) page; - errorPage.setMode(configService.getApplicationMode()); - - // Notify the eventDispatcher and controlRegistry of the error - eventDispatcher.errorOccurred(errorPage.getError()); - controlRegistry.errorOccurred(errorPage.getError()); - } - - boolean continueProcessing = performOnSecurityCheck(page, context); - - ActionResult actionResult = null; - if (continueProcessing && !errorOccurred) { - // Handle page method - String pageAction = context.getRequestParameter(Page.PAGE_ACTION); - if (pageAction != null) { - // Returned actionResult could be null - actionResult = performPageAction(page, pageAction, context); - continueProcessing = false; - } - } - - if (continueProcessing) { - performOnInit(page, context); - - continueProcessing = performOnProcess(page, context, eventDispatcher); - - if (continueProcessing) { - performOnPostOrGet(page, context, context.isPost()); - - performOnRender(page, context); - } - } - - controlRegistry.processPreResponse(context); - controlRegistry.processPreRenderHeadElements(context); - performRender(page, context, actionResult); - } - - /** - * Perform the onSecurityCheck event callback for the specified page, - * returning true if processing should continue, false otherwise. - * - * @param page the page to perform the security check on - * @param context the request context - * @return true if processing should continue, false otherwise - */ - protected boolean performOnSecurityCheck(Page page, Context context) { - boolean continueProcessing = page.onSecurityCheck(); - - if (logger.isTraceEnabled()) { - logger.trace(" invoked: " - + ClassUtils.getShortClassName(page.getClass()) - + ".onSecurityCheck() : " + continueProcessing); - } - return continueProcessing; - } - - /** - * Perform the page action for the given page and return the action result. - * - * @param page the page which action to perform - * @param pageAction the name of the page action - * @param context the request context - * @return the page action ActionResult instance - */ - protected ActionResult performPageAction(Page page, String pageAction, Context context) { - ActionResult actionResult = ClickUtils.invokeAction(page, pageAction); - - if (logger.isTraceEnabled()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - String pageClassName = ClassUtils.getShortClassName(page.getClass()); - buffer.append(" invoked: "); - buffer.append(pageClassName); - buffer.append(".").append(pageAction).append("() : "); - if (actionResult == null) { - buffer.append("null (*no* ActionResult was returned by PageAction)"); - } else { - buffer.append(ClassUtils.getShortClassName(actionResult.getClass())); - } - logger.trace(buffer.toString()); - } - return actionResult; - } - - /** - * Perform the onInit event callback for the specified page. - * - * @param page the page to initialize - * @param context the request context - */ - protected void performOnInit(Page page, Context context) { - page.onInit(); - - if (logger.isTraceEnabled()) { - logger.trace(" invoked: " - + ClassUtils.getShortClassName(page.getClass()) + ".onInit()"); - } - - if (page.hasControls()) { - List controls = page.getControls(); - - for (int i = 0, size = controls.size(); i < size; i++) { - Control control = controls.get(i); - control.onInit(); - - if (logger.isTraceEnabled()) { - String controlClassName = control.getClass().getName(); - controlClassName = controlClassName.substring( - controlClassName.lastIndexOf('.') + 1); - String msg = " invoked: '" + control.getName() + "' " - + controlClassName + ".onInit()"; - logger.trace(msg); - } - } - } - } - - /** - * Perform onProcess event callback for the specified page, returning true - * if processing should continue, false otherwise. - * - * @param page the page to process - * @param context the request context - * @param eventDispatcher the action event dispatcher - * @return true if processing should continue, false otherwise - */ - protected boolean performOnProcess(Page page, Context context, - ActionEventDispatcher eventDispatcher) { - - boolean continueProcessing = true; - - // Make sure don't process a forwarded request - if (page.hasControls() && !context.isForward()) { - List controls = page.getControls(); - - for (int i = 0, size = controls.size(); i < size; i++) { - Control control = controls.get(i); - - int initialListenerCount = 0; - if (logger.isTraceEnabled()) { - initialListenerCount = eventDispatcher.getEventSourceList().size(); - } - - boolean onProcessResult = control.onProcess(); - if (!onProcessResult) { - continueProcessing = false; - } - - if (logger.isTraceEnabled()) { - String controlClassName = ClassUtils.getShortClassName(control.getClass()); - - String msg = " invoked: '" + control.getName() + "' " - + controlClassName + ".onProcess() : " + onProcessResult; - logger.trace(msg); - - if (initialListenerCount != eventDispatcher.getEventSourceList().size()) { - logger.trace(" listener was registered while processing control"); - } - } - } - - if (continueProcessing) { - // Fire registered action events - continueProcessing = eventDispatcher.fireActionEvents(context); - - if (logger.isTraceEnabled()) { - String msg = " invoked: Control listeners : " - + continueProcessing; - logger.trace(msg); - } - } - } - - return continueProcessing; - } - - /** - * Perform onPost or onGet event callback for the specified page. - * - * @param page the page for which the onGet or onPost is performed - * @param context the request context - * @param isPost specifies whether the request is a post or a get - */ - protected void performOnPostOrGet(Page page, Context context, boolean isPost) { - if (isPost) { - page.onPost(); - - if (logger.isTraceEnabled()) { - logger.trace(" invoked: " - + ClassUtils.getShortClassName(page.getClass()) + ".onPost()"); - } - - } else { - page.onGet(); - - if (logger.isTraceEnabled()) { - logger.trace(" invoked: " - + ClassUtils.getShortClassName(page.getClass()) + ".onGet()"); - } - } - } - - /** - * Perform onRender event callback for the specified page. - * - * @param page page to render - * @param context the request context - */ - protected void performOnRender(Page page, Context context) { - page.onRender(); - - if (logger.isTraceEnabled()) { - logger.trace(" invoked: " - + ClassUtils.getShortClassName(page.getClass()) + ".onRender()"); - } - - if (page.hasControls()) { - List controls = page.getControls(); - - for (int i = 0, size = controls.size(); i < size; i++) { - Control control = controls.get(i); - control.onRender(); - - if (logger.isTraceEnabled()) { - String controlClassName = control.getClass().getName(); - controlClassName = controlClassName.substring(controlClassName. - lastIndexOf('.') + 1); - String msg = " invoked: '" + control.getName() + "' " - + controlClassName + ".onRender()"; - logger.trace(msg); - } - } - } - } - - /** - * Performs rendering of the specified page. - * - * @param page page to render - * @param context the request context - * @throws java.lang.Exception if error occurs - */ - protected void performRender(Page page, Context context) throws Exception { - performRender(page, context, null); - } - - /** - * Performs rendering of the specified page. - * - * @param page page to render - * @param context the request context - * @param actionResult the action result - * @throws java.lang.Exception if error occurs - */ - protected void performRender(Page page, Context context, ActionResult actionResult) - throws Exception { - - // Process page interceptors, and abort rendering if specified - for (PageInterceptor interceptor : getThreadLocalInterceptors()) { - if (!interceptor.preResponse(page)) { - return; - } - } - - final HttpServletRequest request = context.getRequest(); - final HttpServletResponse response = context.getResponse(); - - if (StringUtils.isNotBlank(page.getRedirect())) { - String url = page.getRedirect(); - - url = response.encodeRedirectURL(url); - - if (logger.isTraceEnabled()) { - logger.debug(" redirect: " + url); - - } else if (logger.isDebugEnabled()) { - logger.debug("redirect: " + url); - } - - response.sendRedirect(url); - - } else if (StringUtils.isNotBlank(page.getForward())) { - // Indicates the request is forwarded - request.setAttribute(CLICK_FORWARD, CLICK_FORWARD); - - if (logger.isTraceEnabled()) { - logger.debug(" forward: " + page.getForward()); - - } else if (logger.isDebugEnabled()) { - logger.debug("forward: " + page.getForward()); - } - - if (page.getForward().endsWith(".jsp")) { - renderJSP(page); - - } else { - RequestDispatcher dispatcher = - request.getRequestDispatcher(page.getForward()); - - dispatcher.forward(request, response); - } - - } else if (actionResult != null) { - renderActionResult(actionResult, page, context); - - } else if (page.getPath() != null) { - // Render template unless the request was a page action. This check - // guards against the scenario where the page action returns null - // instead of a action result - if (context.getRequestParameter(Page.PAGE_ACTION) == null) { - String pagePath = page.getPath(); - - // Check if request is a JSP page - if (pagePath.endsWith(".jsp") || configService.isJspPage(pagePath)) { - // CLK-141. Set pagePath as the forward value. - page.setForward(StringUtils.replace(pagePath, ".htm", ".jsp")); - - // Indicates the request is forwarded - request.setAttribute(CLICK_FORWARD, CLICK_FORWARD); - renderJSP(page); - - } else { - renderTemplate(page); - } - } - - } else { - if (logger.isTraceEnabled()) { - logger.debug(" path not defined for " + page.getClass().getName()); - - } else if (logger.isDebugEnabled()) { - logger.debug("path not defined for " + page.getClass().getName()); - } - } - } - - /** - * Render the Velocity template defined by the page's path. - *

- * This method creates a Velocity Context using the Page's model Map and - * then merges the template with the Context writing the result to the - * HTTP servlet response. - *

- * This method was adapted from org.apache.velocity.servlet.VelocityServlet. - * - * @param page the page template to merge - * @throws Exception if an error occurs - */ - protected void renderTemplate(Page page) throws Exception { - - long startTime = System.currentTimeMillis(); - - final Map model = createTemplateModel(page); - - Context context = page.getContext(); - HttpServletResponse response = context.getResponse(); - - response.setContentType(page.getContentType()); - - Writer writer = getWriter(response); - - if (page.hasHeaders()) { - setPageResponseHeaders(response, page.getHeaders()); - } - - configService.getTemplateService().renderTemplate(page, model, writer); - - if (!configService.isProductionMode()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(50); - if (logger.isTraceEnabled()) { - buffer.append(" "); - } - buffer.append("renderTemplate: "); - if (!page.getTemplate().equals(page.getPath())) { - buffer.append(page.getPath()); - buffer.append(","); - } - buffer.append(page.getTemplate()); - buffer.append(" - "); - buffer.append(System.currentTimeMillis() - startTime); - buffer.append(" ms"); - logger.info(buffer); - } - } - - /** - * Render the given page as a JSP to the response. - * - * @param page the page to render - * @throws Exception if an error occurs rendering the JSP - */ - protected void renderJSP(Page page) throws Exception { - - long startTime = System.currentTimeMillis(); - - HttpServletRequest request = page.getContext().getRequest(); - - HttpServletResponse response = page.getContext().getResponse(); - - setRequestAttributes(page); - - RequestDispatcher dispatcher = null; - - String forward = page.getForward(); - - // As "getTemplate" returns the page.getPath() by default, which is *.htm - // we need to change to *.jsp in order to compare to the page.getForward() - String jspTemplate = StringUtils.replace(page.getTemplate(), ".htm", ".jsp"); - - if (forward.equals(jspTemplate)) { - dispatcher = request.getRequestDispatcher(forward); - - } else { - dispatcher = request.getRequestDispatcher(page.getTemplate()); - } - - dispatcher.forward(request, response); - - if (!configService.isProductionMode()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(50); - buffer.append("renderJSP: "); - if (!page.getTemplate().equals(page.getForward())) { - buffer.append(page.getTemplate()); - buffer.append(","); - } - buffer.append(page.getForward()); - buffer.append(" - "); - buffer.append(System.currentTimeMillis() - startTime); - buffer.append(" ms"); - logger.info(buffer); - } - } - - /** - * Render the given ActionResult. If the action result is null, nothing is - * rendered. - * - * @param actionResult the action result to render - * @param page the requested page - * @param context the request context - */ - protected void renderActionResult(ActionResult actionResult, Page page, Context context) { - if (actionResult == null) { - return; - } - - long startTime = System.currentTimeMillis(); - - actionResult.render(context); - - if (!configService.isProductionMode()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(50); - if (logger.isTraceEnabled()) { - buffer.append(" "); - } - - buffer.append("renderActionResult ("); - buffer.append(actionResult.getContentType()); - buffer.append(")"); - String template = actionResult.getTemplate(); - if (template != null) { - buffer.append(": "); - buffer.append(template); - } - buffer.append(" - "); - buffer.append(System.currentTimeMillis() - startTime); - buffer.append(" ms"); - logger.info(buffer); - } - } - - /** - * Return a new Page instance for the given request context. This method will - * invoke {@link #initPage(String, Class, HttpServletRequest)} to create - * the Page instance and then set the properties on the page. - * - * @param context the page request context - * @return a new Page instance for the given request, or null if an - * PageInterceptor has aborted page creation - */ - protected Page createPage(Context context) { - - HttpServletRequest request = context.getRequest(); - - // Log request parameters - if (logger.isTraceEnabled()) { - logger.trace(" is Ajax request: " + context.isAjaxRequest()); - logRequestParameters(request); - } - - String path = context.getResourcePath(); - - if (request.getAttribute(FORWARD_PAGE) != null) { - Page forwardPage = (Page) request.getAttribute(FORWARD_PAGE); - - if (forwardPage.getFormat() == null) { - forwardPage.setFormat(configService.createFormat()); - } - - request.removeAttribute(FORWARD_PAGE); - - return forwardPage; - } - - Class pageClass = configService.getPageClass(path); - - if (pageClass == null) { - pageClass = configService.getNotFoundPageClass(); - path = ConfigService.NOT_FOUND_PATH; - } - // Set thread local app page listeners - List interceptors = configService.getPageInterceptors(); - setThreadLocalInterceptors(interceptors); - - for (PageInterceptor listener : interceptors) { - if (!listener.preCreate(pageClass, context)) { - return null; - } - } - - final Page page = initPage(path, pageClass, request); - - if (page.getFormat() == null) { - page.setFormat(configService.createFormat()); - } - - for (PageInterceptor listener : interceptors) { - if (!listener.postCreate(page)) { - return null; - } - } - - return page; - } - - /** - * Process the given pages controls onDestroy methods, reset the pages - * navigation state and process the pages onDestroy method. - * - * @param page the page to process - * @param startTime the start time to log if greater than 0 and not in - * production mode - */ - @SuppressWarnings("deprecation") - protected void processPageOnDestroy(Page page, long startTime) { - Context context = page.getContext(); - if (page.hasControls()) { - - // notify callbacks of destroy event - // TODO check that exceptions don't unnecessarily trigger preDestroy - ControlRegistry.getThreadLocalRegistry().processPreDestroy(context); - - List controls = page.getControls(); - - for (int i = 0, size = controls.size(); i < size; i++) { - try { - Control control = controls.get(i); - control.onDestroy(); - - if (logger.isTraceEnabled()) { - String controlClassName = control.getClass().getName(); - controlClassName = controlClassName.substring(controlClassName.lastIndexOf('.') + 1); - String msg = " invoked: '" + control.getName() - + "' " + controlClassName + ".onDestroy()"; - logger.trace(msg); - } - } catch (Throwable error) { - logger.error(error.toString(), error); - } - } - } - - // Reset the page navigation state - try { - // Reset the path - String path = context.getResourcePath(); - page.setPath(path); - - // Reset the forward - if (configService.isJspPage(path)) { - page.setForward(StringUtils.replace(path, ".htm", ".jsp")); - } else { - page.setForward((String) null); - } - - // Reset the redirect - page.setRedirect((String) null); - - } catch (Throwable error) { - logger.error(error.toString(), error); - } - - try { - page.onDestroy(); - - if (page.isStateful()) { - context.setSessionAttribute(page.getClass().getName(), page); - } else { - context.removeSessionAttribute(page.getClass().getName()); - } - - if (logger.isTraceEnabled()) { - String shortClassName = page.getClass().getName(); - shortClassName = - shortClassName.substring(shortClassName.lastIndexOf('.') + 1); - logger.trace(" invoked: " + shortClassName + ".onDestroy()"); - } - - if (!configService.isProductionMode() && startTime > 0) { - logger.info("handleRequest: " + page.getPath() + " - " - + (System.currentTimeMillis() - startTime) - + " ms"); - } - - } catch (Throwable error) { - logger.error(error.toString(), error); - - } finally { - // Nullify PageImports - page.setPageImports(null); - } - } - - /** - * Initialize a new page instance using - * {@link #newPageInstance(String, Class, HttpServletRequest)} method and - * setting format, headers and the forward if a JSP. - *

- * This method will also automatically register any public Page controls - * in the page's model. When the page is created any public visible - * page Control variables will be automatically added to the page using - * the method {@link Page#addControl(Control)} method. If the controls name - * is not defined it is set to the member variables name before it is added - * to the page. - *

- * This feature saves you from having to manually add the controls yourself. - * If you don't want the controls automatically added, simply declare them - * as non public variables. - *

- * An example auto control registration is provided below. In this example - * the Table control is automatically added to the model using the name - * "table", and the ActionLink controls are added using the names - * "editDetailsLink" and "viewDetailsLink". - * - *

-     * public class OrderDetailsPage extends Page {
-     *
-     *     public Table table = new Table();
-     *     public ActionLink editDetailsLink = new ActionLink();
-     *     publicActionLink viewDetailsLink = new ActionLink();
-     *
-     *     public OrderDetailsPage() {
-     *         ..
-     *     }
-     * } 
- * - * @param path the page path - * @param pageClass the page class - * @param request the page request - * @return initialized page - */ - protected Page initPage(String path, Class pageClass, - HttpServletRequest request) { - - try { - Page newPage = null; - - // Look up the page in the users session. - HttpSession session = request.getSession(false); - if (session != null) { - newPage = (Page) session.getAttribute(pageClass.getName()); - } - - if (newPage == null) { - newPage = newPageInstance(path, pageClass, request); - - if (logger.isTraceEnabled()) { - String shortClassName = pageClass.getName(); - shortClassName = - shortClassName.substring(shortClassName.lastIndexOf('.') + 1); - logger.trace(" invoked: " + shortClassName + ".<>"); - } - } - - activatePageInstance(newPage); - - Map defaultHeaders = configService.getPageHeaders(path); - if (newPage.hasHeaders()) { - - // Don't override existing headers - Map pageHeaders = newPage.getHeaders(); - for (Map.Entry entry : defaultHeaders.entrySet()) { - if (!pageHeaders.containsKey(entry.getKey())) { - pageHeaders.put(entry.getKey(), entry.getValue()); - } - } - - } else { - newPage.getHeaders().putAll(defaultHeaders); - } - - newPage.setPath(path); - - // Bind to final variable to enable callback processing - final Page page = newPage; - - if (configService.getAutoBindingMode() != AutoBinding.NONE) { - - processPageFields(newPage, new ClickServlet.FieldCallback() { - public void processField(String fieldName, Object fieldValue) { - if (fieldValue instanceof Control) { - Control control = (Control) fieldValue; - if (control.getName() == null) { - control.setName(fieldName); - } - - if (!page.getModel().containsKey(control.getName())) { - page.addControl(control); - } - } - } - }); - - processPageRequestParams(page); - } - - // In mock mode add the Page instance as a request attribute. - if (request.getAttribute(MOCK_MODE_ENABLED) != null) { - request.setAttribute(MOCK_PAGE_REFERENCE, page); - } - - return newPage; - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Process the page binding any request parameters to any public Page - * fields with the same name which are "primitive" types. These types - * include string, numbers and booleans. - *

- * Type conversion is performed using the TypeConverter - * returned by the {@link #getTypeConverter()} method. - * - * @param page the page whose fields are to be processed - * @throws OgnlException if an error occurs - */ - protected void processPageRequestParams(Page page) throws OgnlException { - - if (configService.getPageFields(page.getClass()).isEmpty()) { - return; - } - - Map ognlContext = null; - - boolean customConverter = - ! getTypeConverter().getClass().equals(RequestTypeConverter.class); - - HttpServletRequest request = page.getContext().getRequest(); - - for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) { - String name = e.nextElement().toString(); - String value = request.getParameter(name); - - if (StringUtils.isNotBlank(value)) { - - Field field = configService.getPageField(page.getClass(), name); - - if (field != null) { - Class type = field.getType(); - - if (customConverter - || (type.isPrimitive() - || String.class.isAssignableFrom(type) - || Number.class.isAssignableFrom(type) - || Boolean.class.isAssignableFrom(type))) { - - if (ognlContext == null) { - ognlContext = Ognl.createDefaultContext( - page, null, getTypeConverter(), getMemberAccess()); - } - - PropertyUtils.setValueOgnl(page, name, value, ognlContext); - - if (logger.isTraceEnabled()) { - logger.trace(" auto bound variable: " + name + "=" + value); - } - } - } - } - } - } - - /** - * Return a new Page instance for the given page path, class and request. - *

- * The default implementation of this method simply creates new page - * instances: - *

-     * protected Page newPageInstance(String path, Class pageClass,
-     *     HttpServletRequest request) throws Exception {
-     *
-     *     return (Page) pageClass.newInstance();
-     * } 
- * - * This method is designed to be overridden by applications providing their - * own page creation patterns. - *

- * A typical example of this would be with Inversion of Control (IoC) - * frameworks such as Spring or HiveMind. For example a Spring application - * could override this method and use a ApplicationContext to instantiate - * new Page objects: - *

-     * protected Page newPageInstance(String path, Class pageClass,
-     *     HttpServletRequest request) throws Exception {
-     *
-     *     String beanName = path.substring(0, path.indexOf("."));
-     *
-     *     if (applicationContext.containsBean(beanName)) {
-     *         Page page = (Page) applicationContext.getBean(beanName);
-     *
-     *     } else {
-     *         page = (Page) pageClass.newInstance();
-     *     }
-     *
-     *     return page;
-     * } 
- * - * @param path the request page path - * @param pageClass the page Class the request is mapped to - * @param request the page request - * @return a new Page object - * @throws Exception if an error occurs creating the Page - */ - protected Page newPageInstance(String path, Class pageClass, - HttpServletRequest request) throws Exception { - - return pageClass.newInstance(); - } - - /** - * Provides an extension point for ClickServlet sub classes to activate - * stateful page which may have been deserialized. - *

- * This method does nothing and is designed for extension. - * - * @param page the page instance to activate - */ - protected void activatePageInstance(Page page) { - } - - /** - * Return a new VelocityContext for the given pages model and Context. - *

- * The following values automatically added to the VelocityContext: - *

    - *
  • any public Page fields using the fields name
  • - *
  • context - the Servlet context path, e.g. /mycorp
  • - *
  • format - the {@link org.apache.click.util.Format} object for formatting - * the display of objects
  • - *
  • imports - the {@link org.apache.click.util.PageImports} object
  • - *
  • messages - the page messages bundle
  • - *
  • path - the page of the page template to render
  • - *
  • request - the pages servlet request
  • - *
  • response - the pages servlet request
  • - *
  • session - the {@link org.apache.click.util.SessionMap} adaptor for the - * users HttpSession
  • - *
- * - * @see org.openidentityplatform.openam.click.util.ClickUtils#createTemplateModel(Page, Context) - * - * @param page the page to create a VelocityContext for - * @return a new VelocityContext - */ - @SuppressWarnings("deprecation") - protected Map createTemplateModel(final Page page) { - - if (configService.getAutoBindingMode() != AutoBinding.NONE) { - - processPageFields(page, new ClickServlet.FieldCallback() { - public void processField(String fieldName, Object fieldValue) { - if (fieldValue instanceof Control == false) { - page.addModel(fieldName, fieldValue); - - } else { - // Add any controls not already added to model - Control control = (Control) fieldValue; - if (!page.getModel().containsKey(control.getName())) { - page.addControl(control); - } - } - } - }); - } - - final Context context = page.getContext(); - final Map model = ClickUtils.createTemplateModel(page, context); - - PageImports pageImports = page.getPageImports(); - pageImports.populateTemplateModel(model); - - return model; - } - - /** - * Set the HTTP headers in the servlet response. The Page response headers - * are defined in {@link Page#getHeaders()}. - * - * @param response the response to set the headers in - * @param headers the map of HTTP headers to set in the response - */ - protected void setPageResponseHeaders(HttpServletResponse response, - Map headers) { - - for (Map.Entry entry : headers.entrySet()) { - String name = entry.getKey(); - Object value = entry.getValue(); - - if (value instanceof String) { - String strValue = (String) value; - if (!strValue.equalsIgnoreCase("Content-Encoding")) { - response.setHeader(name, strValue); - } - - } else if (value instanceof Date) { - long time = ((Date) value).getTime(); - response.setDateHeader(name, time); - - } else if (value instanceof Integer) { - int intValue = (Integer) value; - response.setIntHeader(name, intValue); - - } else if (value != null) { - throw new IllegalStateException("Invalid Page header value type: " - + value.getClass() + ". Header value must of type String, Date or Integer."); - } - } - } - - /** - * Set the page model, context, format, messages and path as request - * attributes to support JSP rendering. These request attributes include: - *
    - *
  • any public Page fields using the fields name
  • - *
  • context - the Servlet context path, e.g. /mycorp
  • - *
  • format - the {@link org.apache.click.util.Format} object for - * formatting the display of objects
  • - *
  • forward - the page forward path, if defined
  • - *
  • imports - the {@link org.apache.click.util.PageImports} object
  • - *
  • messages - the page messages bundle
  • - *
  • path - the page of the page template to render
  • - *
- * - * @param page the page to set the request attributes on - */ - @SuppressWarnings("deprecation") - protected void setRequestAttributes(final Page page) { - final HttpServletRequest request = page.getContext().getRequest(); - - processPageFields(page, new ClickServlet.FieldCallback() { - public void processField(String fieldName, Object fieldValue) { - if (fieldValue instanceof Control == false) { - request.setAttribute(fieldName, fieldValue); - } else { - // Add any controls not already added to model - Control control = (Control) fieldValue; - if (!page.getModel().containsKey(control.getName())) { - page.addControl(control); - } - } - } - }); - - Map model = page.getModel(); - for (Map.Entry entry : model.entrySet()) { - String name = entry.getKey(); - Object value = entry.getValue(); - - request.setAttribute(name, value); - } - - request.setAttribute("context", request.getContextPath()); - if (model.containsKey("context")) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"context\". The request attribute " - + "has been replaced with the request " - + "context path"; - logger.warn(msg); - } - - request.setAttribute("format", page.getFormat()); - if (model.containsKey("format")) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"format\". The request attribute " - + "has been replaced with the format object"; - logger.warn(msg); - } - - request.setAttribute("forward", page.getForward()); - if (model.containsKey("forward")) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"forward\". The request attribute " - + "has been replaced with the page path"; - logger.warn(msg); - } - - request.setAttribute("path", page.getPath()); - if (model.containsKey("path")) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"path\". The request attribute " - + "has been replaced with the page path"; - logger.warn(msg); - } - - request.setAttribute("messages", page.getMessages()); - if (model.containsKey("messages")) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"messages\". The request attribute " - + "has been replaced with the page messages"; - logger.warn(msg); - } - - PageImports pageImports = page.getPageImports(); - pageImports.populateRequest(request, model); - } - - /** - * Return the request parameters OGNL TypeConverter. This method - * performs a lazy load of the TypeConverter object, using the classname - * defined in the Servlet init parameter type-converter-class, - * if this parameter is not defined this method will return a - * {@link RequestTypeConverter} instance. - * - * @return the request parameters OGNL TypeConverter - * @throws RuntimeException if the TypeConverter instance could not be created - */ - @SuppressWarnings("unchecked") - protected TypeConverter getTypeConverter() throws RuntimeException { - if (typeConverter == null) { - Class converter = RequestTypeConverter.class; - - try { - String classname = getInitParameter(TYPE_CONVERTER_CLASS); - if (StringUtils.isNotBlank(classname)) { - converter = ClickUtils.classForName(classname); - } - - typeConverter = converter.newInstance(); - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - return typeConverter; - } - - /** - * Creates and returns a new Context instance for this path, class and - * request. - *

- * The default implementation of this method simply creates a new Context - * instance. - *

- * Subclasses can override this method to provide a custom Context. - * - * @param request the page request - * @param response the page response - * @param isPost true if this is a post request, false otherwise - * @return a Context instance - */ - protected Context createContext(HttpServletRequest request, - HttpServletResponse response, boolean isPost) { - - Context context = new Context(getServletContext(), - getServletConfig(), - request, - response, - isPost, - this); - return context; - } - - /** - * Creates and returns a new ActionEventDispatcher instance. - * - * @return the new ActionEventDispatcher instance - */ - protected ActionEventDispatcher createActionEventDispatcher() { - return new ActionEventDispatcher(configService); - } - - /** - * Creates and returns a new ControlRegistry instance. - * - * @return the new ControlRegistry instance - */ - protected ControlRegistry createControlRegistry() { - return new ControlRegistry(configService); - } - - /** - * Creates and returns a new ErrorPage instance. - *

- * This method creates the custom page as specified in click.xml, - * otherwise the default ErrorPage instance. - *

- * Subclasses can override this method to provide custom ErrorPages tailored - * for specific exceptions. - *

- * Note you can safely use {@link Context} in this - * method. - * - * @param pageClass the page class with the error - * @param exception the error causing exception - * @return a new ErrorPage instance - */ - protected ErrorPage createErrorPage(Class pageClass, Throwable exception) { - try { - return (ErrorPage) configService.getErrorPageClass().newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Return the application configuration service instance. - * - * @return the application configuration service instance - */ - protected ConfigService getConfigService() { - return configService; - } - - /** - * Return a new Page instance for the given path. The path must start with - * a "/". - * - * @param path the path which maps to a Page class - * @param request the Page request - * @return a new Page object - * @throws IllegalArgumentException if the Page is not found - */ - @SuppressWarnings("unchecked") - protected T createPage(String path, HttpServletRequest request) { - Class pageClass = getConfigService().getPageClass(path); - - if (pageClass == null) { - String msg = "No Page class configured for path: " + path; - throw new IllegalArgumentException(msg); - } - - return (T) initPage(path, pageClass, request); - } - - /** - * Return a new Page instance for the page Class. - * - * @param pageClass the class of the Page to create - * @param request the Page request - * @return a new Page object - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - @SuppressWarnings("unchecked") - protected T createPage(Class pageClass, HttpServletRequest request) { - String path = getConfigService().getPagePath(pageClass); - - if (path == null) { - String msg = - "No path configured for Page class: " + pageClass.getName(); - throw new IllegalArgumentException(msg); - } - - return (T) initPage(path, pageClass, request); - } - - /** - * Creates and returns a new PageImports instance for the specified page. - * - * @param page the page to create a new PageImports instance for - * @return the new PageImports instance - */ - protected PageImports createPageImports(Page page) { - return new PageImports(page); - } - - // TODO refactor Page events into its a separate Livecycle class. This will - // take some of the responsibility off ClickServlet and remove code duplication - - /** - * Process the given page events, invoking the "on" event callback methods - * and directing the response. - * - * @param page the page which events to process - * @param context the request context - * @throws Exception if an error occurs - */ - protected void processAjaxPageEvents(Page page, Context context) throws Exception { - - ActionEventDispatcher eventDispatcher = ActionEventDispatcher.getThreadLocalDispatcher(); - - ControlRegistry controlRegistry = ControlRegistry.getThreadLocalRegistry(); - - // TODO Ajax requests shouldn't reach this code path since errors - // are rendered directly - if (page instanceof ErrorPage) { - ErrorPage errorPage = (ErrorPage) page; - errorPage.setMode(configService.getApplicationMode()); - - // Notify the dispatcher and registry of the error - eventDispatcher.errorOccurred(errorPage.getError()); - controlRegistry.errorOccurred(errorPage.getError()); - } - - boolean continueProcessing = performOnSecurityCheck(page, context); - - ActionResult actionResult = null; - if (continueProcessing) { - - // Handle page method - String pageAction = context.getRequestParameter(Page.PAGE_ACTION); - if (pageAction != null) { - continueProcessing = false; - - // Returned action result could be null - actionResult = performPageAction(page, pageAction, context); - - controlRegistry.processPreResponse(context); - controlRegistry.processPreRenderHeadElements(context); - - renderActionResult(actionResult, page, context); - } - } - - if (continueProcessing) { - performOnInit(page, context); - - // TODO: Ajax doesn't support forward. Is it still necessary to - // check isForward? - if (controlRegistry.hasAjaxTargetControls() && !context.isForward()) { - - // Perform onProcess for regsitered Ajax target controls - processAjaxTargetControls(context, eventDispatcher, controlRegistry); - - // Fire AjaxBehaviors registered during the onProcess event - // The target AjaxBehavior will set the eventDispatcher action - // result instance to render - eventDispatcher.fireAjaxBehaviors(context); - - // Ensure we execute the beforeResponse and beforeGetHeadElements - // for Ajax requests - controlRegistry.processPreResponse(context); - controlRegistry.processPreRenderHeadElements(context); - - actionResult = eventDispatcher.getActionResult(); - - // Render the actionResult - renderActionResult(actionResult, page, context); - - } else { - - // If no Ajax target controls have been registered fallback to - // the old behavior of processing and rendering the page template - if (logger.isTraceEnabled()) { - String msg = " *no* Ajax target controls have been registered." - + " Will process the page as a normal non Ajax request."; - logger.trace(msg); - } - - continueProcessing = performOnProcess(page, context, eventDispatcher); - - if (continueProcessing) { - performOnPostOrGet(page, context, context.isPost()); - - performOnRender(page, context); - } - - // If Ajax request does not target a valid page, return a 404 - // repsonse status, allowing JavaScript to display a proper message - if (ConfigService.NOT_FOUND_PATH.equals(page.getPath())) { - HttpServletResponse response = context.getResponse(); - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - return; - } - - controlRegistry.processPreResponse(context); - controlRegistry.processPreRenderHeadElements(context); - performRender(page, context); - } - } else { - // If security check fails for an Ajax request, Click returns without - // any rendering. It is up to the user to render an ActionResult - // in the onSecurityCheck event - // Note: this code path is also followed if a pageAction is invoked - } - } - - /** - * Process all Ajax target controls and return true if the page should continue - * processing, false otherwise. - * - * @param context the request context - * @param eventDispatcher the event dispatcher - * @param controlRegistry the control registry - * @return true if the page should continue processing, false otherwise - */ - protected boolean processAjaxTargetControls(Context context, - ActionEventDispatcher eventDispatcher, ControlRegistry controlRegistry) { - - boolean continueProcessing = true; - - // Resolve the Ajax target control for this request - Control ajaxTarget = resolveAjaxTargetControl(context, controlRegistry); - - if (ajaxTarget != null) { - - // Process the control - if (!ajaxTarget.onProcess()) { - continueProcessing = false; - } - - // Log a trace if no behavior was registered after processing the control - if (logger.isTraceEnabled()) { - - HtmlStringBuffer buffer = new HtmlStringBuffer(); - String controlClassName = ClassUtils.getShortClassName(ajaxTarget.getClass()); - buffer.append(" invoked: '"); - buffer.append(ajaxTarget.getName()); - buffer.append("' ").append(controlClassName); - buffer.append(".onProcess() : ").append(continueProcessing); - logger.trace(buffer.toString()); - - if (!eventDispatcher.hasAjaxBehaviorSourceSet()) { - logger.trace(" *no* AjaxBehavior was registered while processing the control"); - } - } - } - - return continueProcessing; - } - - /** - * Provides an Ajax exception handler. Exceptions are wrapped inside a - * div element and streamed back to the browser. The response status - * is set to an {@link jakarta.servlet.http.HttpServletResponse#SC_INTERNAL_SERVER_ERROR HTTP 500 error} - * which allows the JavaScript that initiated the Ajax request to handle - * the error as appropriate. - *

- * If Click is running in development modes the exception stackTrace - * will be rendered, in production modes an error message is - * rendered. - *

- * Below is an example error response: - * - *

-     * <div id='errorReport' class='errorReport'>
-     * The application encountered an unexpected error.
-     * </div>
-     * 
- * - * @param request the servlet request - * @param response the servlet response - * @param isPost determines whether the request is a POST - * @param exception the error causing exception - * @param pageClass the page class with the error - */ - protected void handleAjaxException(HttpServletRequest request, - HttpServletResponse response, boolean isPost, Throwable exception, - Class pageClass) { - - // If an exception occurs during an Ajax request, stream - // the exception instead of creating an ErrorPage - try { - - PrintWriter writer = null; - - try { - writer = getPrintWriter(response); - response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - - // TODO: use an ErrorReport instance instead? - writer.write("
\n"); - - if (configService.isProductionMode() || configService.isProfileMode()) { - writer.write("The application encountered an unexpected error."); - } else { - exception.printStackTrace(writer); - } - - writer.write("\n
"); - } finally { - if (writer != null) { - writer.flush(); - } - } - } catch (Throwable error) { - logger.error(error.getMessage(), error); - throw new RuntimeException(error); - } - logger.error("handleException: ", exception); - } - - // ------------------------------------------------ Package Private Methods - - /** - * Return the OGNL MemberAccess. This method performs a lazy load - * of the MemberAccess object, using a {@link DefaultMemberAccess} instance. - * - * @return the OGNL MemberAccess - */ - MemberAccess getMemberAccess() { - if (memberAccess == null) { - memberAccess = new DefaultMemberAccess(true); - } - return memberAccess; - } - - /** - * Create a Click application ConfigService instance. - * - * @param servletContext the Servlet Context - * @return a new application ConfigService instance - * @throws Exception if an initialization error occurs - */ - @SuppressWarnings("unchecked") - ConfigService createConfigService(ServletContext servletContext) - throws Exception { - - Class serviceClass = XmlConfigService.class; - - String classname = servletContext.getInitParameter(CONFIG_SERVICE_CLASS); - if (StringUtils.isNotBlank(classname)) { - serviceClass = ClickUtils.classForName(classname); - } - - return serviceClass.newInstance(); - } - - /** - * Initialize the Click application ConfigService instance and bind - * it as a ServletContext attribute using the key - * "org.apache.click.service.ConfigService". - *

- * This method will use the configuration service class specified by the - * {@link #CONFIG_SERVICE_CLASS} parameter, otherwise it will create a - * {@link org.apache.click.service.XmlConfigService} instance. - * - * @param servletContext the servlet context to retrieve the - * {@link #CONFIG_SERVICE_CLASS} from - * @throws RuntimeException if the configuration service cannot be - * initialized - */ - void initConfigService(ServletContext servletContext) { - - if (configService != null) { - try { - - // Note this order is very important as components need to lookup - // the configService out of the ServletContext while the service - // is being initialized. - servletContext.setAttribute(ConfigService.CONTEXT_NAME, configService); - - // Initialize the ConfigService instance - configService.onInit(servletContext); - - } catch (Exception e) { - - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } else { - throw new RuntimeException(e); - } - } - } - } - - /** - * Destroy the application configuration service instance and remove - * it from the ServletContext attribute. - * - * @param servletContext the servlet context - * @throws RuntimeException if the configuration service cannot be - * destroyed - */ - void destroyConfigService(ServletContext servletContext) { - - if (configService != null) { - - try { - configService.onDestroy(); - - } catch (Exception e) { - - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } else { - throw new RuntimeException(e); - } - } finally { - servletContext.setAttribute(ConfigService.CONTEXT_NAME, null); - } - } - } - - /** - * Process all the Pages public fields using the given callback. - * - * @param page the page to obtain the fields from - * @param callback the fields iterator callback - */ - void processPageFields(Page page, ClickServlet.FieldCallback callback) { - - Field[] fields = configService.getPageFieldArray(page.getClass()); - - if (fields != null) { - for (int i = 0; i < fields.length; i++) { - Field field = fields[i]; - - try { - Object fieldValue = field.get(page); - - if (fieldValue != null) { - callback.processField(field.getName(), fieldValue); - } - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - } - - List getThreadLocalInterceptors() { - List listeners = - THREAD_LOCAL_INTERCEPTORS.get(); - - if (listeners != null) { - return listeners; - } else { - return Collections.emptyList(); - } - } - - void setThreadLocalInterceptors(List listeners) { - THREAD_LOCAL_INTERCEPTORS.set(listeners); - } - - /** - * Retrieve a writer instance for the given context. - * - * @param response the servlet response - * @return a writer instance - * @throws IOException if an input or output exception occurred - */ - Writer getWriter(HttpServletResponse response) throws IOException { - try { - - return response.getWriter(); - - } catch (IllegalStateException ignore) { - // If writer cannot be retrieved fallback to OutputStream. CLK-644 - return new OutputStreamWriter(response.getOutputStream(), - response.getCharacterEncoding()); - } - } - - /** - * Return a PrintWriter instance for the given response. - * - * @param response the servlet response - * @return a PrintWriter instance - */ - PrintWriter getPrintWriter(HttpServletResponse response) throws IOException { - Writer writer = getWriter(response); - if (writer instanceof PrintWriter) { - return (PrintWriter) writer; - } - return new PrintWriter(writer); - } - - /** - * Return true if this is an ajax request, false otherwise. - * - * @param request the servlet request - * @return true if this is an ajax request, false otherwise - */ - boolean isAjaxRequest(HttpServletRequest request) { - boolean isAjaxRequest = false; - if (Context.hasThreadLocalContext()) { - Context context = Context.getThreadLocalContext(); - if (context.isAjaxRequest()) { - isAjaxRequest = true; - } - } else { - isAjaxRequest = ClickUtils.isAjaxRequest(request); - } - return isAjaxRequest; - } - - // ---------------------------------------------------------- Inner Classes - - /** - * Field iterator callback. - */ - static interface FieldCallback { - - /** - * Callback method invoked for each field. - * - * @param fieldName the field name - * @param fieldValue the field value - */ - public void processField(String fieldName, Object fieldValue); - - } - - // Private methods -------------------------------------------------------- - - /** - * Resolve and return the Ajax target control for this request or null if no - * Ajax target was found. - * - * @param context the request context - * @param controlRegistry the control registry - * @return the target Ajax target control or null if no Ajax target was found - */ - private Control resolveAjaxTargetControl(Context context, ControlRegistry controlRegistry) { - - Control ajaxTarget = null; - - if (logger.isTraceEnabled()) { - logger.trace(" the following controls have been registered as potential Ajax targets:"); - if (controlRegistry.hasAjaxTargetControls()) { - for (Control control : controlRegistry.getAjaxTargetControls()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - String controlClassName = ClassUtils.getShortClassName(control.getClass()); - buffer.append(" ").append(controlClassName); - buffer.append(": name='").append(control.getName()).append("'"); - logger.trace(buffer.toString()); - } - } else { - logger.trace(" *no* control has been registered"); - } - } - - for (Control control : controlRegistry.getAjaxTargetControls()) { - - if (control.isAjaxTarget(context)) { - ajaxTarget = control; - // The first matching control will be processed. Multiple matching - // controls are not supported - break; - } - } - - if (logger.isTraceEnabled()) { - if (ajaxTarget == null) { - String msg = " *no* target control was found for the Ajax request"; - logger.trace(msg); - - } else { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - buffer.append(" invoked: '"); - buffer.append(ajaxTarget.getName()).append("' "); - String className = ClassUtils.getShortClassName(ajaxTarget.getClass()); - buffer.append(className); - buffer.append(".isAjaxTarget() : true (Ajax target control found)"); - logger.trace(buffer.toString()); - } - } - - return ajaxTarget; - } - - /** - * Log the request parameter names and values. - * - * @param request the HTTP servlet request - */ - private void logRequestParameters(HttpServletRequest request) { - - Map requestParams = new TreeMap(); - - Enumeration e = request.getParameterNames(); - while (e.hasMoreElements()) { - String name = e.nextElement().toString(); - String[] values = request.getParameterValues(name); - requestParams.put(name, values); - } - - for (Map.Entry entry : requestParams.entrySet()) { - String name = entry.getKey(); - String[] values = entry.getValue(); - - HtmlStringBuffer buffer = new HtmlStringBuffer(40); - buffer.append(" request param: " + name + "="); - - if (values == null) { - // ignore - } else if (values.length == 1) { - - buffer.append(ClickUtils.limitLength(values[0], 40)); - } else { - - for (int i = 0; i < values.length; i++) { - if (i == 0) { - buffer.append('['); - } else { - buffer.append(", "); - } - buffer.append(ClickUtils.limitLength(values[i], 40)); - } - buffer.append("]"); - } - - logger.trace(buffer.toString()); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/Context.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/Context.java deleted file mode 100644 index ac6ddbe9b4..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/Context.java +++ /dev/null @@ -1,973 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Locale; -import java.util.Map; - -import jakarta.servlet.ServletConfig; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; - -import org.openidentityplatform.openam.click.service.FileUploadService; -import org.openidentityplatform.openam.click.service.LogService; -import org.openidentityplatform.openam.click.service.MessagesMapService; -import org.openidentityplatform.openam.click.service.TemplateService; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.apache.click.util.FlashAttribute; -import org.apache.commons.fileupload.FileItem; - -/** - * Provides the HTTP request context information for pages and controls. - * A new Context object is created for each Page request. The request Context - * object can be obtained from the thread local variable via the - * {@link org.openidentityplatform.openam.click.Context#getThreadLocalContext()} method. - */ -public class Context { - - // -------------------------------------------------------------- Constants - - /** The user's session Locale key:   locale. */ - public static final String LOCALE = "locale"; - - /** - * The attribute key used for storing any error that occurred while - * Context is created. - */ - static final String CONTEXT_FATAL_ERROR = "_context_fatal_error"; - - /** The thread local context stack. */ - private static final ThreadLocal THREAD_LOCAL_CONTEXT_STACK = new ThreadLocal<>(); - - // ----------------------------------------------------- Instance Variables - - /** The servlet context. */ - protected final ServletContext context; - - /** The servlet config. */ - protected final ServletConfig config; - - /** The HTTP method is POST flag. */ - protected final boolean isPost; - - /** The servlet response. */ - protected final HttpServletResponse response; - - /** The click services interface. */ - final ClickServlet clickServlet; - - /** The servlet request. */ - final HttpServletRequest request; - - // ----------------------------------------------------------- Constructors - - /** - * Create a new request context. - * - * @param context the servlet context - * @param config the servlet config - * @param request the servlet request - * @param response the servlet response - * @param isPost the servlet request is a POST - * @param clickServlet the click servlet instance - */ - public Context(ServletContext context, ServletConfig config, - HttpServletRequest request, HttpServletResponse response, - boolean isPost, ClickServlet clickServlet) { - - this.clickServlet = clickServlet; - this.context = context; - this.config = config; - this.isPost = isPost; - - Context.ContextStack contextStack = getContextStack(); - - if (contextStack.size() == 0) { - - // CLK-312. Apply request.setCharacterEncoding before wrapping - // request in ClickRequestWrapper - String charset = clickServlet.getConfigService().getCharset(); - if (charset != null) { - - try { - request.setCharacterEncoding(charset); - - } catch (UnsupportedEncodingException ex) { - String msg = - "The character encoding " + charset + " is invalid."; - LogService logService = - clickServlet.getConfigService().getLogService(); - logService.warn(msg, ex); - } - } - - FileUploadService fus = - clickServlet.getConfigService().getFileUploadService(); - - this.request = new ClickRequestWrapper(request, fus); - - } else { - this.request = request; - } - this.response = response; - } - - /** - * Package private constructor to support Mock objects. - * - * @param request the servlet request - * @param response the servlet response - */ - Context(HttpServletRequest request, HttpServletResponse response) { - // This method should be removed once the deprecated MockContext - // constructor is removed - this.request = request; - this.response = response; - this.clickServlet = null; - this.context = null; - this.config = null; - this.isPost = "POST".equalsIgnoreCase(request.getMethod()); - } - - // --------------------------------------------------------- Public Methods - - /** - * Return the thread local request context instance. - * - * @return the thread local request context instance. - * @throws RuntimeException if a Context is not available on the thread. - */ - public static Context getThreadLocalContext() { - return getContextStack().peek(); - } - - /** - * Returns true if a Context instance is available on the current thread, - * false otherwise. Unlike {@link #getThreadLocalContext()} this method - * can safely be used and will not throw an exception if Context is not - * available on the current thread. - *

- * This method is very useful inside a {@link Control} constructor which - * might need access to the Context. As Controls could potentially be - * instantiated during Click startup (in order to deploy their resources), - * this check can be used to determine whether Context is available or not. - *

- * For example: - * - *

-     * public MyControl extends AbstractControl {
-     *     public MyControl(String name) {
-     *         if (Context.hasThreadLocalContext()) {
-     *             // Context is available, meaning a user initiated a web
-     *             // request
-     *             Context context = getContext();
-     *             String state = (String) context.getSessionAttribute(name);
-     *             setValue(state);
-     *         } else {
-     *             // No Context is available, meaning this is most probably
-     *             // the application startup and deployment phase.
-     *         }
-     *     }
-     * }
-     * 
- * - * @return true if a Context instance is available on the current thread, - * false otherwise - */ - public static boolean hasThreadLocalContext() { - Context.ContextStack contextStack = THREAD_LOCAL_CONTEXT_STACK.get(); - if (contextStack == null) { - return false; - } - return !contextStack.isEmpty(); - } - - /** - * Returns the servlet request. - * - * @return HttpServletRequest - */ - public HttpServletRequest getRequest() { - return request; - } - - /** - * Returns the servlet response. - * - * @return HttpServletResponse - */ - public HttpServletResponse getResponse() { - return response; - } - - /** - * Returns the servlet config. - * - * @return ServletConfig - */ - public ServletConfig getServletConfig() { - return config; - } - - /** - * Returns the servlet context. - * - * @return ServletContext - */ - public ServletContext getServletContext() { - return context; - } - - /** - * Return the user's HttpSession, creating one if necessary. - * - * @return the user's HttpSession, creating one if necessary. - */ - public HttpSession getSession() { - return request.getSession(); - } - - /** - * Return the page resource path from the request. For example: - *
-     * http://www.mycorp.com/banking/secure/login.htm  ->  /secure/login.htm 
- * - * @return the page resource path from the request - */ - public String getResourcePath() { - return ClickUtils.getResourcePath(request); - } - - /** - * Return true if the request has been forwarded. A forwarded request - * will contain a {@link ClickServlet#CLICK_FORWARD} request attribute. - * - * @return true if the request has been forwarded - */ - public boolean isForward() { - return (request.getAttribute(ClickServlet.CLICK_FORWARD) != null); - } - - /** - * Return true if the HTTP request method is "POST". - * - * @return true if the HTTP request method is "POST" - */ - public boolean isPost() { - return isPost; - } - - /** - * Return true if the HTTP request method is "GET". - * - * @return true if the HTTP request method is "GET" - */ - public boolean isGet() { - return !isPost && getRequest().getMethod().equalsIgnoreCase("GET"); - } - - /** - * Return true is this is an Ajax request, false otherwise. - *

- * An Ajax request is identified by the presence of the request header - * or request parameter: "X-Requested-With". - * "X-Requested-With" is the de-facto standard identifier used by - * Ajax libraries. - *

- * Note: incoming requests that contains a request parameter - * "X-Requested-With" will result in this method returning true, even - * though the request itself was not initiated through a XmlHttpRequest - * object. This allows one to programmatically enable Ajax requests. A common - * use case for this feature is when uploading files through an IFrame element. - * By specifying "X-Requested-With" as a request parameter the IFrame - * request will be handled like a normal Ajax request. - * - * @return true if this is an Ajax request, false otherwise - */ - public boolean isAjaxRequest() { - return ClickUtils.isAjaxRequest(getRequest()); - } - - /** - * Return true if the request contains the named attribute. - * - * @param name the name of the request attribute - * @return true if the request contains the named attribute - */ - public boolean hasRequestAttribute(String name) { - return (getRequestAttribute(name) != null); - } - - /** - * Return the named request attribute, or null if not defined. - * - * @param name the name of the request attribute - * @return the named request attribute, or null if not defined - */ - public Object getRequestAttribute(String name) { - return request.getAttribute(name); - } - - /** - * This method will set the named object in the HTTP request. - * - * @param name the storage name for the object in the request - * @param value the object to store in the request - */ - public void setRequestAttribute(String name, Object value) { - request.setAttribute(name, value); - } - - /** - * Return true if the request contains the named parameter. - * - * @param name the name of the request parameter - * @return true if the request contains the named parameter - */ - public boolean hasRequestParameter(String name) { - if (name == null) { - throw new IllegalArgumentException("hasRequestParameter was called" - + " with null name argument. This is often caused when a" - + " Control binds to a request parameter, but its name was not" - + " set."); - } - return (getRequestParameter(name) != null); - } - - /** - * Return the named request parameter. This method supports - * "multipart/form-data" POST requests and should be used in - * preference to the HttpServletRequest method - * getParameter(). - * - * @see org.apache.click.control.Form#onProcess() - * @see #isMultipartRequest() - * @see #getFileItemMap() - * - * @param name the name of the request parameter - * @return the value of the request parameter. - */ - public String getRequestParameter(String name) { - if (name == null) { - throw new IllegalArgumentException("getRequestParameter was called" - + " with null name argument. This is often caused when a" - + " Control binds to a request parameter, but its name was not" - + " set."); - } - return request.getParameter(name); - } - - /** - * Returns an array of String objects containing all of the values the given - * request parameter has, or null if the parameter does not exist. - * - * @param name a String containing the name of the parameter whose - * value is requested - * @return an array of String objects containing the parameter's values - */ - public String[] getRequestParameterValues(String name) { - if (name == null) { - throw new IllegalArgumentException("getRequestParameter was called" - + " with null name argument. This is often caused when a" - + " Control binds to a request parameter, but its name was not" - + " set."); - } - return request.getParameterValues(name); - } - - /** - * Return the named session attribute, or null if not defined. - *

- * If the session is not defined this method will return null, and a - * session will not be created. - *

- * This method supports {@link FlashAttribute} which when accessed are then - * removed from the session. - * - * @param name the name of the session attribute - * @return the named session attribute, or null if not defined - */ - public Object getSessionAttribute(String name) { - if (hasSession()) { - Object object = getSession().getAttribute(name); - - if (object instanceof FlashAttribute) { - FlashAttribute flashObject = (FlashAttribute) object; - object = flashObject.getValue(); - removeSessionAttribute(name); - } - - return object; - } else { - return null; - } - } - - /** - * This method will set the named object in the HttpSession. - *

- * This method will create a session if one does not already exist. - * - * @param name the storage name for the object in the session - * @param value the object to store in the session - */ - public void setSessionAttribute(String name, Object value) { - getSession().setAttribute(name, value); - } - - /** - * Remove the named attribute from the session. If the session does not - * exist or the name is null, this method does nothing. - * - * @param name of the attribute to remove from the session - */ - public void removeSessionAttribute(String name) { - if (hasSession() && name != null) { - getSession().removeAttribute(name); - } - } - - /** - * Return true if there is a session and it contains the named attribute. - * - * @param name the name of the attribute - * @return true if the session contains the named attribute - */ - public boolean hasSessionAttribute(String name) { - return (getSessionAttribute(name) != null); - } - - /** - * Return true if a HttpSession exists, or false otherwise. - * - * @return true if a HttpSession exists, or false otherwise - */ - public boolean hasSession() { - return (request.getSession(false) != null); - } - - /** - * This method will set the named object as a flash HttpSession object. - *

- * The flash object will exist in the session until it is accessed once, - * and then removed. Flash objects are typically used to display a message - * once. - * - * @param name the storage name for the object in the session - * @param value the object to store in the session - */ - public void setFlashAttribute(String name, Object value) { - getSession().setAttribute(name, new FlashAttribute(value)); - } - - /** - * Return the cookie for the given name or null if not found. - * - * @param name the name of the cookie - * @return the cookie for the given name or null if not found - */ - public Cookie getCookie(String name) { - return ClickUtils.getCookie(getRequest(), name); - } - - /** - * Return the cookie value for the given name or null if not found. - * - * @param name the name of the cookie - * @return the cookie value for the given name or null if not found - */ - public String getCookieValue(String name) { - return ClickUtils.getCookieValue(getRequest(), name); - } - - /** - * Sets the given cookie value in the servlet response with the path "/". - *

- * @see ClickUtils#setCookie(HttpServletRequest, HttpServletResponse, String, String, int, String) - * - * @param name the cookie name - * @param value the cookie value - * @param maxAge the maximum age of the cookie in seconds. A negative - * value will expire the cookie at the end of the session, while 0 will delete - * the cookie. - * @return the Cookie object created and set in the response - */ - public Cookie setCookie(String name, String value, int maxAge) { - return ClickUtils.setCookie(getRequest(), - getResponse(), - name, - value, - maxAge, - "/"); - } - - /** - * Invalidate the specified cookie and delete it from the response object. - * Deletes only cookies mapped against the root "/" path. - * - * @see ClickUtils#invalidateCookie(HttpServletRequest, HttpServletResponse, String) - * - * @param name the name of the cookie you want to delete. - */ - public void invalidateCookie(String name) { - ClickUtils.invalidateCookie(getRequest(), getResponse(), name); - } - - /** - * Return a new Page instance for the given path. - *

- * This method can be used to create a target page for the - * {@link org.apache.click.Page#setForward(org.apache.click.Page)}, for example: - * - *

-     * UserEdit userEdit = (UserEdit) getContext().createPage("/user-edit.htm");
-     * userEdit.setUser(user);
-     *
-     * setForward(userEdit); 
- * - * The given page path must start with a '/'. - * - * @param path the Page path as configured in the click.xml file - * @return a new Page object - * @throws IllegalArgumentException if the Page is not found - */ - @SuppressWarnings("unchecked") - public T createPage(String path) { - if (path == null || path.length() == 0) { - throw new IllegalArgumentException("page path cannot be null or empty"); - } - - if (path.charAt(0) != '/') { - throw new IllegalArgumentException("page path must start with a '/'"); - } - return (T) clickServlet.createPage(path, request); - } - - /** - * Return a new Page instance for the given class. - *

- * This method can be used to create a target page for the - * {@link org.apache.click.Page#setForward(org.apache.click.Page)}, for example: - * - *

-     * UserEdit userEdit = (UserEdit) getContext().createPage(UserEdit.class);
-     * userEdit.setUser(user);
-     *
-     * setForward(userEdit); 
- * - * @param pageClass the Page class as configured in the click.xml file - * @return a new Page object - * @throws IllegalArgumentException if the Page is not found, or is not - * configured with a unique path - */ - public T createPage(Class pageClass) { - return clickServlet.createPage(pageClass, request); - } - - /** - * Return the path for the given page Class. - * - * @param pageClass the class of the Page to lookup the path for - * @return the path for the given page Class - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - public String getPagePath(Class pageClass) { - return clickServlet.getConfigService().getPagePath(pageClass); - } - - /** - * Return the page Class for the given path. - * - * @param path the page path - * @return the page class for the given path - * @throws IllegalArgumentException if the Page Class for the path is not - * found - */ - public Class getPageClass(String path) { - return clickServlet.getConfigService().getPageClass(path); - } - - /** - * Return the Click application mode value:   - * ["production", "profile", "development", "debug", "trace"]. - * - * @return the application mode value - */ - public String getApplicationMode() { - return clickServlet.getConfigService().getApplicationMode(); - } - - /** - * Return the Click application charset or ISO-8859-1 if not is defined. - *

- * The charset is defined in click.xml through the charset attribute - * on the click-app element. - * - *

-     * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-     * <click-app charset="UTF-8">
-     *    ..
-     * </click-app> 
- * - * @return the application charset or ISO-8859-1 if not defined - */ - public String getCharset() { - String charset = clickServlet.getConfigService().getCharset(); - if (charset == null) { - charset = "ISO-8859-1"; - } - return charset; - } - - /** - * Return a new messages map for the given baseClass (a page or control) - * and the given global resource bundle name. - * - * @param baseClass the target class - * @param globalResource the global resource bundle name - * @return a new messages map with the messages for the target. - */ - public Map createMessagesMap(Class baseClass, String globalResource) { - MessagesMapService messagesMapService = - clickServlet.getConfigService().getMessagesMapService(); - - return messagesMapService.createMessagesMap(baseClass, globalResource, getLocale()); - } - - /** - * Returns a map of FileItem arrays keyed on request parameter - * name for "multipart" POST requests (file uploads). Thus each map entry - * will consist of one or more FileItem objects. - * - * @return map of FileItem arrays keyed on request parameter name - * for "multipart" POST requests - */ - public Map getFileItemMap() { - return findClickRequestWrapper(request).getFileItemMap(); - } - - /** - * Returns the value of a request parameter as a FileItem, for - * "multipart" POST requests (file uploads), or null if the parameter - * is not found. - *

- * If there were multivalued parameters in the request (ie two or more - * file upload fields with the same name), the first fileItem - * in the array is returned. - * - * @param name the name of the parameter of the fileItem to retrieve - * - * @return the fileItem for the specified name - */ - public FileItem getFileItem(String name) { - Object value = findClickRequestWrapper(request).getFileItemMap().get(name); - - if (value != null) { - if (value instanceof FileItem[]) { - FileItem[] array = (FileItem[]) value; - if (array.length >= 1) { - return array[0]; - } - } else if (value instanceof FileItem) { - return (FileItem) value; - } - } - return null; - } - - /** - * Return the users Locale. - *

- * If the users Locale is stored in their session this will be returned. - * Else if the click-app configuration defines a default Locale this - * value will be returned, otherwise the request's Locale will be returned. - *

- * To override the default request Locale set the users Locale using the - * {@link #setLocale(Locale)} method. - *

- * Pages and Controls obtain the users Locale using this method. - * - * @return the users Locale in the session, or if null the request Locale - */ - public Locale getLocale() { - Locale locale = (Locale) getSessionAttribute(LOCALE); - - if (locale == null) { - - if (clickServlet.getConfigService().getLocale() != null) { - locale = clickServlet.getConfigService().getLocale(); - - } else { - locale = getRequest().getLocale(); - } - } - - return locale; - } - - /** - * This method stores the given Locale in the users session. If the given - * Locale is null, the "locale" attribute will be removed from the session. - *

- * The Locale object is stored in the session using the {@link #LOCALE} - * key. - * - * @param locale the Locale to store in the users session using the key - * "locale" - */ - public void setLocale(Locale locale) { - if (locale == null && hasSession()) { - getSession().removeAttribute(LOCALE); - } else { - setSessionAttribute(LOCALE, locale); - } - } - - /** - * Return true if the request is a multi-part content type POST request. - * - * @return true if the request is a multi-part content type POST request - */ - public boolean isMultipartRequest() { - return ClickUtils.isMultipartRequest(request); - } - - /** - * Return a rendered Velocity template and model for the given - * class and model data. - *

- * This method will merge the class .htm Velocity template and - * model data using the applications Velocity Engine. - *

- * An example of the class template resolution is provided below: - *

-     * // Full class name
-     * com.mycorp.control.CustomTextField
-     *
-     * // Template path name
-     * /com/mycorp/control/CustomTextField.htm 
- * - * Example method usage: - *
-     * public String toString() {
-     *     Map model = getModel();
-     *     return getContext().renderTemplate(getClass(), model);
-     * } 
- * - * @param templateClass the class to resolve the template for - * @param model the model data to merge with the template - * @return rendered Velocity template merged with the model data - * @throws RuntimeException if an error occurs - */ - @SuppressWarnings("unchecked") - public String renderTemplate(Class templateClass, Map model) { - - if (templateClass == null) { - String msg = "Null templateClass parameter"; - throw new IllegalArgumentException(msg); - } - - String templatePath = templateClass.getName(); - templatePath = '/' + templatePath.replace('.', '/') + ".htm"; - - return renderTemplate(templatePath, model); - } - - /** - * Return a rendered Velocity template and model data. - *

- * Example method usage: - *

-     * public String toString() {
-     *     Map model = getModel();
-     *     return getContext().renderTemplate("/custom-table.htm", model);
-     * } 
- * - * @param templatePath the path of the Velocity template to render - * @param model the model data to merge with the template - * @return rendered Velocity template merged with the model data - * @throws RuntimeException if an error occurs - */ - public String renderTemplate(String templatePath, Map model) { - - if (templatePath == null) { - String msg = "Null templatePath parameter"; - throw new IllegalArgumentException(msg); - } - - if (model == null) { - String msg = "Null model parameter"; - throw new IllegalArgumentException(msg); - } - - StringWriter stringWriter = new StringWriter(1024); - - TemplateService templateService = - clickServlet.getConfigService().getTemplateService(); - - try { - templateService.renderTemplate(templatePath, model, stringWriter); - - } catch (Exception e) { - String msg = "Error occurred rendering template: " - + templatePath + "\n"; - clickServlet.getConfigService().getLogService().error(msg, e); - - throw new RuntimeException(e); - } - - return stringWriter.toString(); - } - - // ------------------------------------------------ Package Private Methods - - /** - * Return the stack data structure where Context's are stored. - * - * @return stack data structure where Context's are stored - */ - static Context.ContextStack getContextStack() { - Context.ContextStack contextStack = THREAD_LOCAL_CONTEXT_STACK.get(); - - if (contextStack == null) { - contextStack = new Context.ContextStack(2); - THREAD_LOCAL_CONTEXT_STACK.set(contextStack); - } - - return contextStack; - } - - /** - * Adds the specified Context on top of the Context stack. - * - * @param context a context instance - */ - static void pushThreadLocalContext(Context context) { - getContextStack().push(context); - } - - /** - * Remove and return the context instance on top of the context stack. - * - * @return the context instance on top of the context stack - */ - static Context popThreadLocalContext() { - Context.ContextStack contextStack = getContextStack(); - Context context = contextStack.pop(); - - if (contextStack.isEmpty()) { - THREAD_LOCAL_CONTEXT_STACK.remove(); - } - - return context; - } - - /** - * Find and return the ClickRequestWrapper that is wrapped by the specified - * request. - * - * @param request the servlet request that wraps the ClickRequestWrapper - * @return the wrapped servlet request - */ - static ClickRequestWrapper findClickRequestWrapper(ServletRequest request) { - - while (!(request instanceof ClickRequestWrapper) - && request instanceof HttpServletRequestWrapper - && request != null) { - request = ((HttpServletRequestWrapper) request).getRequest(); - } - - if (request instanceof ClickRequestWrapper) { - return (ClickRequestWrapper) request; - } else { - throw new IllegalStateException("Click request is not present"); - } - } - - // -------------------------------------------------- Inner Classes - - /** - * Provides an unsynchronized Context Stack. - */ - static final class ContextStack extends ArrayList { - - /** Serialization version indicator. */ - private static final long serialVersionUID = 1L; - - /** - * Create a new ContextStack with the given initial capacity. - * - * @param initialCapacity specify initial capacity of this stack - */ - private ContextStack(int initialCapacity) { - super(initialCapacity); - } - - /** - * Pushes the Context onto the top of this stack. - * - * @param context the Context to push onto this stack - * @return the Context pushed on this stack - */ - private Context push(Context context) { - add(context); - - return context; - } - - /** - * Removes and return the Context at the top of this stack. - * - * @return the Context at the top of this stack - */ - private Context pop() { - Context context = peek(); - - remove(size() - 1); - - return context; - } - - /** - * Looks at the Context at the top of this stack without removing it. - * - * @return the Context at the top of this stack - */ - private Context peek() { - int length = size(); - - if (length == 0) { - String msg = "No Context available on ThreadLocal Context Stack"; - throw new RuntimeException(msg); - } - - return get(length - 1); - } - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/Control.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/Control.java deleted file mode 100644 index 96d0c9d548..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/Control.java +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.Serializable; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.ServletContext; - -import org.openidentityplatform.openam.click.element.Element; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides the interface for Page controls. Controls are also referred to - * as components or widgets. - *

- * When a Page request event is processed Controls may perform server side event - * processing through their {@link #onProcess()} method. Controls are generally - * rendered in a Page by calling their toString() method. - *

- * The Control execution sequence is illustrated below: - *

- * - * - *

HTML HEAD Elements

- * - * Control HTML HEAD elements can be included in the Page by overriding the - * {@link #getHeadElements()} method. - *

- * Below is an example of a custom TextField control specifying that the - * custom.js file should be included in the HTML HEADer: - * - *

- * public class CustomField extends TextField {
- *
- *     public List getHeadElements() {
- *         if(headElements == null) {
- *             // If headElements is null, create default headElements
- *             headElements = super.getHeadElements();
- *
- *             // Add a new JavaScript Import Element for the "/custom.js" script
- *             headElements.add(new JsImport("/click/custom.js"));
- *         }
- *         return headElements;
- *     }
- *
- *     ..
- * } 
- * - * - *

Deploying Resources

- * - * The Click framework uses the Velocity Tools WebappResourceLoader for loading templates. - * This avoids issues associate with using the Velocity ClasspathResourceLoader and - * FileResourceLoader on J2EE application servers. - * To make preconfigured resources (templates, JavaScript, stylesheets, etc.) - * available to web applications Click automatically deploys configured classpath - * resources to the /click directory at startup - * (existing files will not be overwritten). - *

- * Click supports two ways of deploying pre-configured resources. The recommended - * deployment strategy (which also the simplest) relies on packaging resources - * into a special folder of the JAR, called 'META-INF/resources'. At - * startup time Click will scan this folder for resources and deploy them to the - * web application. This deployment strategy is the same approach taken by the - * Servlet 3.0 specification. Please see the section - * Deploying Custom Resources - * for more details. - *

- * An alternative approach to deploying static resources on startup is provided - * by the Control interface through the {@link #onDeploy(ServletContext)} method. - *

- * Continuing our example, the CustomField control deploys its - * custom.js file to the /click directory: - * - *

- * public class CustomField extends TextField {
- *     ..
- *
- *     public void onDeploy(ServletContext servletContext) {
- *         ClickUtils.deployFile
- *             (servletContext, "/com/mycorp/control/custom.js", "click");
- *     }
- * } 
- * - * Controls using the onDeploy() method must be registered in the - * application WEB-INF/click.xml for them to be invoked. - * For example: - * - *
- * <click-app>
- *   <pages package="com.mycorp.page" automapping="true"/>
- *
- *   <controls>
- *     <control classname="com.mycorp.control.CustomField"/>
- *   </controls>
- * </click-app> 
- * - * When the Click application starts up it will deploy any control elements - * defined in the following files in sequential order: - *
    - *
  • /click-controls.xml - *
  • /extras-controls.xml - *
  • WEB-INF/click.xml - *
- * - * Please note {@link org.apache.click.control.AbstractControl} provides - * a default implementation of the Control interface to make it easier for - * developers to create their own controls. - * - * @see org.apache.click.util.PageImports - */ -public interface Control extends Serializable { - - /** - * The global control messages bundle name:   click-control. - */ - public static final String CONTROL_MESSAGES = "click-control"; - - /** - * Return the Page request Context of the Control. - * - * @deprecated getContext() is now obsolete on the Control interface, - * but will still be available on AbstractControl: - * {@link org.apache.click.control.AbstractControl#getContext()} - * - * @return the Page request Context - */ - public Context getContext(); - - /** - * Return the list of HEAD {@link org.apache.click.element.Element elements} - * to be included in the page. Example HEAD elements include - * {@link org.apache.click.element.JsImport JsImport}, - * {@link org.apache.click.element.JsScript JsScript}, - * {@link org.apache.click.element.CssImport CssImport} and - * {@link org.apache.click.element.CssStyle CssStyle}. - *

- * Controls can contribute their own list of HEAD elements by implementing - * this method. - *

- * The recommended approach when implementing this method is to use - * lazy loading to ensure the HEAD elements are only added - * once and when needed. For example: - * - *

-     * public MyControl extends AbstractControl {
-     *
-     *     public List getHeadElements() {
-     *         // Use lazy loading to ensure the JS is only added the
-     *         // first time this method is called.
-     *         if (headElements == null) {
-     *             // Get the head elements from the super implementation
-     *             headElements = super.getHeadElements();
-     *
-     *             // Include the control's external JavaScript resource
-     *             JsImport jsImport = new JsImport("/mycorp/mycontrol/mycontrol.js");
-     *             headElements.add(jsImport);
-     *
-     *             // Include the control's external Css resource
-     *             CssImport cssImport = new CssImport("/mycorp/mycontrol/mycontrol.css");
-     *             headElements.add(cssImport);
-     *         }
-     *         return headElements;
-     *     }
-     * } 
- * - * Alternatively one can add the HEAD elements in the Control's constructor: - * - *
-     * public MyControl extends AbstractControl {
-     *
-     *     public MyControl() {
-     *
-     *         JsImport jsImport = new JsImport("/mycorp/mycontrol/mycontrol.js");
-     *         getHeadElements().add(jsImport);
-     *
-     *         CssImport cssImport = new CssImport("/mycorp/mycontrol/mycontrol.css");
-     *         getHeadHeaders().add(cssImport);
-     *     }
-     * } 
- * - * One can also add HEAD elements from event handler methods such as - * {@link #onInit()}, {@link #onProcess()}, {@link #onRender()} - * etc. - *

- * The order in which JS and CSS files are included will be preserved in the - * page. - *

- * Note: this method must never return null. If no HEAD elements - * are available this method must return an empty {@link java.util.List}. - *

- * Also note: a common problem when overriding getHeadElements in - * subclasses is forgetting to call super.getHeadElements. Consider - * carefully whether you should call super.getHeadElements or not. - * - * @return the list of HEAD elements to be included in the page - */ - public List getHeadElements(); - - /** - * Return HTML element identifier attribute "id" value. - * - * {@link org.apache.click.control.AbstractControl#getId()} - * - * @return HTML element identifier attribute "id" value - */ - public String getId(); - - /** - * Set the controls event listener. - *

- * The method signature of the listener is:

    - *
  • must have a valid Java method name
  • - *
  • takes no arguments
  • - *
  • returns a boolean value
  • - *
- *

- * An example event listener method would be: - * - *

-     * public boolean onClick() {
-     *     System.out.println("onClick called");
-     *     return true;
-     * } 
- * - * @param listener the listener object with the named method to invoke - * @param method the name of the method to invoke - * - * @deprecated this method is now obsolete on the Control interface, but - * will still be available on AbstractControl: - * {@link org.apache.click.control.AbstractControl#setListener(java.lang.Object, java.lang.String)} - */ - public void setListener(Object listener, String method); - - /** - * Return the localized messages Map of the Control. - * - * @return the localized messages Map of the Control - */ - public Map getMessages(); - - /** - * Return the name of the Control. Each control name must be unique in the - * containing Page model or the containing Form. - * - * @return the name of the control - */ - public String getName(); - - /** - * Set the name of the Control. Each control name must be unique in the - * containing Page model or the parent container. - *

- * Please note: changing the name of a Control after it has been - * added to its parent container is undefined. Thus it is best not - * to change the name of a Control once its been set. - * - * @param name of the control - * @throws IllegalArgumentException if the name is null - */ - public void setName(String name); - - /** - * Return the parent of the Control. - * - * @return the parent of the Control - */ - public Object getParent(); - - /** - * Set the parent of the Control. - * - * @param parent the parent of the Control - */ - public void setParent(Object parent); - - /** - * The on deploy event handler, which provides classes the - * opportunity to deploy static resources when the Click application is - * initialized. - *

- * For example: - *

-     * public void onDeploy(ServletContext servletContext) throws IOException {
-     *     ClickUtils.deployFile
-     *         (servletContext, "/com/mycorp/control/custom.js", "click");
-     * } 
- * Please note: a common problem when overriding onDeploy in - * subclasses is forgetting to call super.onDeploy. Consider - * carefully whether you should call super.onDeploy or not. - *

- * Click also supports an alternative deployment strategy which relies on - * packaging resource (stylesheets, JavaScript, images etc.) following a - * specific convention. See the section - * Deploying Custom Resources - * for further details. - * - * @param servletContext the servlet context - */ - public void onDeploy(ServletContext servletContext); - - /** - * The on initialize event handler. Each control will be initialized - * before its {@link #onProcess()} method is called. - *

- * {@link org.apache.click.control.Container} implementations should recursively - * invoke the onInit method on each of their child controls ensuring that - * all controls receive this event. - *

- * Please note: a common problem when overriding onInit in - * subclasses is forgetting to call super.onInit(). Consider - * carefully whether you should call super.onInit() or not, - * especially for {@link org.apache.click.control.Container}s which by default - * call onInit on all their child controls as well. - */ - public void onInit(); - - /** - * The on process event handler. Each control will be processed when the - * Page is requested. - *

- * ClickServlet will process all Page controls in the order they were added - * to the Page. - *

- * {@link org.apache.click.control.Container} implementations should recursively - * invoke the onProcess method on each of their child controls ensuring that - * all controls receive this event. However when a control onProcess method - * return false, no other controls onProcess method should be invoked. - *

- * When a control is processed it should return true if the Page should - * continue event processing, or false if no other controls should be - * processed and the {@link org.apache.click.Page#onGet()} or {@link Page#onPost()} methods - * should not be invoked. - *

- * Please note: a common problem when overriding onProcess in - * subclasses is forgetting to call super.onProcess(). Consider - * carefully whether you should call super.onProcess() or not, - * especially for {@link org.apache.click.control.Container}s which by default - * call onProcess on all their child controls as well. - * - * @return true to continue Page event processing or false otherwise - */ - public boolean onProcess(); - - /** - * The on render event handler. This event handler is invoked prior to the - * control being rendered, and is useful for providing pre rendering logic. - *

- * The on render method is typically used to populate tables performing some - * database intensive operation. By putting the intensive operations in the - * on render method they will not be performed if the user navigates away - * to a different page. - *

- * {@link org.apache.click.control.Container} implementations should recursively - * invoke the onRender method on each of their child controls ensuring that - * all controls receive this event. - *

- * Please note: a common problem when overriding onRender in - * subclasses is forgetting to call super.onRender(). Consider - * carefully whether you should call super.onRender() or not, - * especially for {@link org.apache.click.control.Container}s which by default - * call onRender on all their child controls as well. - */ - public void onRender(); - - /** - * The on destroy request event handler. Control classes should use this - * method to add any resource clean up code. - *

- * This method is guaranteed to be called before the Page object reference - * goes out of scope and is available for garbage collection. - *

- * {@link org.apache.click.control.Container} implementations should recursively - * invoke the onDestroy method on each of their child controls ensuring that - * all controls receive this event. - *

- * Please note: a common problem when overriding onDestroy in - * subclasses is forgetting to call super.onDestroy(). Consider - * carefully whether you should call super.onDestroy() or not, - * especially for {@link org.apache.click.control.Container}s which by default - * call onDestroy on all their child controls as well. - */ - public void onDestroy(); - - /** - * Render the control's HTML representation to the specified buffer. The - * control's {@link java.lang.Object#toString()} method should delegate the - * rendering to the render method for improved performance. - *

- * An example implementation: - *

-     * public class Border extends AbstractContainer {
-     *
-     *     public String toString() {
-     *         int estimatedSizeOfControl = 100;
-     *         HtmlStringBuffer buffer = new HtmlStringBuffer(estimatedSizeOfControl);
-     *         render(buffer);
-     *         return buffer.toString();
-     *     }
-     *
-     *     /**
-     *      * @see Control#render(HtmlStringBuffer)
-     *      */
-     *     public void render(HtmlStringBuffer buffer) {
-     *         buffer.elementStart("div");
-     *         buffer.appendAttribute("name", getName());
-     *         buffer.closeTag();
-     *         buffer.append(getField());
-     *         buffer.elementEnd("div");
-     *     }
-     * }
-     * 
- * - * @param buffer the specified buffer to render the control's output to - */ - public void render(HtmlStringBuffer buffer); - - /** - * Returns true if this control has any - * Behaviors registered, false otherwise. - * - * @return true if this control has any - * Behaviors registered, false otherwise - */ - public boolean hasBehaviors(); - - /** - * Returns the list of behaviors for this control. - * - * @return the list with this control behaviors. - */ - public Set getBehaviors(); - - /** - * Returns true if this control is an Ajax target, false - * otherwise. - *

- * In order for a Control to be considered as an Ajax target it must be - * registered through {@link org.apache.click.ControlRegistry#registerAjaxTarget(org.apache.click.Control) ControlRegistry.registerAjaxTarget}. - *

- * When the Click handles an Ajax request it iterates the Controls - * registered with the {@link org.apache.click.ControlRegistry ControlRegistry} - * and checks if one of them is the Ajax target by calling - * {@link #isAjaxTarget(org.openidentityplatform.openam.click.Context) isAjaxTarget}. If isAjaxTarget - * returns true, Click will process that Control's {@link #getBehaviors() behaviors}. - *

- * Please note: there can only be one target control, so the first - * Control that is identified as the Ajax target will be processed, the other - * controls will be skipped. - *

- * The most common way to check whether a Control is the Ajax target is to - * check if its {@link #getId ID} is available as a request parameter: - * - *

-     * public MyControl extends AbstractControl {
-     *
-     *     ...
-     *
-     *     public boolean isAjaxTarget(Context context) {
-     *         return context.hasRequestParameter(getId());
-     *     }
-     * } 
- * - * Not every scenario can be covered through an ID attribute though. For example - * if an ActionLink is rendered multiple times on the same page, it cannot have an - * ID attribute, as that would lead to duplicate IDs, which isn't allowed by - * the HTML specification. Control implementations has to cater for how the - * control will be targeted. In the case of ActionLink it might check against - * its id, and if that isn't available check against its name. - * - * @param context the request context - * @return true if this control is an Ajax target, false - * otherwise - */ - public boolean isAjaxTarget(Context context); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ControlRegistry.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ControlRegistry.java deleted file mode 100644 index ecc271bb5e..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ControlRegistry.java +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.openidentityplatform.openam.click.service.ConfigService; -import org.openidentityplatform.openam.click.service.LogService; -import org.apache.commons.lang.Validate; - -/** - * Provides a centralized registry where Controls can be registered and interact - * with the Click runtime. - *

- * The primary use of the ControlRegistry is for Controls to register themselves - * as potential targets of Ajax requests - * (If a control is an Ajax request target, it's onProcess() - * method is invoked while other controls are not processed). - *

- * Registering controls as Ajax targets serves a dual purpose. In addition to - * being potential Ajax targets, these controls will have all their Behaviors - * processed by the Click runtime. - *

- * Thus the ControlRegistry provides the Click runtime with easy access to Controls - * that want to be processed for Ajax requests. It also provides quick access - * to Controls that have Behaviors, and particularly AjaxBehaviors that want to - * handle and respond to Ajax requests. - * - *

Register Control as an Ajax Target

- * Below is an example of a Control registering itself as an Ajax target: - * - *
- * public class AbstractControl implements Control {
- *
- *     public void addBehavior(Behavior behavior) {
- *         getBehaviors().add(behavior);
- *         // Adding a behavior also registers the Control as an Ajax target
- *         ControlRegistry.registerAjaxTarget(this);
- *     }
- * } 
- * - *

Register Interceptor

- * Below is an example of a Container registering a Behavior in order to intercept - * and decorate its child controls: - * - *
- * public class MyContainer extends AbstractContainer {
- *
- *     public void onInit() {
- *         Behavior controlInterceptor = getInterceptor();
- *         ControlRegistry.registerInterceptor(this, controlInterceptor);
- *     }
- *
- *     private Behavior getInterceptor() {
- *         Behavior controlInterceptor = new Behavior() {
- *
- *             // This method is invoked before the controls are rendered to the client
- *             public void preResponse(Control source) {
- *                 // Here we can add a CSS class attribute to each child control
- *                 addCssClassToChildControls();
- *             }
- *
- *             // This method is invoked before the HEAD elements are retrieved for each Control
- *             public void preRenderHeadElements(Control source) {
- *             }
- *
- *             // This method is invoked before the Control onDestroy event
- *             public void preDestroy(Control source) {
- *             }
- *         };
- *         return controlInterceptor;
- *     }
- * } 
- */ -public class ControlRegistry { - - // Constants -------------------------------------------------------------- - - /** The thread local registry holder. */ - private static final ThreadLocal THREAD_LOCAL_REGISTRY_STACK = - new ThreadLocal<>(); - - // Variables -------------------------------------------------------------- - - /** The set of Ajax target controls. */ - Set ajaxTargetControls; - - /** The list of registered interceptors. */ - List interceptors; - - /** The application log service. */ - LogService logger; - - // Constructors ----------------------------------------------------------- - - /** - * Construct the ControlRegistry with the given ConfigService. - * - * @param configService the click application configuration service - */ - public ControlRegistry(ConfigService configService) { - this.logger = configService.getLogService(); - } - - // Public Methods --------------------------------------------------------- - - /** - * Return the thread local ControlRegistry instance. - * - * @return the thread local ControlRegistry instance. - * @throws RuntimeException if a ControlRegistry is not available on the - * thread - */ - public static ControlRegistry getThreadLocalRegistry() { - return getRegistryStack().peek(); - } - - /** - * Returns true if a ControlRegistry instance is available on the current - * thread, false otherwise. - *

- * Unlike {@link #getThreadLocalRegistry()} this method can safely be used - * and will not throw an exception if a ControlRegistry is not available on - * the current thread. - * - * @return true if an ControlRegistry instance is available on the - * current thread, false otherwise - */ - public static boolean hasThreadLocalRegistry() { - ControlRegistry.RegistryStack registryStack = THREAD_LOCAL_REGISTRY_STACK.get(); - if (registryStack == null) { - return false; - } - return !registryStack.isEmpty(); - } - - /** - * Register the control to be processed by the Click runtime if the control - * is the Ajax target. A control is an Ajax target if the - * {@link Control#isAjaxTarget(Context)} method returns true. - * Once a target control is identified, Click invokes its - * {@link Control#onProcess()} method. - *

- * This method serves a dual purpose as all controls registered here - * will also have their Behaviors (if any) processed. Processing - * {@link Behavior Behaviors} - * means their interceptor methods will be invoked during the request - * life cycle, passing the control as the argument. - * - * @param control the control to register as an Ajax target - */ - public static void registerAjaxTarget(Control control) { - if (control == null) { - throw new IllegalArgumentException("control cannot be null"); - } - - ControlRegistry instance = getThreadLocalRegistry(); - instance.internalRegisterAjaxTarget(control); - } - - /** - * Register a control event interceptor for the given Control and Behavior. - * The control will be passed as the source control to the Behavior - * interceptor methods: - * {@link org.apache.click.Behavior#preRenderHeadElements(org.apache.click.Control) preRenderHeadElements(Control)}, - * {@link org.apache.click.Behavior#preResponse(org.apache.click.Control) preResponse(Control)} and - * {@link org.apache.click.Behavior#preDestroy(org.apache.click.Control) preDestroy(Control)}. - * - * @param control the interceptor source control - * @param controlInterceptor the control interceptor to register - */ - public static void registerInterceptor(Control control, Behavior controlInterceptor) { - if (control == null) { - throw new IllegalArgumentException("control cannot be null"); - } - if (controlInterceptor == null) { - throw new IllegalArgumentException("control interceptor cannot be null"); - } - - ControlRegistry instance = getThreadLocalRegistry(); - instance.internalRegisterInterceptor(control, controlInterceptor); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Allow the registry to handle the error that occurred. - * - * @param throwable the error which occurred during processing - */ - protected void errorOccurred(Throwable throwable) { - clear(); - } - - // Package Private Methods ------------------------------------------------ - - /** - * Remove all interceptors and ajax target controls from this registry. - */ - void clear() { - if (hasInterceptors()) { - getInterceptors().clear(); - } - - if (hasAjaxTargetControls()) { - getAjaxTargetControls().clear(); - } - } - - /** - * Register the AJAX target control. - * - * @param control the AJAX target control - */ - void internalRegisterAjaxTarget(Control control) { - Validate.notNull(control, "Null control parameter"); - getAjaxTargetControls().add(control); - } - - /** - * Register the source control and associated interceptor. - * - * @param source the interceptor source control - * @param controlInterceptor the control interceptor to register - */ - void internalRegisterInterceptor(Control source, Behavior controlInterceptor) { - Validate.notNull(source, "Null source parameter"); - Validate.notNull(controlInterceptor, "Null interceptor parameter"); - - ControlRegistry.InterceptorHolder interceptorHolder = new ControlRegistry.InterceptorHolder(source, controlInterceptor); - - // Guard against adding duplicate interceptors - List localInterceptors = getInterceptors(); - if (!localInterceptors.contains(interceptorHolder)) { - localInterceptors.add(interceptorHolder); - } - } - - void processPreResponse(Context context) { - if (hasAjaxTargetControls()) { - for (Control control : getAjaxTargetControls()) { - for (Behavior behavior : control.getBehaviors()) { - behavior.preResponse(control); - } - } - } - - if (hasInterceptors()) { - for (ControlRegistry.InterceptorHolder interceptorHolder : getInterceptors()) { - Behavior interceptor = interceptorHolder.getInterceptor(); - Control control = interceptorHolder.getControl(); - interceptor.preResponse(control); - } - } - } - - void processPreRenderHeadElements(Context context) { - if (hasAjaxTargetControls()) { - for (Control control : getAjaxTargetControls()) { - for (Behavior behavior : control.getBehaviors()) { - behavior.preRenderHeadElements(control); - } - } - } - - if (hasInterceptors()) { - for (ControlRegistry.InterceptorHolder interceptorHolder : getInterceptors()) { - Behavior interceptor = interceptorHolder.getInterceptor(); - Control control = interceptorHolder.getControl(); - interceptor.preRenderHeadElements(control); - } - } - } - - void processPreDestroy(Context context) { - if (hasAjaxTargetControls()) { - for (Control control : getAjaxTargetControls()) { - for (Behavior behavior : control.getBehaviors()) { - behavior.preDestroy(control); - } - } - } - - if (hasInterceptors()) { - for (ControlRegistry.InterceptorHolder interceptorHolder : getInterceptors()) { - Behavior interceptor = interceptorHolder.getInterceptor(); - Control control = interceptorHolder.getControl(); - interceptor.preDestroy(control); - } - } - } - - /** - * Checks if any AJAX target control have been registered. - */ - boolean hasAjaxTargetControls() { - if (ajaxTargetControls == null || ajaxTargetControls.isEmpty()) { - return false; - } - return true; - } - - /** - * Return the set of potential Ajax target controls. - * - * @return the set of potential Ajax target controls - */ - Set getAjaxTargetControls() { - if (ajaxTargetControls == null) { - ajaxTargetControls = new LinkedHashSet(); - } - return ajaxTargetControls; - } - - /** - * Checks if any control interceptors have been registered. - */ - boolean hasInterceptors() { - if (interceptors == null || interceptors.isEmpty()) { - return false; - } - return true; - } - - /** - * Return the set of registered control interceptors. - * - * @return set of registered interceptors - */ - List getInterceptors() { - if (interceptors == null) { - interceptors = new ArrayList<>(); - } - return interceptors; - } - - /** - * Adds the specified ControlRegistry on top of the registry stack. - * - * @param controlRegistry the ControlRegistry to add - */ - static void pushThreadLocalRegistry(ControlRegistry controlRegistry) { - getRegistryStack().push(controlRegistry); - } - - /** - * Remove and return the controlRegistry instance on top of the - * registry stack. - * - * @return the controlRegistry instance on top of the registry stack - */ - static ControlRegistry popThreadLocalRegistry() { - ControlRegistry.RegistryStack registryStack = getRegistryStack(); - ControlRegistry controlRegistry = registryStack.pop(); - - if (registryStack.isEmpty()) { - THREAD_LOCAL_REGISTRY_STACK.set(null); - } - - return controlRegistry; - } - - static ControlRegistry.RegistryStack getRegistryStack() { - ControlRegistry.RegistryStack registryStack = THREAD_LOCAL_REGISTRY_STACK.get(); - - if (registryStack == null) { - registryStack = new ControlRegistry.RegistryStack(2); - THREAD_LOCAL_REGISTRY_STACK.set(registryStack); - } - - return registryStack; - } - - /** - * Provides an unsynchronized Stack. - */ - static class RegistryStack extends ArrayList { - - /** Serialization version indicator. */ - private static final long serialVersionUID = 1L; - - /** - * Create a new RegistryStack with the given initial capacity. - * - * @param initialCapacity specify initial capacity of this stack - */ - private RegistryStack(int initialCapacity) { - super(initialCapacity); - } - - /** - * Pushes the ControlRegistry onto the top of this stack. - * - * @param controlRegistry the ControlRegistry to push onto this stack - * @return the ControlRegistry pushed on this stack - */ - private ControlRegistry push(ControlRegistry controlRegistry) { - add(controlRegistry); - - return controlRegistry; - } - - /** - * Removes and return the ControlRegistry at the top of this stack. - * - * @return the ControlRegistry at the top of this stack - */ - private ControlRegistry pop() { - ControlRegistry controlRegistry = peek(); - - remove(size() - 1); - - return controlRegistry; - } - - /** - * Looks at the ControlRegistry at the top of this stack without - * removing it. - * - * @return the ControlRegistry at the top of this stack - */ - private ControlRegistry peek() { - int length = size(); - - if (length == 0) { - String msg = "No ControlRegistry available on ThreadLocal Registry Stack"; - throw new RuntimeException(msg); - } - - return get(length - 1); - } - } - - static class InterceptorHolder { - - private Behavior interceptor; - - private Control control; - - public InterceptorHolder(Control control, Behavior interceptor) { - this.control = control; - this.interceptor = interceptor; - } - - public Behavior getInterceptor() { - return interceptor; - } - - public void setInterceptor(Behavior interceptor) { - this.interceptor = interceptor; - } - - public Control getControl() { - return control; - } - - public void setControl(Control control) { - this.control = control; - } - - /** - * @see Object#equals(java.lang.Object) - * - * @param o the reference object with which to compare - * @return true if this object equals the given object - */ - @Override - public boolean equals(Object o) { - - //1. Use the == operator to check if the argument is a reference to this object. - if (o == this) { - return true; - } - - //2. Use the instanceof operator to check if the argument is of the correct type. - if (!(o instanceof ControlRegistry.InterceptorHolder)) { - return false; - } - - //3. Cast the argument to the correct type. - ControlRegistry.InterceptorHolder that = (ControlRegistry.InterceptorHolder) o; - - boolean equals = this.control == null ? that.control == null : this.control.equals(that.control); - if (!equals) { - return false; - } - - return this.interceptor == null ? that.interceptor == null : this.interceptor.equals(that.interceptor); - } - - /** - * @see java.lang.Object#hashCode() - * - * @return the InterceptorHolder hashCode - */ - @Override - public int hashCode() { - int result = 17; - result = 37 * result + (control == null ? 0 : control.hashCode()); - result = 37 * result + (interceptor == null ? 0 : interceptor.hashCode()); - return result; - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/Page.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/Page.java deleted file mode 100644 index aa65de9e1d..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/Page.java +++ /dev/null @@ -1,1362 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -import java.io.Serializable; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.openidentityplatform.openam.click.element.Element; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.apache.click.util.Format; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.openidentityplatform.openam.click.util.PageImports; -import org.apache.commons.lang.StringUtils; - -/** - * Provides the Page request event handler class. - *

- * The Page class plays a central role in Click applications defining how the - * application's pages are processed and rendered. All application pages - * must extend the base Page class, and provide a no arguments constructor. - * - *

Page Execution Sequence

- * - * The default Page execution path for a GET request is: - *
    - *
  1. - * no-args constructor invoked to create a new Page instance. - * At this point no dependencies have been injected into the Page, and any - * request information is not available. You should put any "static" - * page initialization code, which doesn't depend upon request information, - * in the constructor. This will enable subclasses to have this code - * automatically initialized when they are created. - *
  2. - *
  3. - * {@link #format} property is set - *
  4. - *
  5. - * {@link #headers} property is set - *
  6. - *
  7. - * {@link #path} property is set - *
  8. - *
  9. - * {@link #onSecurityCheck()} method called to check whether the page should - * be processed. This method should return true if the Page should continue - * to be processed, or false otherwise. - *
  10. - *
  11. - * {@link #onInit()} method called to complete the initialization of the page - * after all the dependencies have been set. This is where you should put - * any "dynamic" page initialization code which depends upon the request or any - * other dependencies. - *

    - * Form and field controls must be fully initialized by the time this method - * has completed. - *

  12. - *
  13. - * ClickServlet processes all the page {@link #controls} - * calling their {@link org.apache.click.Control#onProcess()} method. If any of these - * controls return false, continued control and page processing will be aborted. - *
  14. - *
  15. - * {@link #onGet()} method called for any additional GET related processing. - *

    - * Form and field controls should NOT be created or initialized at this - * point as the control processing stage has already been completed. - *

  16. - *
  17. - * {@link #onRender()} method called for any pre-render processing. This - * method is often use to perform database queries to load information for - * rendering tables. - *

    - * Form and field controls should NOT be created or initialized at this - * point as the control processing stage has already been completed. - *

  18. - *
  19. - * ClickServlet renders the page merging the {@link #model} with the - * Velocity template defined by the {@link #getTemplate()} property. - *
  20. - *
  21. - * {@link #onDestroy()} method called to clean up any resources. This method - * is guaranteed to be called, even if an exception occurs. You can use - * this method to close resources like database connections or Hibernate - * sessions. - *
  22. - *
- * - * For POST requests the default execution path is identical, except the - * {@link #onPost()} method is called instead of {@link #onGet()}. The POST - * request page execution sequence is illustrated below: - *

- * - * - *

- * A good way to see the page event execution order is to view the log when - * the application mode is set to trace: - * - *

- * [Click] [debug] GET http://localhost:8080/quickstart/home.htm
- * [Click] [trace]    invoked: HomePage.<<init>>
- * [Click] [trace]    invoked: HomePage.onSecurityCheck() : true
- * [Click] [trace]    invoked: HomePage.onInit()
- * [Click] [trace]    invoked: HomePage.onGet()
- * [Click] [trace]    invoked: HomePage.onRender()
- * [Click] [info ]    renderTemplate: /home.htm - 6 ms
- * [Click] [trace]    invoked: HomePage.onDestroy()
- * [Click] [info ] handleRequest:  /home.htm - 24 ms  
- * - *

Rendering Pages

- * - * When a Velocity template is rendered the ClickServlet uses Pages: - *
    - *
  • {@link #getTemplate()} to find the Velocity template.
  • - *
  • {@link #model} to populate the Velocity Context
  • - *
  • {@link #format} to add to the Velocity Context
  • - *
  • {@link #getContentType()} to set as the HttpServletResponse content type
  • - *
  • {@link #headers} to set as the HttpServletResponse headers
  • - *
- * - * These Page properties are also used when rendering JSP pages. - */ -public class Page implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * The global page messages bundle name:   click-page. - */ - public static final String PAGE_MESSAGES = "click-page"; - - /** - * The Page action request parameter:   "pageAction". - */ - public final static String PAGE_ACTION = "pageAction"; - - // Instance Variables ----------------------------------------------------- - - /** The list of page controls. */ - protected List controls; - - /** - * The list of page HTML HEAD elements including: Javascript imports, - * Css imports, inline Javascript and inline Css. - */ - protected List headElements; - - /** The Velocity template formatter object. */ - protected Format format; - - /** The forward path. */ - protected String forward; - - /** The HTTP response headers. */ - protected Map headers; - - /** The map of localized page resource messages. **/ - protected transient Map messages; - - /** - * The page model. For Velocity templates the model is used to populate the - * Velocity context. For JSP pages the model values are set as named - * request attributes. - */ - protected Map model = new HashMap(); - - /** The Page header imports. */ - protected transient PageImports pageImports; - - /** The path of the page template to render. */ - protected String path; - - /** The redirect path. */ - protected String redirect; - - /** - * The page is stateful and should be saved to the users HttpSession - * between requests, default value is false. - * - * @deprecated stateful pages are not supported anymore, use stateful - * Controls instead - */ - protected boolean stateful; - - /** The path of the page border template to render.*/ - protected String template; - - /** - * Indicates whether Control head elements should be included in the - * page template, default value is true. - */ - protected boolean includeControlHeadElements = true; - - // Event Handlers --------------------------------------------------------- - - /** - * The on Security Check event handler. This event handler is invoked after - * the pages constructor has been called and all the page properties have - * been set. - *

- * Security check provides the Page an opportunity to check the users - * security credentials before processing the Page. - *

- * If security check returns true the Page is processed as - * normal. If the method returns then no other event handlers are invoked - * (except onDestroy() and no page controls are processed. - *

- * If the method returns false, the forward or redirect property should be - * set to send the request to another Page. - *

- * By default this method returns true, subclass may override this method - * to provide their security authorization/authentication mechanism. - * - * @return true by default, subclasses may override this method - */ - public boolean onSecurityCheck() { - return true; - } - - /** - * The on Initialization event handler. This event handler is invoked after - * the {@link #onInit()} method has been called. - *

- * Subclasses should place any initialization code which has dependencies - * on the context or other properties in this method. Generally light - * weight initialization code should be placed in the Pages constructor. - *

- * Time consuming operations such as fetching the results of a database - * query should not be placed in this method. These operations should be - * performed in the {@link #onRender()}, {@link #onGet()} or - * {@link #onPost()} methods so that other event handlers may take - * alternative execution paths without performing these expensive operations. - *

- * Please Note however the qualifier for the previous statement is - * that all form and field controls must be fully initialized before they - * are processed, which is after the onInit() method has - * completed. After this point their onProcess() methods will be - * invoked by the ClickServlet. - *

- * Select controls in particular must have their option list values populated - * before the form is processed otherwise field validations cannot be performed. - *

- * For initializing page controls the best practice is to place all the - * control creation code in the pages constructor, and only place any - * initialization code in the onInit() method which has an external - * dependency to the context or some other object. By following this practice - * it is easy to see what code is "design time" initialization code and what - * is "runtime initialization code". - *

- * When subclassing pages which also use the onInit() method is - * is critical you call the super.onInit() method first, for - * example: - *

-     * public void onInit() {
-     *     super.onInit();
-     *
-     *     // Initialization code
-     *     ..
-     * } 
- */ - public void onInit() { - } - - /** - * The on Get request event handler. This event handler is invoked if the - * HTTP request method is "GET". - *

- * The event handler is invoked after {@link #onSecurityCheck()} has been - * called and all the Page {@link #controls} have been processed. If either - * the security check or one of the controls cancels continued event - * processing the onGet() method will not be invoked. - * - *

Important Note

- * - * Form and field controls should NOT be created - * or initialized at this point as the control processing stage has already - * been completed. Select option list values should also be populated - * before the control processing stage is performed so that they can - * validate the submitted values. - */ - public void onGet() { - } - - /** - * The on Post request event handler. This event handler is invoked if the - * HTTP request method is "POST". - *

- * The event handler is invoked after {@link #onSecurityCheck()} has been - * called and all the Page {@link #controls} have been processed. If either - * the security check or one of the controls cancels continued event - * processing the onPost() method will not be invoked. - * - *

Important Note

- * - * Form and field controls should NOT be created - * or initialized at this point as the control processing stage has already - * been completed. Select option list values should also be populated - * before the control processing stage is performed so that they can - * validate the submitted values. - */ - public void onPost() { - } - - /** - * The on render event handler. This event handler is invoked prior to the - * page being rendered. - *

- * This method will not be invoked if either the security check or one of - * the controls cancels continued event processing. - *

- * The on render method is typically used to populate tables performing some - * database intensive operation. By putting the intensive operations in the - * on render method they will not be performed if the user navigates away - * to a different page. - *

- * If you have code which you are using in both the onGet() and - * onPost() methods, use the onRender() method instead. - * - *

Important Note

- * - * Form and field controls should NOT be created - * or initialized at this point as the control processing stage has already - * been completed. Select option list values should also be populated - * before the control processing stage is performed so that they can - * validate the submitted values. - */ - public void onRender() { - } - - /** - * The on Destroy request event handler. Subclasses may override this method - * to add any resource clean up code. - *

- * This method is guaranteed to be called before the Page object reference - * goes out of scope and is available for garbage collection. - */ - public void onDestroy() { - } - - // Public Methods --------------------------------------------------------- - - /** - * Add the control to the page. The control will be added to the page model - * using the control name as the key. The Controls parent property will - * also be set to the page instance. - *

- * Please note: if the page contains a control with the same name as - * the given control, that control will be replaced by the given control. - * If a control has no name defined it cannot be replaced. - * - * @param control the control to add to the page - * @throws IllegalArgumentException if the control is null or if the name - * of the control is not defined - */ - public void addControl(Control control) { - if (control == null) { - throw new IllegalArgumentException("Null control parameter"); - } - if (StringUtils.isBlank(control.getName())) { - throw new IllegalArgumentException("Control name not defined: " - + control.getClass()); - } - - // Check if page already contains a named value - Object currentValue = getModel().get(control.getName()); - if (currentValue != null && currentValue instanceof Control) { - Control currentControl = (Control) currentValue; - replaceControl(currentControl, control); - return; - } - - // Note: set parent first as setParent might veto further processing - control.setParent(this); - - getControls().add(control); - addModel(control.getName(), control); - } - - /** - * Remove the control from the page. The control will be removed from the - * pages model and the control parent property will be set to null. - * - * @param control the control to remove - * @throws IllegalArgumentException if the control is null, or if the name - * of the control is not defined - */ - public void removeControl(Control control) { - if (control == null) { - throw new IllegalArgumentException("Null control parameter"); - } - if (StringUtils.isBlank(control.getName())) { - throw new IllegalArgumentException("Control name not defined"); - } - - getControls().remove(control); - getModel().remove(control.getName()); - - control.setParent(null); - } - - /** - * Return the list of page Controls. - * - * @return the list of page Controls - */ - public List getControls() { - if (controls == null) { - controls = new ArrayList(); - } - return controls; - } - - /** - * Return true if the page has any controls defined. - * - * @return true if the page has any controls defined - */ - public boolean hasControls() { - return (controls != null) && !controls.isEmpty(); - } - - /** - * Return the request context of the page. - * - * @return the request context of the page - */ - public Context getContext() { - return Context.getThreadLocalContext(); - } - - /** - * Return the HTTP response content type. By default this method returns - * "text/html". - *

- * If the request specifies a character encoding via - * If {@link jakarta.servlet.ServletRequest#getCharacterEncoding()} - * then this method will return "text/html; charset=encoding". - *

- * The ClickServlet uses the pages content type for setting the - * HttpServletResponse content type. - * - * @return the HTTP response content type - */ - public String getContentType() { - String charset = getContext().getRequest().getCharacterEncoding(); - - if (charset == null) { - return "text/html"; - - } else { - return "text/html; charset=" + charset; - } - } - - /** - * Return the Velocity template formatter object. - *

- * The ClickServlet adds the format object to the Velocity context using - * the key "format" so that it can be used in the page template. - * - * @return the Velocity template formatter object - */ - public Format getFormat() { - return format; - } - - /** - * Set the Velocity template formatter object. - * - * @param value the Velocity template formatter object. - */ - public void setFormat(Format value) { - format = value; - } - - /** - * Return the path to forward the request to. - *

- * If the {@link #forward} property is not null it will be used to forward - * the request to in preference to rendering the template defined by the - * {@link #path} property. The request is forwarded using the - * RequestDispatcher. - *

- * See also {@link #getPath()}, {@link #getRedirect()} - * - * @return the path to forward the request to - */ - public String getForward() { - return forward; - } - - /** - * Set the path to forward the request to. - *

- * If the {@link #forward} property is not null it will be used to forward - * the request to in preference to rendering the template defined by the - * {@link #path} property. The request is forwarded using the Servlet - * RequestDispatcher. - *

- * If forward paths start with a "/" - * character the forward path is - * relative to web applications root context, otherwise the path is - * relative to the requests current location. - *

- * For example given a web application deployed to context mycorp - * with the pages: - *

-     *  /index.htm
-     *  /customer/search.htm
-     *  /customer/details.htm
-     *  /customer/management/add-customer.htm 
- * - * To forward to the customer search.htm page from - * the web app root you could set forward as - * setForward("/customer/search.htm") - * or setForward("customer/search.htm"). - *

- * If a user was currently viewing the add-customer.htm - * to forward to customer details.htm you could - * set forward as - * setForward("/customer/details.htm") - * or setForward("../details.htm"). - *

- * See also {@link #setPath(String)}, {@link #setRedirect(String)} - * - * @param value the path to forward the request to - */ - public void setForward(String value) { - forward = value; - } - - /** - * The Page instance to forward the request to. The given Page object - * must have a valid {@link #path} defined, as the {@link #path} specifies - * the location to forward to. - * - * @see #setForward(java.lang.String) - * - * @param page the Page object to forward the request to. - */ - public void setForward(Page page) { - if (page == null) { - throw new IllegalArgumentException("Null page parameter"); - } - if (page.getPath() == null) { - throw new IllegalArgumentException("Page has no path defined"); - } - setForward(page.getPath()); - getContext().setRequestAttribute(ClickServlet.FORWARD_PAGE, page); - } - - /** - * Set the request to forward to the given page class. - * - * @see #setForward(java.lang.String) - * - * @param pageClass the class of the Page to forward the request to - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - public void setForward(Class pageClass) { - String target = getContext().getPagePath(pageClass); - - // If page class maps to a jsp, convert to htm which allows ClickServlet - // to process the redirect - if (target != null && target.endsWith(".jsp")) { - target = StringUtils.replaceOnce(target, ".jsp", ".htm"); - } - setForward(target); - } - - /** - * Return the map of HTTP header to be set in the HttpServletResponse. - * - * @return the map of HTTP header to be set in the HttpServletResponse - */ - public Map getHeaders() { - if (headers == null) { - headers = new HashMap(); - } - return headers; - } - - /** - * Return true if the page has headers, false otherwise. - * - * @return true if the page has headers, false otherwise - */ - public boolean hasHeaders() { - return headers != null && !headers.isEmpty(); - } - - /** - * Set the named header with the given value. Value can be either a String, - * Date or Integer. - * - * @param name the name of the header - * @param value the value of the header, either a String, Date or Integer - */ - public void setHeader(String name, Object value) { - if (name == null) { - throw new IllegalArgumentException("Null header name parameter"); - } - - getHeaders().put(name, value); - } - - /** - * Set the map of HTTP header to be set in the HttpServletResponse. - * - * @param value the map of HTTP header to be set in the HttpServletResponse - */ - public void setHeaders(Map value) { - headers = value; - } - - /** - * @deprecated use the new {@link #getHeadElements()} instead - * - * @return the HTML includes statements for the control stylesheet and - * JavaScript files - */ - public final String getHtmlImports() { - throw new UnsupportedOperationException("Use getHeadElements instead"); - } - - /** - * Return the list of HEAD {@link org.apache.click.element.Element elements} - * to be included in the page. Example HEAD elements include - * {@link org.apache.click.element.JsImport JsImport}, - * {@link org.apache.click.element.JsScript JsScript}, - * {@link org.apache.click.element.CssImport CssImport} and - * {@link org.apache.click.element.CssStyle CssStyle}. - *

- * Pages can contribute their own list of HEAD elements by overriding - * this method. - *

- * The recommended approach when overriding this method is to use - * lazy loading to ensure the HEAD elements are only added - * once and when needed. For example: - * - *

-     * public MyPage extends Page {
-     *
-     *     public List getHeadElements() {
-     *         // Use lazy loading to ensure the JS is only added the
-     *         // first time this method is called.
-     *         if (headElements == null) {
-     *             // Get the head elements from the super implementation
-     *             headElements = super.getHeadElements();
-     *
-     *             // Include the page's external Javascript resource
-     *             JsImport jsImport = new JsImport("/mycorp/js/mypage.js");
-     *             headElements.add(jsImport);
-     *
-     *             // Include the page's external Css resource
-     *             CssImport cssImport = new CssImport("/mycorp/js/mypage.css");
-     *             headElements.add(cssImport);
-     *         }
-     *         return headElements;
-     *     }
-     * } 
- * - * Alternatively one can add the HEAD elements in the Page constructor: - * - *
-     * public MyPage extends Page {
-     *
-     *     public MyPage() {
-     *         JsImport jsImport = new JsImport("/mycorp/js/mypage.js");
-     *         getHeadElements().add(jsImport);
-     *
-     *         CssImport cssImport = new CssImport("/mycorp/js/mypage.css");
-     *         getHeadElements().add(cssImport);
-     *     }
-     * } 
- * - * One can also add HEAD elements from event handler methods such as - * {@link #onInit()}, {@link #onGet()}, {@link #onPost()}, {@link #onRender()} - * etc. - *

- * The order in which JS and CSS files are included will be preserved in the - * page. - *

- * Note: this method must never return null. If no HEAD elements - * are available this method must return an empty {@link java.util.List}. - *

- * Also note: a common problem when overriding getHeadElements in - * subclasses is forgetting to call super.getHeadElements. Consider - * carefully whether you should call super.getHeadElements or not. - * - * @return the list of HEAD elements to be included in the page - */ - public List getHeadElements() { - if (headElements == null) { - headElements = new ArrayList(2); - } - return headElements; - } - - /** - * Return the localized Page resource message for the given resource - * name or null if not found. The resource message returned will use the - * Locale obtained from the Context. - *

- * Pages can define text properties files to store localized messages. These - * properties files must be stored on the Page class path with a name - * matching the class name. For example: - *

- * The page class: - *

-     *  package com.mycorp.pages;
-     *
-     *  public class Login extends Page {
-     *     .. 
- * - * The page class property filenames and their path: - *
-     *  /com/mycorp/pages/Login.properties
-     *  /com/mycorp/pages/Login_en.properties
-     *  /com/mycorp/pages/Login_fr.properties 
- * - * Page messages can also be defined in the optional global messages - * bundle: - * - *
-     *  /click-page.properties 
- * - * To define global page messages simply add click-page.properties - * file to your application's class path. Message defined in this properties - * file will be available to all of your application pages. - *

- * Note messages in your page class properties file will override any - * messages in the global click-page.properties file. - *

- * Page messages can be accessed directly in the page template using - * the $messages reference. For examples: - * - *

-     * $messages.title 
- * - * Please see the {@link org.apache.click.util.MessagesMap} adaptor for more - * details. - * - * @param name resource name of the message - * @return the named localized message for the page or null if no message - * was found - */ - public String getMessage(String name) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - return getMessages().get(name); - } - - /** - * Return the formatted page message for the given resource name and - * message format arguments or null if no message was found. The resource - * message returned will use the Locale obtained from the Context. - *

- * {@link #getMessage(java.lang.String)} is invoked to retrieve the message - * for the specified name. - * - * @param name resource name of the message - * @param args the message arguments to format - * @return the named localized message for the page or null if no message - * was found - */ - public String getMessage(String name, Object... args) { - String value = getMessage(name); - - return MessageFormat.format(value, args); - } - - /** - * Return a Map of localized messages for the Page. The messages returned - * will use the Locale obtained from the Context. - * - * @see #getMessage(String) - * - * @return a Map of localized messages for the Page - * @throws IllegalStateException if the context for the Page has not be set - */ - public Map getMessages() { - if (messages == null) { - if (getContext() != null) { - messages = getContext().createMessagesMap(getClass(), PAGE_MESSAGES); - - } else { - String msg = "Context not set cannot initialize messages"; - throw new IllegalStateException(msg); - } - } - return messages; - } - - /** - * Add the named object value to the Pages model map. - *

- * Please note: if the Page contains an object with a matching name, - * that object will be replaced by the given value. - * - * @param name the key name of the object to add - * @param value the object to add - * @throws IllegalArgumentException if the name or value parameters are - * null - */ - public void addModel(String name, Object value) { - if (name == null) { - String msg = "Cannot add null parameter name to " - + getClass().getName() + " model"; - throw new IllegalArgumentException(msg); - } - if (value == null) { - String msg = "Cannot add null " + name + " parameter " - + "to " + getClass().getName() + " model"; - throw new IllegalArgumentException(msg); - } - - getModel().put(name, value); - } - - /** - * Return the Page's model map. The model is used populate the - * Velocity Context with is merged with the page template before rendering. - * - * @return the Page's model map - */ - public Map getModel() { - return model; - } - - /** - * Return the Page header imports. - *

- * PageImports are used define the CSS and JavaScript imports and blocks - * to be included in the page template. - *

- * The PageImports object will be included in the Page template when the - * following methods are invoked: - *

    - *
  • {@link ClickServlet#createTemplateModel(Page)} - for template pages
  • - *
  • {@link ClickServlet#setRequestAttributes(Page)} - for JSP pages
  • - *
- *

- * If you need to tailor the page imports rendered, override this method - * and modify the PageImports object returned. - *

- * If you need to create a custom PageImports, override the method - * {@link ClickServlet#createPageImports(org.openidentityplatform.openam.click.Page)} - * - * @deprecated use the new {@link #getHeadElements()} instead - * - * @return the Page header imports - */ - public PageImports getPageImports() { - return pageImports; - } - - /** - * Set the Page header imports. - *

- * PageImports are used define the CSS and JavaScript imports and blocks - * to be included in the page template. - *

- * The PageImports references will be included in the Page model when the - * following methods are invoked: - *

    - *
  • {@link ClickServlet#createTemplateModel(Page)} - for template pages
  • - *
  • {@link ClickServlet#setRequestAttributes(Page)} - for JSP pages
  • - *
- *

- * If you need to tailor the page imports rendered, override the - * {@link #getPageImports()} method and modify the PageImports object - * returned. - *

- * If you need to create a custom PageImports, override the method - * {@link ClickServlet#createPageImports(org.openidentityplatform.openam.click.Page)} - * - * @deprecated use the new {@link #getHeadElements()} instead - * - * @param pageImports the new pageImports instance to set - */ - public void setPageImports(PageImports pageImports) { - this.pageImports = pageImports; - } - - /** - * Return the path of the Template or JSP to render. - *

- * If this method returns null, Click will not perform any rendering. - * This is useful when you want to stream or write directly to the - * HttpServletResponse. - *

- * See also {@link #getForward()}, {@link #getRedirect()} - * - * @return the path of the Template or JSP to render - */ - public String getPath() { - return path; - } - - /** - * Set the path of the Template or JSP to render. - *

- * By default Click will set the path to the requested page url. Meaning - * if the url /edit-customer.htm is requested, path will be set - * to /edit-customer.htm. - *

- * Here is an example if you want to change the path to a different Template: - *

- *

-     * public void onGet() {
-     *     setPath("/some-other-template.htm");
-     * }
- * And here is an example if you want to change the path to a different JSP. - *
-     * public void onGet() {
-     *     setPath("/some-other-jsp.jsp");
-     * }
- *

- * If path is set to null, Click will not perform any rendering. - * This is useful when you want to stream or write directly to the - * HttpServletResponse. - *

- * See also {@link #setForward(String)}, {@link #setRedirect(String)} - * - * @param value the path of the Template or JSP to render - */ - public void setPath(String value) { - path = value; - } - - /** - * Return the path to redirect the request to. - *

- * If the {@link #redirect} property is not null it will be used to redirect - * the request in preference to {@link #forward} or {@link #path} properties. - * The request is redirected to using the HttpServletResponse.setRedirect() - * method. - *

- * See also {@link #getForward()}, {@link #getPath()} - * - * @return the path to redirect the request to - */ - public String getRedirect() { - return redirect; - } - - /** - * Return true if the page is stateful and should be saved in the users - * HttpSession between requests, default value is false. - * - * @deprecated stateful pages are not supported anymore, use stateful - * Controls instead - * - * @return true if the page is stateful and should be saved in the users - * session - */ - public boolean isStateful() { - return stateful; - } - - /** - * Set whether the page is stateful and should be saved in the users - * HttpSession between requests. - *

- * Click will synchronize on the page instance. This ensures that if - * multiple requests arrive from the same user for the page, only one - * request can access the page at a time. - *

- * Stateful pages are stored in the HttpSession using the key - * page.getClass().getName(). - *

- * It is worth noting that Click checks a Page's stateful property after - * each request. Thus it becomes possible to enable a stateful Page for a - * number of requests and then setting it to false again at which - * point Click will remove the Page from the HttpSession, freeing up memory - * for the server. - * - * @deprecated stateful pages are not supported anymore, use stateful - * Controls instead - * - * @param stateful the flag indicating whether the page should be saved - * between user requests - */ - public void setStateful(boolean stateful) { - this.stateful = stateful; - if (isStateful()) { - getContext().getSession(); - } - } - - /** - * Return true if the Control head elements should be included in the page - * template, false otherwise. Default value is true. - * - * @see #setIncludeControlHeadElements(boolean) - * - * @return true if the Control head elements should be included in the page - * template, false otherwise - */ - public boolean isIncludeControlHeadElements() { - return includeControlHeadElements; - } - - /** - * Set whether the Control head elements should be included in the page - * template. - *

- * By setting this value to false, Click won't include Control's - * {@link #getHeadElements() head elements}, however the Page head elements - * will still be included. - *

- * This allows one to create a single JavaScript and CSS resource file for - * the entire Page which increases performance, since the browser only has - * to load one resource, instead of multiple resources. - *

- * Below is an example: - * - *

-     * public class HomePage extends Page {
-     *
-     *     private Form form = new Form("form");
-     *
-     *     public HomePage() {
-     *         // Indicate that Controls should not import their head elements
-     *         setIncludeControlHeadElements(false);
-     *
-     *         form.add(new EmailField("email");
-     *         addControl(form);
-     *     }
-     *
-     *     // Include the Page JavaScript and CSS resources
-     *     public List getHeadElements() {
-     *         if (headElements == null) {
-     *             headElements = super.getHeadElements();
-     *
-     *             // Include the Page CSS resource. This resource should combine
-     *             // all the CSS necessary for the page
-     *             headElements.add(new CssImport("/assets/css/home-page.css"));
-     *
-     *             // Include the Page JavaScript resource. This resource should
-     *             // combine all the JavaScript necessary for the page
-     *             headElements.add(new JsImport("/assets/js/home-page.js"));
-     *         }
-     *         return headElements;
-     *     }
-     * } 
- * - * @param includeControlHeadElements flag indicating whether Control - * head elements should be included in the page - */ - public void setIncludeControlHeadElements(boolean includeControlHeadElements) { - this.includeControlHeadElements = includeControlHeadElements; - } - - /** - * Set the location to redirect the request to. - *

- * If the {@link #redirect} property is not null it will be used to redirect - * the request in preference to the {@link #forward} and {@link #path} - * properties. The request is redirected using the HttpServletResponse.setRedirect() - * method. - *

- * If the redirect location begins with a "/" - * character the redirect location will be prefixed with the web applications - * context path. Note if the given location is already prefixed - * with the context path, Click won't add it a second time. - *

- * For example if an application is deployed to the context - * "mycorp" calling - * setRedirect("/customer/details.htm") - * will redirect the request to: - * "/mycorp/customer/details.htm" - *

- * If the redirect location does not begin with a "/" - * character the redirect location will be used as specified. Thus if the - * location is http://somehost.com/myapp/customer.jsp, - * Click will redirect to that location. - *

- * JSP note: when redirecting to a JSP template keep in mind that the - * JSP template won't be processed by Click, as ClickServlet is mapped to - * *.htm. Instead JSP templates are processed by the Servlet - * container JSP engine. - *

- * So if you have a situation where a Page Class - * (Customer.class) is mapped to the JSP - * ("/customer.jsp") and you want to redirect to - * Customer.class, you could either redirect to - * ("/customer.htm") or - * use the alternative redirect utility {@link #setRedirect(java.lang.Class)}. - *

- * Please note that Click will url encode the location by invoking - * response.encodeRedirectURL(location) before redirecting. - *

- * See also {@link #setRedirect(java.lang.String, java.util.Map)}, - * {@link #setForward(String)}, {@link #setPath(String)} - * - * @param location the path to redirect the request to - */ - public void setRedirect(String location) { - setRedirect(location, null); - } - - /** - * Set the request to redirect to the give page class. - * - * @see #setRedirect(java.lang.String) - * - * @param pageClass the class of the Page to redirect the request to - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - public void setRedirect(Class pageClass) { - setRedirect(pageClass, null); - } - - /** - * Set the request to redirect to the given location and append - * the map of request parameters to the location URL. - *

- * The map keys will be used as the request parameter names and the map - * values will be used as the request parameter values. For example: - * - *

-     * public boolean onSave() {
-     *     // Specify redirect parameters
-     *     Map parameters = new HashMap();
-     *     parameters.put("customerId", getCustomerId());
-     *
-     *     // Set redirect to customer.htm page
-     *     setRedirect("/customer.htm", parameters);
-     *
-     *     return false;
-     * } 
- * - * To render multiple parameter values for the same parameter name, specify - * the values as a String[] array. For example: - * - *
-     * public boolean onSave() {
-     *
-     *     // Specify an array of customer IDs
-     *     String[] ids = {"123", "456", "789"};
-     *
-     *     // Specify redirect parameters
-     *     Map parameters = new HashMap();
-     *     parameters.put("customerIds", ids);
-     *
-     *     // Set redirect to customer.htm page
-     *     setRedirect("/customer.htm", parameters);
-     *
-     *     return false;
-     * } 
- * - * @see #setRedirect(java.lang.String) - * - * @param location the path to redirect the request to - * @param params the map of request parameter name and value pairs - */ - public void setRedirect(String location, Map params) { - Context context = getContext(); - if (StringUtils.isNotBlank(location)) { - if (location.charAt(0) == '/') { - String contextPath = context.getRequest().getContextPath(); - - // Guard against adding duplicate context path - if (!location.startsWith(contextPath + '/')) { - location = contextPath + location; - } - } - } - - if (params != null && !params.isEmpty()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - - Iterator> i = params.entrySet().iterator(); - while (i.hasNext()) { - Map.Entry entry = i.next(); - String paramName = entry.getKey(); - Object paramValue = entry.getValue(); - - // Check for multivalued parameter - if (paramValue instanceof String[]) { - String[] paramValues = (String[]) paramValue; - for (int j = 0; j < paramValues.length; j++) { - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValues[j], context)); - if (j < paramValues.length - 1) { - buffer.append("&"); - } - } - } else { - if (paramValue != null) { - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValue, context)); - } - } - if (i.hasNext()) { - buffer.append("&"); - } - } - - if (buffer.length() > 0) { - if (location.contains("?")) { - location += "&" + buffer.toString(); - } else { - location += "?" + buffer.toString(); - } - } - } - - redirect = location; - } - - /** - * Set the request to redirect to the given page class and and append - * the map of request parameters to the page URL. - *

- * The map keys will be used as the request parameter names and the map - * values will be used as the request parameter values. - * - * @see #setRedirect(java.lang.String, java.util.Map) - * @see #setRedirect(java.lang.String) - * - * @param pageClass the class of the Page to redirect the request to - * @param params the map of request parameter name and value pairs - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - public void setRedirect(Class pageClass, - Map params) { - - String target = getContext().getPagePath(pageClass); - - // If page class maps to a jsp, convert to htm which allows ClickServlet - // to process the redirect - if (target != null && target.endsWith(".jsp")) { - target = StringUtils.replaceOnce(target, ".jsp", ".htm"); - } - - setRedirect(target, params); - } - - /** - * Return the path of the page border template to render, by default this - * method returns {@link #getPath()}. - *

- * Pages can override this method to return an alternative border page - * template. This is very useful when implementing an standardized look and - * feel for a web site. The example below provides a BorderedPage base Page - * which other site templated Pages should extend. - * - *

-     * public class BorderedPage extends Page {
-     *     public String getTemplate() {
-     *         return "border.htm";
-     *     }
-     * } 
- * - * The BorderedPage returns the page border template "border.htm": - * - *
-     * <html>
-     *   <head>
-     *     <title> $title </title>
-     *     <link rel="stylesheet" type="text/css" href="style.css" title="Style"/>
-     *   </head>
-     *   <body>
-     *
-     *     <h1> $title </h1>
-     *     <hr/>
-     *
-     *     #parse( $path )
-     *
-     *   </body>
-     * </html> 
- * - * Other pages insert their content into this template, via their - * {@link #path} property using the Velocity - * #parse - * directive. Note the $path value is automatically - * added to the VelocityContext by the ClickServlet. - * - * @return the path of the page template to render, by default returns - * {@link #getPath()} - */ - public String getTemplate() { - return template == null ? getPath() : template; - } - - /** - * Set the page border template path. - *

- * Note: if this value is not set, {@link #getTemplate()} will default - * to {@link #getPath()}. - * - * @param template the border template path - */ - public void setTemplate(String template) { - this.template = template; - } - - // Private methods -------------------------------------------------------- - - /** - * Replace the current control with the new control. - * - * @param currentControl the control currently contained in the page - * @param newControl the control to replace the current control container in - * the page - * - * @throws IllegalStateException if the currentControl is not contained in - * the page - */ - private void replaceControl(Control currentControl, Control newControl) { - - // Current control and new control are referencing the same object - // so we exit early - if (currentControl == newControl) { - return; - } - - int controlIndex = getControls().indexOf(currentControl); - if (controlIndex == -1) { - throw new IllegalStateException("Cannot replace the given control" - + " because it is not present in the page"); - } - - // Note: set parent first since setParent might veto further processing - newControl.setParent(this); - currentControl.setParent(null); - - // Set control to current control index - getControls().set(controlIndex, newControl); - - addModel(newControl.getName(), newControl); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/PageInterceptor.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/PageInterceptor.java deleted file mode 100644 index 42b4829629..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/PageInterceptor.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click; - -/** - * Provides a Page life cycle interceptor. Classes implementing this interface - * can be used to listen for key page life cycle events and abort further page - * processing if required. - *

- * PageInterceptors can be used for many different purposes including: - *

    - *
  • enforcing application wide page security policies
  • - *
  • injecting dependencies into page objects
  • - *
  • logging and profiling page performance
  • - *
- * - * A Click application can define multiple page interceptors that are invoked in - * the order in which they are returned by the ConfigService. - * - *

Scope

- * - * Page interceptors can be defined with a request level scope, whereby a new - * page interceptor will be created with each page request providing a thread - * safe programming model. - *

- * Please note, as new interceptor instances are created with each request, care - * should be taken to ensure that these objects are light weight and do not - * introduce a performance bottleneck into your application. - *

- * Alternatively, page interceptors can be defined with application level scope - * whereby a single instance is created for the application and is used for - * all requests. - *

- * Note application scope interceptors are more efficient that request scope - * interceptors, but you are responsible for ensuring that they are thread safe - * and support reentrant method invocations as multiple page requests are - * processed at the same time. - * - *

Configuration

- * - * Application PageInterceptors are configured in the click.xml - * configuration file. PageInterceptors must support construction using a - * no-args public constructor. - *

- * Page interceptors can have multiple properties configured with their XML - * definition which are set after the constructor has been called. Properties - * are set using OGNL via {@link org.apache.click.util.PropertyUtils}. - *

- * An example configuration is provided below: - * - *

- * <page-interceptor classname="com.mycorp.PageSecurityInterceptor" scope="application">
- *     <property name="notAuthenticatedPath" value="/not-authenticated.htm"/>
- *     <property name="notAuthorizedPath" value="/not-authorized.htm"/>
- * </page-interceptor> 
- * - * The default scope for page interceptors is "request", but this can be configured - * as "application" as is done in the example configuration above. - * - *

Example

- * - *
- * public class SecurityInterceptor implements PageInterceptor {
- *
- *    // The request not authenticated redirect path.
- *    private String notAuthenticatedPath;
- *
- *    // The request not authorized redirect path.
- *    private String notAuthorizedPath;
- *
- *    // Public Methods ---------------------------------------------------------
- *
- *    public boolean preCreate(Class pageClass, Context context) {
- *
- *       // If authentication required, then ensure user is authenticated
- *       Authentication authentication = pageClass.getAnnotation(Authentication.class);
- *
- *       // TODO: user context check.
- *
- *       if (authentication != null && authentication.required()) {
- *          sendRedirect(getNotAuthenticatedPath(), context);
- *          return false;
- *       }
- *
- *       // If authorization permission defined, then ensure user is authorized to access the page
- *       Authorization authorization = pageClass.getAnnotation(Authorization.class);
- *       if (authorization != null) {
- *          if (!UserContext.getThreadUserContext().hasPermission(authorization.permission())) {
- *             sendRedirect(getNotAuthorizedPath(), context);
- *             return false;
- *          }
- *       }
- *
- *       return true;
- *    }
- *
- *    public boolean postCreate(Page page) {
- *       return true;
- *    }
- *
- *    public boolean preResponse(Page page) {
- *       return true;
- *    }
- *
- *    public void postDestroy(Page page) {
- *    }
- *
- *    public String getNotAuthenticatedPath() {
- *       return notAuthenticatedPath;
- *    }
- *
- *    public void setNotAuthenticatedPath(String notAuthenticatedPath) {
- *       this.notAuthenticatedPath = notAuthenticatedPath;
- *    }
- *
- *    public String getNotAuthorizedPath() {
- *       return notAuthorizedPath;
- *    }
- *
- *    public void setNotAuthorizedPath(String notAuthorizedPath) {
- *       this.notAuthorizedPath = notAuthorizedPath;
- *    }
- *
- *    // Protected Methods ------------------------------------------------------
- *
- *    protected void sendRedirect(String location, Context context) {
- *       if (StringUtils.isNotBlank(location)) {
- *          if (location.charAt(0) == '/') {
- *             String contextPath = context.getRequest().getContextPath();
- *
- *             // Guard against adding duplicate context path
- *             if (!location.startsWith(contextPath + '/')) {
- *                location = contextPath + location;
- *             }
- *          }
- *       }
- *
- *       location = context.getResponse().encodeRedirectURL(location);
- *
- *       try {
- *          context.getResponse().sendRedirect(location);
- *
- *       } catch (IOException ioe) {
- *          throw new RuntimeException(ioe);
- *       }
- *   }
- * } 
- * - *
- * // Page class authentication annotation
- * @Retention(RetentionPolicy.RUNTIME)
- * public @interface Authentication {
- *    boolean required() default true;
- * } 
- * - *
- * // Page class authorization annotation
- * @Retention(RetentionPolicy.RUNTIME)
- * public @interface Authorization {
- *    String permission();
- * }
- * 
- */ -public interface PageInterceptor { - - /** - * Provides a before page object creation interceptor method, which is passed - * the class of the page to be instantiated and the page request context. - * If this method returns true then the normal page processing is performed, - * otherwise if this method returns false the page instance is never created - * and the request is considered to have been handled. - * - * @param pageClass the class of the page to be instantiated - * @param context the page request context - * @return true to continue normal page processing or false whereby the - * request is considered to be handled - */ - public boolean preCreate(Class pageClass, Context context); - - /** - * Provides a post page object creation interceptor method, which is passed - * the instance of the newly created page. This interceptor method is called - * before the page {@link Page#onSecurityCheck()} method is invoked. - *

- * If this method returns true then the normal page processing is performed, - * otherwise if this method returns false the request is considered to have - * been handled. - *

- * Please note the page {@link Page#onDestroy()} method will still be invoked. - * - * @param page the newly instantiated page instance - * @return true to continue normal page processing or false whereby the - * request is considered to be handled - */ - public boolean postCreate(Page page); - - /** - * Provides a page interceptor before response method. This method is invoked - * prior to the page redirect, forward or rendering phase. - *

- * If this method returns true then the normal page processing is performed, - * otherwise if this method returns false request is considered to have been - * handled. - *

- * Please note the page {@link Page#onDestroy()} method will still be invoked. - * - * @param page the newly instantiated page instance - * @return true to continue normal page processing or false whereby the - * request is considered to be handled - */ - public boolean preResponse(Page page); - - /** - * Provides a post page destroy interceptor method. This interceptor method - * is called immediately after the page {@link Page#onDestroy()} method is - * invoked. - * - * @param page the page object which has just been destroyed - */ - public void postDestroy(Page page); - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/ajax/AjaxBehavior.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/ajax/AjaxBehavior.java deleted file mode 100644 index fac9f69b4e..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/ajax/AjaxBehavior.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.ajax; - - -import org.openidentityplatform.openam.click.ActionResult; -import org.openidentityplatform.openam.click.Behavior; -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Control; - -/** - * AjaxBehavior extends the basic Behavior functionality to allow Controls to - * handle and process incoming Ajax requests. - *

- * To handle an Ajax request, AjaxBehavior exposes the listener method: - * {@link #onAction(org.openidentityplatform.openam.click.Control) onAction}. - * The onAction method returns an ActionResult that is rendered back - * to the browser. - *

- * Before Click invokes the onAction method it checks whether the request - * is targeted at the AjaxBehavior by invoking the method - * {@link #isAjaxTarget(org.openidentityplatform.openam.click.Context) Behavior.isAjaxTarget()}. - * Click will only invoke onAction if isAjaxTarget returns true. - */ -public interface AjaxBehavior extends Behavior { - - /** - * This method can be implemented to handle and respond to an Ajax request. - * For example: - * - *

-     * public void onInit() {
-     *     ActionLink link = new ActionLink("link");
-     *     link.addBehaior(new DefaultAjaxBehavior() {
-     *
-     *         public ActionResult onAction(Control source) {
-     *             ActionResult result = new ActionResult("<h1>Hello world</h1>", ActionResult.HTML);
-     *             return result;
-     *         }
-     *     });
-     * } 
- * - * @param source the control the behavior is attached to - * @return the action result instance - */ - public ActionResult onAction(Control source); - - /** - * Return true if the behavior is the request target, false otherwise. - *

- * This method is queried by Click to determine if the behavior's - * {@link #onAction(org.openidentityplatform.openam.click.Control)} method should be called in - * response to a request. - *

- * By exposing this method through the Behavior interface it provides - * implementers with fine grained control over whether the Behavior's - * {@link #onAction(org.openidentityplatform.openam.click.Control)} method should be invoked or not. - *

- * Below is an example implementation: - * - *

-     * public CustomBehavior implements Behavior {
-     *
-     *     private String eventType;
-     *
-     *     public CustomBehavior(String eventType) {
-     *         // The event type of the behavior
-     *         super(eventType);
-     *     }
-     *
-     *     public boolean isAjaxTarget(Context context) {
-     *         // Retrieve the eventType parameter from the incoming request
-     *         String eventType = context.getRequestParameter("type");
-     *
-     *         // Check if this Behavior's eventType matches the request
-     *         // "type" parameter
-     *         return StringUtils.equalsIgnoreCase(this.eventType, eventType);
-     *     }
-     *
-     *     public ActionResult onAction(Control source) {
-     *         // If isAjaxTarget returned true, the onAction method will be
-     *         // invoked
-     *         ...
-     *     }
-     * } 
- * - * @param context the request context - * @return true if the behavior is the request target, false otherwise - */ - public boolean isAjaxTarget(Context context); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractContainer.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractContainer.java deleted file mode 100644 index fa553be154..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractContainer.java +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.ContainerUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Provides a default implementation of the {@link org.openidentityplatform.openam.click.control.Container} interface - * to make it easier for developers to create their own containers. - *

- * Subclasses can override {@link #getTag()} to return a specific HTML element. - *

- * The following example shows how to create an HTML div element: - * - *

- * public class Div extends AbstractContainer {
- *
- *     public String getTag() {
- *         // Return the HTML tag
- *         return "div";
- *     }
- * } 
- */ -public abstract class AbstractContainer extends AbstractControl implements - Container { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - // Instance Variables ----------------------------------------------------- - - /** The list of controls. */ - protected List controls; - - /** The map of controls keyed by field name. */ - protected Map controlMap; - - // Constructors ----------------------------------------------------------- - - /** - * Create a container with no name defined. - */ - public AbstractContainer() { - } - - /** - * Create a container with the given name. - * - * @param name the container name - */ - public AbstractContainer(String name) { - super(name); - } - - // Public Methods --------------------------------------------------------- - - /** - * @see org.openidentityplatform.openam.click.control.Container#add(Control). - *

- * Please note: if the container contains a control with the same name - * as the given control, that control will be - * {@link #replace(Control, Control) replaced} - * by the given control. If a control has no name defined it cannot be replaced. - * - * @param control the control to add to the container - * @return the control that was added to the container - * @throws IllegalArgumentException if the control is null - */ - public Control add(Control control) { - return insert(control, getControls().size()); - } - - /** - * Add the control to the container at the specified index, and return the - * added instance. - *

- * Please note: if the container contains a control with the same name - * as the given control, that control will be - * {@link #replace(Control, Control) replaced} - * by the given control. If a control has no name defined it cannot be replaced. - *

- * Also note if the specified control already has a parent assigned, - * it will automatically be removed from that parent and inserted into this - * container. - * - * @see org.openidentityplatform.openam.click.control.Container#insert(Control, int) - * - * @param control the control to add to the container - * @param index the index at which the control is to be inserted - * @return the control that was added to the container - * - * @throws IllegalArgumentException if the control is null or if the control - * and container is the same instance - * - * @throws IndexOutOfBoundsException if index is out of range - * (index < 0 || index > getControls().size()) - */ - public Control insert(Control control, int index) { - // Check if panel already contains the control - String controlName = control.getName(); - if (controlName != null) { - // Check if container already contains the control - Control currentControl = getControlMap().get(control.getName()); - - // If container already contains the control do a replace - if (currentControl != null) { - - // Current control and new control are referencing the same object - // so we exit early - if (currentControl == control) { - return control; - } - - // If the two controls are different objects, we remove the current - // control and add the given control - return replace(currentControl, control); - } - } - - return ContainerUtils.insert(this, control, index, getControlMap()); - } - - /** - * @seeorg.openidentityplatform.openam.click.control.Container#remove(Control). - * - * @param control the control to remove from the container - * @return true if the control was removed from the container - * @throws IllegalArgumentException if the control is null - */ - public boolean remove(Control control) { - return ContainerUtils.remove(this, control, getControlMap()); - } - - /** - * Replace the control in the container at the specified index, and return - * the newly added control. - * - * @see org.openidentityplatform.openam.click.control.Container#replace(Control, Control) - * - * @param currentControl the control currently contained in the container - * @param newControl the control to replace the current control contained in - * the container - * @return the new control that replaced the current control - * - * @deprecated this method was used for stateful pages, which have been deprecated - * - * @throws IllegalArgumentException if the currentControl or newControl is - * null - * @throws IllegalStateException if the currentControl is not contained in - * the container - */ - public Control replace(Control currentControl, Control newControl) { - int controlIndex = getControls().indexOf(currentControl); - return ContainerUtils.replace(this, currentControl, newControl, - controlIndex, getControlMap()); - } - - /** - * @see org.apache.click.control.Container#getControls(). - * - * @return the sequential list of controls held by the container - */ - public List getControls() { - if (controls == null) { - controls = new ArrayList(); - } - return controls; - } - - /** - * @see org.apache.click.control.Container#getControl(String) - * - * @param controlName the name of the control to get from the container - * @return the named control from the container if found or null otherwise - */ - public Control getControl(String controlName) { - if (hasControls()) { - return getControlMap().get(controlName); - } - return null; - } - - /** - * @see Container#contains(Control) - * - * @param control the control whose presence in this container is to be tested - * @return true if the container contains the specified control - */ - public boolean contains(Control control) { - return getControls().contains(control); - } - - /** - * Returns true if this container has existing controls, false otherwise. - * - * @return true if the container has existing controls, false otherwise. - */ - public boolean hasControls() { - return (controls != null) && !controls.isEmpty(); - } - - /** - * Return the map of controls where each map's key / value pair will consist - * of the control name and instance. - *

- * Controls added to the container that did not specify a {@link #name}, - * will not be included in the returned map. - * - * @return the map of controls - */ - public Map getControlMap() { - if (controlMap == null) { - controlMap = new HashMap(); - } - return controlMap; - } - - /** - * @see org.openidentityplatform.openam.click.control.AbstractControl#getControlSizeEst(). - * - * @return the estimated rendered control size in characters - */ - @Override - public int getControlSizeEst() { - int size = 20; - - if (getTag() != null && hasAttributes()) { - size += 20 * getAttributes().size(); - } - - if (hasControls()) { - size += getControls().size() * size; - } - - return size; - } - - /** - * @see Control#onProcess(). - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - - boolean continueProcessing = true; - - for (Control control : getControls()) { - if (!control.onProcess()) { - continueProcessing = false; - } - } - - dispatchActionEvent(); - - return continueProcessing; - } - - /** - * @see Control#onDestroy() - */ - @Override - public void onDestroy() { - for (Control control : getControls()) { - try { - control.onDestroy(); - } catch (Throwable t) { - ClickUtils.getLogService().error("onDestroy error", t); - } - } - } - - /** - * @see Control#onInit() - */ - @Override - public void onInit() { - super.onInit(); - for (Control control : getControls()) { - control.onInit(); - } - } - - /** - * @see Control#onRender() - */ - @Override - public void onRender() { - for (Control control : getControls()) { - control.onRender(); - } - } - - /** - * Render the HTML representation of the container and all its child - * controls to the specified buffer. - *

- * If {@link #getTag()} returns null, this method will render only its - * child controls. - *

- * @see org.openidentityplatform.openam.click.control.AbstractControl#render(HtmlStringBuffer) - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - - //If tag is set, render it - if (getTag() != null) { - renderTagBegin(getTag(), buffer); - buffer.closeTag(); - if (hasControls()) { - buffer.append("\n"); - } - renderContent(buffer); - renderTagEnd(getTag(), buffer); - - } else { - - //render only content because no tag is specified - renderContent(buffer); - } - } - - /** - * Returns the HTML representation of this control. - *

- * This method delegates the rendering to the method - * {@link #render(HtmlStringBuffer)}. The size of buffer - * is determined by {@link #getControlSizeEst()}. - * - * @see Object#toString() - * - * @return the HTML representation of this control - */ - @Override - public String toString() { - HtmlStringBuffer buffer = new HtmlStringBuffer(getControlSizeEst()); - render(buffer); - return buffer.toString(); - } - - // Protected Methods ------------------------------------------------------ - - /** - * @see AbstractControl#renderTagEnd(String, HtmlStringBuffer). - * - * @param tagName the name of the tag to close - * @param buffer the buffer to append the output to - */ - @Override - protected void renderTagEnd(String tagName, HtmlStringBuffer buffer) { - buffer.elementEnd(tagName); - } - - /** - * Render this container content to the specified buffer. - * - * @param buffer the buffer to append the output to - */ - protected void renderContent(HtmlStringBuffer buffer) { - renderChildren(buffer); - } - - /** - * Render this container children to the specified buffer. - * - * @see #getControls() - * - * @param buffer the buffer to append the output to - */ - protected void renderChildren(HtmlStringBuffer buffer) { - for (Control control : getControls()) { - - int before = buffer.length(); - control.render(buffer); - - int after = buffer.length(); - if (before != after) { - buffer.append("\n"); - } - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractControl.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractControl.java deleted file mode 100644 index a651f26699..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractControl.java +++ /dev/null @@ -1,1121 +0,0 @@ -package org.openidentityplatform.openam.click.control; - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.Map.Entry; - -import jakarta.servlet.ServletContext; - -import org.openidentityplatform.openam.click.ActionEventDispatcher; -import org.openidentityplatform.openam.click.ActionListener; -import org.openidentityplatform.openam.click.Behavior; -import org.openidentityplatform.openam.click.ControlRegistry; -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.Page; -import org.openidentityplatform.openam.click.element.Element; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides a default implementation of the {@link Control} interface - * to make it easier for developers to create their own controls. - *

- * Subclasses are expected to at least override {@link #getTag()} - * to differentiate the control. However some controls do not map cleanly - * to an html tag, in which case you can override - * {@link #render(org.openidentityplatform.openam.click.util.HtmlStringBuffer)} for complete control - * over the output. - *

- * Below is an example of creating a new control called MyField: - *

- * public class MyField extends AbstractControl {
- *
- *     private String value;
- *
- *     public void setValue(String value) {
- *         this.value = value;
- *     }
- *
- *     public String getValue() {
- *         return value;
- *     }
- *
- *     public String getTag() {
- *         // Return the HTML tag
- *         return "input";
- *     }
- *
- *     public boolean onProcess() {
- *         // Bind the request parameter to the field value
- *         String requestValue = getContext().getRequestParameter(getName());
- *         setValue(requestValue);
- *
- *         // Invoke any listener of MyField
- *         return dispatchActionEvent();
- *     }
- * }
- * 
- * By overriding {@link #getTag()} one can specify the html tag to render. - *

- * Overriding {@link #onProcess()} allows one to bind the servlet request - * parameter to MyField value. The {@link #dispatchActionEvent()} method - * registers the listener for this control on the Context. Once the onProcess - * event has finished, all registered listeners will be fired. - *

- * To view the html rendered by MyField invoke the control's {@link #toString()} - * method: - * - *

- * public class Test {
- *     public static void main (String args[]) {
- *         // Create mock context in which to test the control.
- *         MockContext.initContext();
- *
- *         String fieldName = "myfield";
- *         MyField myfield = new MyField(fieldName);
- *         String output = myfield.toString();
- *         System.out.println(output);
- *     }
- * } 
- * - * Executing the above test results in the following output: - * - *
- * <input name="myfield" id="myfield"/>
- * - * Also see {@link org.apache.click.Control} javadoc for an explanation of the - * Control execution sequence. - * - *

Message Resources and Internationalization (i18n)

- * - * Controls support a hierarchy of resource bundles for displaying messages. - * These localized messages can be accessed through the methods: - * - *
    - *
  • {@link #getMessage(String)}
  • - *
  • {@link #getMessage(String, Object...)}
  • - *
  • {@link #getMessages()}
  • - *
- * - * The order in which localized messages resolve are described in the - * user guide. - */ -public abstract class AbstractControl implements Control { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - // Instance Variables ----------------------------------------------------- - - /** The control's action listener. */ - protected ActionListener actionListener; - - /** The control's list of {@link org.apache.click.Behavior behaviors}. */ - protected Set behaviors; - - /** - * The list of page HTML HEAD elements including: Javascript imports, - * Css imports, inline Javascript and inline Css. - */ - protected List headElements; - - /** The Control attributes Map. */ - protected Map attributes; - - /** The Control localized messages Map. */ - protected transient Map messages; - - /** The Control name. */ - protected String name; - - /** The control's parent. */ - protected Object parent; - - /** - * The Map of CSS style attributes. - * - * @deprecated use {@link #addStyleClass(String)} and - * {@link #removeStyleClass(String)} instead. - */ - protected Map styles; - - /** The listener target object. */ - protected Object listener; - - /** The listener method name. */ - protected String listenerMethod; - - // Constructors ----------------------------------------------------------- - - /** - * Create a control with no name defined. - */ - public AbstractControl() { - } - - /** - * Create a control with the given name. - * - * @param name the control name - */ - public AbstractControl(String name) { - if (name != null) { - setName(name); - } - } - - // Public Methods --------------------------------------------------------- - - /** - * Returns the controls html tag. - *

- * Subclasses should override this method and return the correct tag. - *

- * This method returns null by default. - *

- * Example tags include table, form, a and - * input. - * - * @return this controls html tag - */ - public String getTag() { - return null; - } - - /** - * Return the control's action listener. If the control has a listener - * target and listener method defined, this method will return an - * {@link org.apache.click.ActionListener} instance. - * - * @return the control's action listener - */ - public ActionListener getActionListener() { - if (actionListener == null && listener != null && listenerMethod != null) { - actionListener = new ActionListener() { - - private static final long serialVersionUID = 1L; - - public boolean onAction(Control source) { - return ClickUtils.invokeListener(listener, listenerMethod); - } - }; - } - return actionListener; - } - - /** - * Set the control's action listener. - * - * @param listener the control's action listener - */ - public void setActionListener(ActionListener listener) { - this.actionListener = listener; - } - - /** - * Returns true if this control has any - * Behaviors registered, false otherwise. - * - * @return true if this control has any Behaviors registered, - * false otherwise - */ - public boolean hasBehaviors() { - return (behaviors != null && !behaviors.isEmpty()); - } - - /** - * Add the given Behavior to the control's Set of - * {@link #getBehaviors() Behaviors}. - *

- * In addition, the Control will be registered with the - * {@link org.apache.click.ControlRegistry#registerAjaxTarget(org.apache.click.Control) ControlRegistry} - * as a potential Ajax target control and to have it's - * Behaviors processed by the Click runtime. - * - * @param behavior the Behavior to add - */ - public void addBehavior(Behavior behavior) { - if (behavior == null) { - throw new IllegalArgumentException("Behavior cannot be null"); - } - - // Ensure we register the behavior only once - if (!hasBehaviors()) { - ControlRegistry.registerAjaxTarget(this); - } - - getBehaviors().add(behavior); - } - - /** - * Remove the given Behavior from the Control's Set of - * {@link #getBehaviors() Behaviors}. - * - * @param behavior the Behavior to remove - */ - public void removeBehavior(Behavior behavior) { - getBehaviors().remove(behavior); - } - - /** - * Returns the Set of Behaviors for this control. - * - * @return the Set of Behaviors for this control - */ - public Set getBehaviors() { - if (behaviors == null) { - behaviors = new HashSet(); - } - return behaviors; - } - - /** - * Returns true if this control is an AJAX target, false - * otherwise. - *

- * The control is defined as an Ajax target if the control {@link #getId() ID} - * is send as a request parameter. - * - * @param context the request context - * @return true if this control is an AJAX target, false - * otherwise - */ - public boolean isAjaxTarget(Context context) { - // TODO each control could have an optimized version of isAjaxTarget - // targeting specifically that control. For now we just check that the - // control id is present. Not all controls can use an ID for example: - // ActionLink - String id = getId(); - if (id != null) { - return context.hasRequestParameter(id); - } else { - return false; - } - } - - /** - * Return the control HTML attribute with the given name, or null if the - * attribute does not exist. - * - * @param name the name of link HTML attribute - * @return the link HTML attribute - */ - public String getAttribute(String name) { - if (hasAttributes()) { - return getAttributes().get(name); - } - return null; - } - - /** - * Set the control attribute with the given attribute name and value. You would - * generally use attributes if you were creating the entire Control - * programmatically and rendering it with the {@link #toString()} method. - *

- * For example given the ActionLink: - * - *

-     * ActionLink addLink = new ActionLink("addLink", "Add");
-     * addLink.setAttribute("target", "_blank"); 
- * - * Will render the HTML as: - *
-     * <a href=".." target="_blank">Add</a> 
- * - * Note: for style and class attributes you can - * also use the methods {@link #setStyle(String, String)} and - * {@link #addStyleClass(String)}. - * - * @see #setStyle(String, String) - * @see #addStyleClass(String) - * @see #removeStyleClass(String) - * - * @param name the attribute name - * @param value the attribute value - * @throws IllegalArgumentException if name parameter is null - */ - public void setAttribute(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getAttributes().put(name, value); - } else { - getAttributes().remove(name); - } - } - - /** - * Return the control's attributes Map. - * - * @return the control's attributes Map. - */ - public Map getAttributes() { - if (attributes == null) { - attributes = new HashMap(); - } - return attributes; - } - - /** - * Return true if the control has attributes or false otherwise. - * - * @return true if the control has attributes on false otherwise - */ - public boolean hasAttributes() { - return attributes != null && !attributes.isEmpty(); - } - - /** - * Returns true if specified attribute is defined, false otherwise. - * - * @param name the specified attribute to check - * @return true if name is a defined attribute - */ - public boolean hasAttribute(String name) { - return hasAttributes() && getAttributes().containsKey(name); - } - - /** - * @see org.apache.click.Control#getContext() - * - * @return the Page request Context - */ - public Context getContext() { - return Context.getThreadLocalContext(); - } - - /** - * @see Control#getName() - * - * @return the name of the control - */ - public String getName() { - return name; - } - - /** - * @see Control#setName(String) - * - * @param name of the control - * @throws IllegalArgumentException if the name is null - */ - public void setName(String name) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - this.name = name; - } - - /** - * Return the "id" attribute value if defined, or the control name otherwise. - * - * @see org.apache.click.Control#getId() - * - * @return HTML element identifier attribute "id" value - */ - public String getId() { - String id = getAttribute("id"); - - return (id != null) ? id : getName(); - } - - /** - * Set the HTML id attribute for the control with the given value. - * - * @param id the element HTML id attribute value to set - */ - public void setId(String id) { - if (id != null) { - setAttribute("id", id); - } else { - getAttributes().remove("id"); - } - } - - /** - * Return the localized message for the given key or null if not found. - * The resource message returned will use the Locale obtained from the - * Context. - *

- * This method will attempt to lookup the localized message in the - * parent's messages, which resolves to the Page's resource bundle. - *

- * If the message was not found, this method will attempt to look up the - * value in the /click-control.properties message properties file, - * through the method {@link #getMessages()}. - *

- * If still not found, this method will return null. - * - * @param name the name of the message resource - * @return the named localized message for the control, or null if not found - */ - public String getMessage(String name) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - String message = null; - - message = ClickUtils.getParentMessage(this, name); - - if (message == null && getMessages().containsKey(name)) { - message = getMessages().get(name); - } - - return message; - } - - /** - * Return the formatted message for the given resource name and message - * format arguments or null if no message was found. The resource - * message returned will use the Locale obtained from the Context. - *

- * {@link #getMessage(java.lang.String)} is invoked to retrieve the message - * for the specified name. - * - * @param name resource name of the message - * @param args the message arguments to format - * @return the named localized message for the control or null if no message - * was found - */ - public String getMessage(String name, Object... args) { - String value = getMessage(name); - if (value == null) { - return null; - } - return MessageFormat.format(value, args); - } - - /** - * Return a Map of localized messages for the control. The messages returned - * will use the Locale obtained from the Context. - * - * @return a Map of localized messages for the control - * @throws IllegalStateException if the context for the control has not be set - */ - public Map getMessages() { - if (messages == null) { - messages = getContext().createMessagesMap(getClass(), CONTROL_MESSAGES); - } - return messages; - } - - /** - * @see org.apache.click.Control#getParent() - * - * @return the Control's parent - */ - public Object getParent() { - return parent; - } - - /** - * @see org.apache.click.Control#setParent(Object) - * - * @param parent the parent of the Control - * @throws IllegalArgumentException if the given parent instance is - * referencing this object: if (parent == this) - */ - public void setParent(Object parent) { - if (parent == this) { - throw new IllegalArgumentException("Cannot set parent to itself"); - } - this.parent = parent; - } - - /** - * @see org.apache.click.Control#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - public boolean onProcess() { - dispatchActionEvent(); - return true; - } - - /** - * Set the controls event listener. - *

- * The method signature of the listener is:

    - *
  • must have a valid Java method name
  • - *
  • takes no arguments
  • - *
  • returns a boolean value
  • - *
- *

- * An example event listener method would be: - * - *

-     * public boolean onClick() {
-     *     System.out.println("onClick called");
-     *     return true;
-     * } 
- * - * @param listener the listener object with the named method to invoke - * @param method the name of the method to invoke - */ - public void setListener(Object listener, String method) { - this.listener = listener; - this.listenerMethod = method; - } - - /** - * This method does nothing. Subclasses may override this method to perform - * initialization. - * - * @see org.apache.click.Control#onInit() - */ - public void onInit() { - } - - /** - * This method does nothing. Subclasses may override this method to perform - * clean up any resources. - * - * @see org.apache.click.Control#onDestroy() - */ - public void onDestroy() { - } - - /** - * This method does nothing. Subclasses may override this method to deploy - * static web resources. - * - * @see org.openidentityplatform.openam.click.Control#onDeploy(ServletContext) - * - * @param servletContext the servlet context - */ - public void onDeploy(ServletContext servletContext) { - } - - /** - * This method does nothing. Subclasses may override this method to perform - * pre rendering logic. - * - * @see org.apache.click.Control#onRender() - */ - public void onRender() { - } - - /** - * @deprecated use the new {@link #getHeadElements()} instead - * - * @return the HTML includes statements for the control stylesheet and - * JavaScript files - */ - public final String getHtmlImports() { - throw new UnsupportedOperationException("Use getHeadElements instead"); - } - - /** - * @see org.apache.click.Control#getHeadElements() - * - * @return the list of HEAD elements to be included in the page - */ - public List getHeadElements() { - if (headElements == null) { - // Most controls won't provide their own head elements, so save - // memory by creating an empty array list - headElements = new ArrayList(0); - } - return headElements; - } - - /** - * Return the parent page of this control, or null if not defined. - * - * @return the parent page of this control, or null if not defined - */ - public Page getPage() { - return ClickUtils.getParentPage(this); - } - - /** - * Return the control CSS style for the given name. - * - * @param name the CSS style name - * @return the CSS style for the given name - */ - public String getStyle(String name) { - if (hasAttribute("style")) { - String currentStyles = getAttribute("style"); - Map stylesMap = parseStyles(currentStyles); - return stylesMap.get(name); - } else { - return null; - } - } - - /** - * Set the control CSS style name and value pair. - *

- * For example given the ActionLink: - * - *

-     * ActionLink addLink = new ActionLink("addLink", "Add");
-     * addLink.setStyle("color", "red");
-     * addLink.setStyle("border", "1px solid black"); 
- * - * Will render the HTML as: - *
-     * <a href=".." style="color:red;border:1px solid black;">Add</a>
-     * 
- * To remove an existing style, set the value to null. - * - * @param name the CSS style name - * @param value the CSS style value - */ - public void setStyle(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - String oldStyles = getAttribute("style"); - - if (oldStyles == null) { - - if (value == null) { - //Exit early - return; - } else { - //If value is not null, append the new style and return - getAttributes().put("style", name + ":" + value + ";"); - return; - } - } - - //Create buffer and estimate the new size - HtmlStringBuffer buffer = new HtmlStringBuffer( - oldStyles.length() + 10); - - //Parse the current styles into a map - Map stylesMap = parseStyles(oldStyles); - - //Check if the new style is already present - if (stylesMap.containsKey(name)) { - - //If the style is present and the value is null, remove the style, - //otherwise replace it with the new value - if (value == null) { - stylesMap.remove(name); - } else { - stylesMap.put(name, value); - } - } else { - stylesMap.put(name, value); - } - - //The styles map might be empty if the last style was removed - if (stylesMap.isEmpty()) { - getAttributes().remove("style"); - return; - } - - //Iterate over the stylesMap appending each entry to buffer - for (Entry entry : stylesMap.entrySet()) { - String styleName = entry.getKey(); - String styleValue = entry.getValue(); - buffer.append(styleName); - buffer.append(":"); - buffer.append(styleValue); - buffer.append(";"); - } - getAttributes().put("style", buffer.toString()); - } - - /** - * Return true if CSS styles are defined. - * - * @deprecated use {@link #hasAttribute(String)} instead - * - * @return true if CSS styles are defined - */ - public boolean hasStyles() { - return (styles != null && !styles.isEmpty()); - } - - /** - * Return the Map of control CSS styles. - * - * @deprecated use {@link #getAttribute(String)} instead - * - * @return the Map of control CSS styles - */ - public Map getStyles() { - if (styles == null) { - styles = new HashMap(); - } - return styles; - } - - /** - * Add the CSS class attribute. Null values will be ignored. - *

- * For example given the ActionLink: - * - *

-     * ActionLink addLink = new ActionLink("addLink", "Add");
-     * addLink.addStyleClass("red"); 
- * - * Will render the HTML as: - *
-     * <a href=".." class="red">Add</a> 
- * - * @param value the class attribute to add - */ - public void addStyleClass(String value) { - //If value is null, exit early - if (value == null) { - return; - } - - //If any class attributes already exist, check if the specified class - //exists in the current set of classes. - if (hasAttribute("class")) { - String oldStyleClasses = getAttribute("class").trim(); - - //Check if the specified class exists in the class attribute set - boolean classExists = classExists(value, oldStyleClasses); - - if (classExists) { - //If the class already exist, exit early - return; - } - - //Specified class does not exist so add it with the other class - //attributes - getAttributes().put("class", oldStyleClasses + " " + value); - - } else { - //Since no class attributes exist yet, only add the specified class - setAttribute("class", value); - } - } - - /** - * Removes the CSS class attribute. - * - * @param value the CSS class attribute - */ - public void removeStyleClass(String value) { - // If value is null, exit early - if (value == null) { - return; - } - - // If any class attributes already exist, check if the specified class - // exists in the current set of classes. - if (hasAttribute("class")) { - String oldStyleClasses = getAttribute("class").trim(); - - //Check if the specified class exists in the class attribute set - boolean classExists = classExists(value, oldStyleClasses); - - if (!classExists) { - //If the class does not exist, exit early - return; - } - - // If the class does exist, parse the class attributes into a set - // and remove the specified class - Set styleClassSet = parseStyleClasses(oldStyleClasses); - styleClassSet.remove(value); - - if (styleClassSet.isEmpty()) { - // If there are no more styleClasses left, remove the class - // attribute from the attributes list - getAttributes().remove("class"); - } else { - // Otherwise render the styleClasses. - // Create buffer and estimate the new size - HtmlStringBuffer buffer = new HtmlStringBuffer( - oldStyleClasses.length() + value.length()); - - // Iterate over the styleClassSet appending each entry to buffer - Iterator it = styleClassSet.iterator(); - while (it.hasNext()) { - String entry = it.next(); - buffer.append(entry); - if (it.hasNext()) { - buffer.append(" "); - } - } - getAttributes().put("class", buffer.toString()); - } - } - } - - /** - * Render the control's output to the specified buffer. - *

- * If {@link #getTag()} returns null, this method will return an empty - * string. - *

- * @see org.apache.click.Control#render(org.apache.click.util.HtmlStringBuffer) - * - * @param buffer the specified buffer to render the control's output to - */ - public void render(HtmlStringBuffer buffer) { - if (getTag() == null) { - return; - } - renderTagBegin(getTag(), buffer); - renderTagEnd(getTag(), buffer); - } - - /** - * Returns the HTML representation of this control. - *

- * This method delegates the rendering to the method - * {@link #render(org.openidentityplatform.openam.click.util.HtmlStringBuffer)}. The size of buffer - * is determined by {@link #getControlSizeEst()}. - * - * @see Object#toString() - * - * @return the HTML representation of this control - */ - @Override - public String toString() { - if (getTag() == null) { - return ""; - } - HtmlStringBuffer buffer = new HtmlStringBuffer(getControlSizeEst()); - render(buffer); - return buffer.toString(); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Dispatch an action event to the {@link org.apache.click.ActionEventDispatcher}. - * - * @see org.apache.click.ActionEventDispatcher#dispatchActionEvent(org.apache.click.Control, org.apache.click.ActionListener) - * @see org.apache.click.ActionEventDispatcher#dispatchAjaxBehaviors(org.apache.click.Control) - */ - protected void dispatchActionEvent() { - if (getActionListener() != null) { - ActionEventDispatcher.dispatchActionEvent(this, getActionListener()); - } - - if (hasBehaviors()) { - ActionEventDispatcher.dispatchAjaxBehaviors(this); - } - } - - /** - * Append all the controls attributes to the specified buffer. - * - * @param buffer the specified buffer to append all the attributes - */ - protected void appendAttributes(HtmlStringBuffer buffer) { - if (hasAttributes()) { - buffer.appendAttributes(attributes); - } - } - - /** - * Render the tag and common attributes including {@link #getId() id}, - * class and style. The {@link #getName() name} attribute - * is not rendered by this control. It is up to subclasses - * whether to render the name attribute or not. Generally only - * {@link org.apache.click.control.Field} controls render the name attribute. - *

- * Please note: the tag will not be closed by this method. This - * enables callers of this method to append extra attributes as needed. - *

- * For example the following example: - * - *

-     * Field field = new TextField("mytext");
-     * HtmlStringBuffer buffer = new HtmlStringBuffer();
-     * field.renderTagBegin("input", buffer); 
- * - * will render: - * - *
-     * <input name="mytext" id="mytext" 
- * - * Note that the tag is not closed, so subclasses can render extra - * attributes. - * - * @param tagName the name of the tag to render - * @param buffer the buffer to append the output to - */ - protected void renderTagBegin(String tagName, - HtmlStringBuffer buffer) { - if (tagName == null) { - throw new IllegalStateException("Tag cannot be null"); - } - - buffer.elementStart(tagName); - - String id = getId(); - if (id != null) { - buffer.appendAttribute("id", id); - } - - appendAttributes(buffer); - } - - /** - * Closes the specified tag. - * - * @param tagName the name of the tag to close - * @param buffer the buffer to append the output to - */ - protected void renderTagEnd(String tagName, HtmlStringBuffer buffer) { - buffer.elementEnd(); - } - - /** - * Return the estimated rendered control size in characters. - * - * @return the estimated rendered control size in characters - */ - protected int getControlSizeEst() { - int size = 0; - if (getTag() != null && hasAttributes()) { - //length of the markup -> == 3 - //1 * tag.length() - size += 3 + getTag().length(); - //using 20 as an estimate - size += 20 * getAttributes().size(); - } - return size; - } - - // Private Methods -------------------------------------------------------- - - /** - * Parse the specified string of style attributes and return a Map - * of key/value pairs. Invalid key/value pairs will not be added to - * the map. - * - * @param style the string containing the styles to parse - * @return map containing key/value pairs of the specified style - * @throws IllegalArgumentException if style is null - */ - private Map parseStyles(String style) { - if (style == null) { - throw new IllegalArgumentException("style cannot be null"); - } - - //LinkHashMap is used to keep the order of the style names. Probably - //makes no difference to browser but it makes testing easier since the - //order that styles are added are kept when rendering the control. - Map stylesMap = new LinkedHashMap(); - StringTokenizer tokens = new StringTokenizer(style, ";"); - while (tokens.hasMoreTokens()) { - String token = tokens.nextToken(); - int keyEnd = token.indexOf(":"); - - //If there is no key/value delimiter or value is empty, continue - if (keyEnd == -1 || (keyEnd + 1) == token.length()) { - continue; - } - //Check that the value part of the key/value pair not only - //consists of a ';' char. - if (token.charAt(keyEnd + 1) == ';') { - continue; - } - String styleName = token.substring(0, keyEnd); - String styleValue = token.substring(keyEnd + 1); - stylesMap.put(styleName, styleValue); - } - - return stylesMap; - } - - /** - * Parse the specified string of style attributes and return a Map - * of key/value pairs. Invalid key/value pairs will not be added to - * the map. - * - * @param styleClasses the string containing the styles to parse - * @return map containing key/value pairs of the specified style - * @throws IllegalArgumentException if styleClasses is null - */ - private Set parseStyleClasses(String styleClasses) { - if (styleClasses == null) { - throw new IllegalArgumentException("styleClasses cannot be null"); - } - - //LinkHashMap is used to keep the order of the class names. Probably - //makes no difference to browser but it makes testing easier since the - //order that classes were added in, are kept when rendering the control. - //Thus one can test whether the expected result and actual result match. - Set styleClassesSet = new LinkedHashSet(); - StringTokenizer tokens = new StringTokenizer(styleClasses, " "); - while (tokens.hasMoreTokens()) { - String token = tokens.nextToken(); - styleClassesSet.add(token); - } - - return styleClassesSet; - } - - /** - * Return true if the new value exists in the current value. - * - * @param newValue the value of the class attribute to check - * @param currentValue the current value to test - * @return true if the classToFind exists in the current value set - */ - private boolean classExists(String newValue, String currentValue) { - //To find if a class eg. "myclass" exists, check the following: - - //1. Check if "myclass" is the only value in the string - // -> "myclass" - if (newValue.length() == currentValue.length() - && currentValue.indexOf(newValue) == 0) { - return true; - } - - //2. Check if "myclass" exists at beginning of string - // -> "myclass otherclass" - if (currentValue.startsWith(newValue + " ")) { - return true; - } - - //3. Check if "myclass" exists in middle of string - // -> "anotherclass myclass otherclass" - if (currentValue.indexOf(" " + newValue + " ") >= 0) { - return true; - } - - //4. Check if "myclass" exists at end of string - // -> "anotherclass myclass" - return (currentValue.endsWith(" " + newValue)); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractLink.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractLink.java deleted file mode 100644 index e920367086..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/AbstractLink.java +++ /dev/null @@ -1,818 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.apache.click.Stateful; -import org.apache.click.control.ActionLink; -import org.apache.click.control.Submit; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.lang.StringUtils; - -import jakarta.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -/** - * Provides a Abstract Link control:   <a href=""></a>. - *

- * See also the W3C HTML reference: - * A Links - * - * @see ActionLink - * @see Submit - */ -public abstract class AbstractLink extends AbstractControl implements Stateful { - - private static final long serialVersionUID = 1L; - - // Instance Variables ----------------------------------------------------- - - /** The Field disabled value. */ - protected boolean disabled; - - /** - * The image src path attribute. If the image src is defined then a - * <img/> element will rendered inside the anchor link when - * using the AbstractLink {@link #toString()} method. - *

- * If the image src value is prefixed with '/' then the request context path - * will be prefixed to the src value when rendered by the control. - */ - protected String imageSrc; - - /** The link display label. */ - protected String label; - - /** The link parameters map. */ - protected Map parameters; - - /** The link 'tabindex' attribute. */ - protected int tabindex; - - /** The link title attribute, which acts as a tooltip help message. */ - protected String title; - - /** Flag to set if both icon and text are rendered, default value is false. */ - protected boolean renderLabelAndImage = false; - - // Constructors ----------------------------------------------------------- - - /** - * Create an AbstractLink for the given name. - * - * @param name the page link name - * @throws IllegalArgumentException if the name is null - */ - public AbstractLink(String name) { - setName(name); - } - - /** - * Create an AbstractLink with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public AbstractLink() { - } - - // Public Attributes ------------------------------------------------------ - - /** - * Return the link html tag: a. - * - * @see AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "a"; - } - - /** - * Return true if the AbstractLink is a disabled. If the link is disabled - * it will be rendered as <span> element with a HTML class attribute - * of "disabled". - * - * @return true if the AbstractLink is a disabled - */ - public boolean isDisabled() { - return disabled; - } - - /** - * Set the disabled flag. If the link is disabled it will be rendered as - * <span> element with a HTML class attribute of "disabled". - * - * @param disabled the disabled flag - */ - public void setDisabled(boolean disabled) { - this.disabled = disabled; - } - - /** - * Return the AbstractLink anchor <a> tag href attribute. - * This method will encode the URL with the session ID - * if required using HttpServletResponse.encodeURL(). - * - * @return the AbstractLink HTML href attribute - */ - public abstract String getHref(); - - /** - * Return the image src path attribute. If the image src is defined then a - * <img/> element will be rendered inside the anchor link - * when using the AbstractLink {@link #toString()} method. - *

- * Note: the label will not be rendered in this case (default behavior), - * unless the {@link #setRenderLabelAndImage(boolean)} flag is set to true. - *

- * If the src value is prefixed with '/' then the request context path will - * be prefixed to the src value when rendered by the control. - * - * @return the image src path attribute - */ - public String getImageSrc() { - return imageSrc; - } - - /** - * Set the image src path attribute. If the src value is prefixed with - * '/' then the request context path will be prefixed to the src value when - * rendered by the control. - *

- * If the image src is defined then an <img/> element will - * be rendered inside the anchor link when using the AbstractLink - * {@link #toString()} method. - *

- * Note: the label will not be rendered in this case (default behavior), - * unless the {@link #setRenderLabelAndImage(boolean)} flag is set to true. - * - * @param src the image src path attribute - */ - public void setImageSrc(String src) { - this.imageSrc = src; - } - - /** - * Return the "id" attribute value if defined, or null otherwise. - * - * @see org.apache.click.Control#getId() - * - * @return HTML element identifier attribute "id" value - */ - @Override - public String getId() { - if (hasAttributes()) { - return getAttribute("id"); - } else { - return null; - } - } - - /** - * Return the label for the AbstractLink. - *

- * If the label value is null, this method will attempt to find a - * localized label message in the parent messages using the key: - *

- * getName() + ".label" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - * If a value still cannot be found then the ActionLink name will be converted - * into a label using the method: {@link ClickUtils#toLabel(String)} - *

- * For example given a OrderPage with the properties file - * OrderPage.properties: - * - *

-     * checkout.label=Checkout
-     * checkout.title=Proceed to Checkout 
- * - * The page ActionLink code: - *
-     * public class OrderPage extends Page {
-     *     ActionLink checkoutLink = new ActionLink("checkout");
-     *     ..
-     * } 
- * - * Will render the AbstractLink label and title properties as: - *
-     * <a href=".." title="Proceed to Checkout">Checkout</a> 
- * - * When a label value is not set, or defined in any properties files, then - * its value will be created from the Fields name. - *

- * For example given the ActionLink code: - * - *

-     * ActionLink nameField = new ActionLink("deleteItem");  
- * - * Will render the ActionLink label as: - *
-     * <a href="..">Delete Item</a> 
- * - * Note the ActionLink label can include raw HTML to render other elements. - *

- * For example the configured label: - * - *

-     * edit.label=<img src="images/edit.png" title="Edit Item"/> 
- * - * Will render the ActionLink label as: - *
-     * <a href=".."><img src="images/edit.png" title="Edit Item"/></a> 
- * - * @return the label for the ActionLink - */ - public String getLabel() { - if (label == null) { - label = getMessage(getName() + ".label"); - } - if (label == null) { - label = ClickUtils.toLabel(getName()); - } - return label; - } - - /** - * Set the label for the ActionLink. - * - * @see #getLabel() - * - * @param label the label for the ActionLink - */ - public void setLabel(String label) { - this.label = label; - } - - /** - * Return the link request parameter value for the given name, or null if - * the parameter value does not exist. - * - * @param name the name of request parameter - * @return the link request parameter value - */ - public String getParameter(String name) { - if (hasParameters()) { - Object value = getParameters().get(name); - - if (value instanceof String) { - return (String) value; - } - - if (value instanceof String[]) { - String[] array = (String[]) value; - if (array.length >= 1) { - return array[0]; - } else { - return null; - } - } - - return (value == null ? null : value.toString()); - } else { - return null; - } - } - - /** - * Set the link parameter with the given parameter name and value. You would - * generally use parameter if you were creating the entire AbstractLink - * programmatically and rendering it with the {@link #toString()} method. - *

- * For example given the ActionLink: - * - *

-     * PageLink editLink = new PageLink("editLink", EditCustomer.class);
-     * editLink.setLabel("Edit Customer");
-     * editLink.setParameter("customerId", customerId); 
- * - * And the page template: - *
-     * $editLink 
- * - * Will render the HTML as: - *
-     * <a href="/mycorp/edit-customer.htm?customerId=13490">Edit Customer</a> 
- * - * @param name the attribute name - * @param value the attribute value - * @throws IllegalArgumentException if name parameter is null - */ - public void setParameter(String name, Object value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getParameters().put(name, value); - } else { - getParameters().remove(name); - } - } - - /** - * Return the link request parameter values for the given name, or null if - * the parameter values does not exist. - * - * @param name the name of request parameter - * @return the link request parameter values - */ - public String[] getParameterValues(String name) { - if (hasParameters()) { - Object values = getParameters().get(name); - if (values instanceof String) { - return new String[] { values.toString() }; - } - if (values instanceof String[]) { - return (String[]) values; - } else { - return null; - } - } else { - return null; - } - } - - /** - * Set the link parameter with the given parameter name and values. If the - * values are null, the parameter will be removed from the {@link #parameters}. - * - * @see #setParameter(String, Object) - * - * @param name the attribute name - * @param values the attribute values - * @throws IllegalArgumentException if name parameter is null - */ - public void setParameterValues(String name, Object[] values) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (values != null) { - getParameters().put(name, values); - } else { - getParameters().remove(name); - } - } - - /** - * Return the AbstractLink parameters Map. - * - * @return the AbstractLink parameters Map - */ - public Map getParameters() { - if (parameters == null) { - parameters = new HashMap(4); - } - return parameters; - } - - /** - * Set the AbstractLink parameter map. - * - * @param parameters the link parameter map - */ - public void setParameters(Map parameters) { - this.parameters = parameters; - } - - /** - * Defines a link parameter that will have its value bound to a matching - * request parameter. {@link #setParameter(String, Object) setParameter} - * implicitly defines a parameter as well. - *

- * Please note: parameters need only be defined for Ajax requests. - * For non-Ajax requests, all incoming request parameters - * are bound, whether they are defined or not. This behavior may change in a - * future release. - *

- * Also note: link parameters are bound to request parameters - * during the {@link #onProcess()} event, so link parameters must be defined - * in the Page constructor or onInit() event. - * - * @param name the name of the parameter to define - */ - public void defineParameter(String name) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - Map localParameters = getParameters(); - if (!localParameters.containsKey(name)) { - localParameters.put(name, null); - } - } - - /** - * Return true if the AbstractLink has parameters, false otherwise. - * - * @return true if the AbstractLink has parameters, false otherwise - */ - public boolean hasParameters() { - return parameters != null && !parameters.isEmpty(); - } - - /** - * Return the link "tabindex" attribute value. - * - * @return the link "tabindex" attribute value - */ - public int getTabIndex() { - return tabindex; - } - - /** - * Set the link "tabindex" attribute value. - * - * @param tabindex the link "tabindex" attribute value - */ - public void setTabIndex(int tabindex) { - this.tabindex = tabindex; - } - - /** - * Return the 'title' attribute, or null if not defined. The title - * attribute acts like tooltip message over the link. - *

- * If the title value is null, this method will attempt to find a - * localized label message in the parent messages using the key: - *

- * getName() + ".title" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - *

- * For examle given a ItemsPage with the properties file - * ItemPage.properties: - * - *

-     * edit.label=Edit
-     * edit.title=Edit Item 
- * - * The page ActionLink code: - *
-     * public class ItemsPage extends Page {
-     *     ActionLink editLink = new ActionLink("edit");
-     *     ..
-     * } 
- * - * Will render the ActionLink label and title properties as: - *
-     * <a href=".." title="Edit Item">Edit</a> 
- * - * @return the 'title' attribute tooltip message - */ - public String getTitle() { - if (title == null) { - title = getMessage(getName() + ".title"); - } - return title; - } - - /** - * Set the 'title' attribute tooltip message. - * - * @see #getTitle() - * - * @param value the 'title' attribute tooltip message - */ - public void setTitle(String value) { - title = value; - } - - /** - * Returns true if both {@link #setImageSrc(String) icon} - * and {@link #setLabel(String) label} are rendered, - * false otherwise. - * - * @return true if both icon and text are rendered, - * false otherwise - */ - public boolean isRenderLabelAndImage() { - return renderLabelAndImage; - } - - /** - * Sets whether both {@link #setLabel(String) label} and - * {@link #setImageSrc(String) icon} are rendered for this - * link. - * - * @param renderLabelAndImage sets the rendering type of the link. - */ - public void setRenderLabelAndImage(boolean renderLabelAndImage) { - this.renderLabelAndImage = renderLabelAndImage; - } - - @Override - public boolean isAjaxTarget(Context context) { - String id = getId(); - if (id != null) { - return context.getRequestParameter(id) != null; - } else { - String localName = getName(); - if (localName != null) { - return localName.equals(context.getRequestParameter(ActionLink.ACTION_LINK)); - } - } - return false; - } - - // Public Methods --------------------------------------------------------- - - /** - * This method does nothing by default since AbstractLink does not bind to - * request values. - */ - public void bindRequestValue() { - } - - /** - * Return the link state. The following state is returned: - *
    - *
  • {@link #getParameters() link parameters}
  • - *
- * - * @return the link state - */ - public Object getState() { - if (hasParameters()) { - return getParameters(); - } - return null; - } - - /** - * Set the link state. - * - * @param state the link state to set - */ - public void setState(Object state) { - if (state == null) { - return; - } - - Map linkState = (Map) state; - setParameters(linkState); - } - - /** - * Render the HTML representation of the anchor link. This method - * will render the entire anchor link including the tags, the label and - * any attributes, see {@link #setAttribute(String, String)} for an - * example. - *

- * If the image src is defined then a <img/> element will - * rendered inside the anchor link instead of the label property. - *

- * This method invokes the abstract {@link #getHref()} method. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - - if (isDisabled()) { - - buffer.elementStart("span"); - buffer.appendAttribute("id", getId()); - addStyleClass("disabled"); - buffer.appendAttribute("class", getAttribute("class")); - - if (hasAttribute("style")) { - buffer.appendAttribute("style", getAttribute("style")); - } - - buffer.closeTag(); - - if (StringUtils.isBlank(getImageSrc())) { - buffer.append(getLabel()); - - } else { - renderImgTag(buffer); - if (isRenderLabelAndImage()) { - buffer.elementStart("span").closeTag(); - buffer.append(getLabel()); - buffer.elementEnd("span"); - } - } - - buffer.elementEnd("span"); - - } else { - buffer.elementStart(getTag()); - removeStyleClass("disabled"); - buffer.appendAttribute("href", getHref()); - buffer.appendAttribute("id", getId()); - buffer.appendAttributeEscaped("title", getTitle()); - if (getTabIndex() > 0) { - buffer.appendAttribute("tabindex", getTabIndex()); - } - - appendAttributes(buffer); - - buffer.closeTag(); - - if (StringUtils.isBlank(getImageSrc())) { - buffer.append(getLabel()); - - } else { - renderImgTag(buffer); - if (isRenderLabelAndImage()) { - buffer.elementStart("span").closeTag(); - buffer.append(getLabel()); - buffer.elementEnd("span"); - } - } - - buffer.elementEnd(getTag()); - } - } - - /** - * Remove the link state from the session for the given request context. - * - * @see #saveState(Context) - * @see #restoreState(Context) - * - * @param context the request context - */ - public void removeState(Context context) { - ClickUtils.removeState(this, getName(), context); - } - - /** - * Restore the link state from the session for the given request context. - *

- * This method delegates to {@link #setState(Object)} to set the - * link restored state. - * - * @see #saveState(Context) - * @see #removeState(Context) - * - * @param context the request context - */ - public void restoreState(Context context) { - ClickUtils.restoreState(this, getName(), context); - } - - /** - * Save the link state to the session for the given request context. - *

- * * This method delegates to {@link #getState()} to retrieve the link state - * to save. - * - * @see #restoreState(Context) - * @see #removeState(Context) - * - * @param context the request context - */ - public void saveState(Context context) { - ClickUtils.saveState(this, getName(), context); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Render the Image tag to the buffer. - * - * @param buffer the buffer to render the image tag to - */ - protected void renderImgTag(HtmlStringBuffer buffer) { - buffer.elementStart("img"); - buffer.appendAttribute("border", 0); - buffer.appendAttribute("hspace", 2); - buffer.appendAttribute("class", "link"); - - if (getTitle() != null) { - buffer.appendAttributeEscaped("alt", getTitle()); - } else { - buffer.appendAttributeEscaped("alt", getLabel()); - } - - String src = getImageSrc(); - if (StringUtils.isNotBlank(src)) { - if (src.charAt(0) == '/') { - src = getContext().getRequest().getContextPath() + src; - } - buffer.appendAttribute("src", src); - } - - buffer.elementEnd(); - } - - /** - * Render the given link parameters to the buffer. - *

- * The parameters will be rendered as URL key/value pairs e.g: - * "firstname=john&lastname=smith". - *

- * Multivalued parameters will be rendered with each value sharing the same - * key e.g: "name=john&name=susan&name=mary". - *

- * The parameter value will be encoded through - * {@link ClickUtils#encodeUrl(Object, Context)}. - * - * @param buffer the buffer to render the parameters to - * @param parameters the parameters to render - * @param context the request context - */ - protected void renderParameters(HtmlStringBuffer buffer, Map parameters, - Context context) { - - Iterator i = parameters.keySet().iterator(); - while (i.hasNext()) { - String paramName = i.next(); - Object paramValue = getParameters().get(paramName); - - // Check for multivalued parameter - if (paramValue instanceof String[]) { - String[] paramValues = (String[]) paramValue; - for (int j = 0; j < paramValues.length; j++) { - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValues[j], context)); - if (j < paramValues.length - 1) { - buffer.append("&"); - } - } - } else { - if (paramValue != null) { - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValue, context)); - } - } - if (i.hasNext()) { - buffer.append("&"); - } - } - } - - /** - * This method binds the submitted request parameters to the link - * parameters. - *

- * For non-Ajax requests this method will bind all incoming request - * parameters to the link. For Ajax requests this method will only bind - * the parameters already defined on the link. - * - * @param context the request context - */ - @SuppressWarnings("unchecked") - protected void bindRequestParameters(Context context) { - HttpServletRequest request = context.getRequest(); - - Set parameterNames = null; - - if (context.isAjaxRequest()) { - parameterNames = getParameters().keySet(); - } else { - parameterNames = request.getParameterMap().keySet(); - } - - for (String param : parameterNames) { - String[] values = request.getParameterValues(param); - // Do not process request parameters that return null values. Null - // values are only returned if the request parameter is not present. - // A null value can only occur for Ajax requests which processes - // parameters defined on the link, not the incoming parameters. - // The reason for not processing the null value is because it would - // nullify parametesr that was set during onInit - if (values == null) { - continue; - } - - if (values.length == 1) { - getParameters().put(param, values[0]); - } else { - getParameters().put(param, values); - } - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/ActionLink.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/ActionLink.java deleted file mode 100644 index c90f5f0423..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/ActionLink.java +++ /dev/null @@ -1,517 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.apache.click.control.Submit; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.lang.StringUtils; - -/** - * Provides a Action Link control:   <a href=""></a>. - * - * - * - *
- * Action Link - *
- * - * This control can render the "href" URL attribute using - * {@link #getHref()}, or the entire ActionLink anchor tag using - * {@link #toString()}. - *

- * ActionLink support invoking control listeners. - * - *

ActionLink Example

- * - * An example of using ActionLink to call a logout method is illustrated below: - * - *
- * public class MyPage extends Page {
- *
- *     public MyPage() {
- *         ActionLink link = new ActionLink("logoutLink");
- *         link.setListener(this, "onLogoutClick");
- *         addControl(link);
- *     }
- *
- *     public boolean onLogoutClick() {
- *         if (getContext().hasSession()) {
- *            getContext().getSession().invalidate();
- *         }
- *         setRedirect(LogoutPage.class);
- *
- *         return false;
- *     }
- * } 
- * - * The corresponding template code is below. Note href is evaluated by Velocity - * to {@link #getHref()}: - * - *
- * <a href="$logoutLink.href" title="Click to Logout">Logout</a> 
- * - * ActionLink can also support a value parameter which is accessible - * using {@link #getValue()}. - *

- * For example a products table could include rows - * of products, each with a get product details ActionLink and add product - * ActionLink. The ActionLinks include the product's id as a parameter to - * the {@link #getHref(Object)} method, which is then available when the - * control is processed: - * - *

- * <table>
- * #foreach ($product in $productList)
- *   <tr>
- *    <td>
- *      $product.name
- *    </td>
- *    <td>
- *      <a href="$detailsLink.getHref($product.id)" title="Get product information">Details</a>
- *    </td>
- *    <td>
- *      <a href="$addLink.getHref($product.id)" title="Add to basket">Add</a>
- *    </td>
- *   </tr>
- * #end
- * </table> 
- * - * The corresponding Page class for this template is: - * - *
- * public class ProductsPage extends Page {
- *
- *     public ActionLink addLink = new ActionLink("addLink", this, "onAddClick");
- *     public ActionLink detailsLink  = new ActionLink("detailsLink", this, "onDetailsClick");
- *     public List productList;
- *
- *     public boolean onAddClick() {
- *         // Get the product clicked on by the user
- *         Integer productId = addLink.getValueInteger();
- *         Product product = getProductService().getProduct(productId);
- *
- *         // Add product to basket
- *         List basket = (List) getContext().getSessionAttribute("basket");
- *         basket.add(product);
- *         getContext().setSessionAttribute("basket", basket);
- *
- *         return true;
- *     }
- *
- *     public boolean onDetailsClick() {
- *         // Get the product clicked on by the user
- *         Integer productId = detailsLink.getValueInteger();
- *         Product product = getProductService().getProduct(productId);
- *
- *         // Store the product in the request and display in the details page
- *         getContext().setRequestAttribute("product", product);
- *         setForward(ProductDetailsPage.class);
- *
- *         return false;
- *     }
- *
- *     public void onRender() {
- *         // Display the list of available products
- *         productList = getProductService().getProducts();
- *     }
- * } 
- * - * See also the W3C HTML reference: - * A Links - * - * @see org.apache.click.control.AbstractLink - * @see Submit - */ -public class ActionLink extends AbstractLink { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - /** The action link parameter name:   actionLink. */ - public static final String ACTION_LINK = "actionLink"; - - /** The value parameter name:   value. */ - public static final String VALUE = "value"; - - // Instance Variables ----------------------------------------------------- - - /** The link is clicked. */ - protected boolean clicked; - - // Constructors ----------------------------------------------------------- - - /** - * Create an ActionLink for the given name. - *

- * Please note the name 'actionLink' is reserved as a control request - * parameter name and cannot be used as the name of the control. - * - * @param name the action link name - * @throws IllegalArgumentException if the name is null - */ - public ActionLink(String name) { - setName(name); - } - - /** - * Create an ActionLink for the given name and label. - *

- * Please note the name 'actionLink' is reserved as a control request - * parameter name and cannot be used as the name of the control. - * - * @param name the action link name - * @param label the action link label - * @throws IllegalArgumentException if the name is null - */ - public ActionLink(String name, String label) { - setName(name); - setLabel(label); - } - - /** - * Create an ActionLink for the given listener object and listener - * method. - * - * @param listener the listener target object - * @param method the listener method to call - * @throws IllegalArgumentException if the name, listener or method is null - * or if the method is blank - */ - public ActionLink(Object listener, String method) { - if (listener == null) { - throw new IllegalArgumentException("Null listener parameter"); - } - if (StringUtils.isBlank(method)) { - throw new IllegalArgumentException("Blank listener method"); - } - setListener(listener, method); - } - - /** - * Create an ActionLink for the given name, listener object and listener - * method. - *

- * Please note the name 'actionLink' is reserved as a control request - * parameter name and cannot be used as the name of the control. - * - * @param name the action link name - * @param listener the listener target object - * @param method the listener method to call - * @throws IllegalArgumentException if the name, listener or method is null - * or if the method is blank - */ - public ActionLink(String name, Object listener, String method) { - setName(name); - if (listener == null) { - throw new IllegalArgumentException("Null listener parameter"); - } - if (StringUtils.isBlank(method)) { - throw new IllegalArgumentException("Blank listener method"); - } - setListener(listener, method); - } - - /** - * Create an ActionLink for the given name, label, listener object and - * listener method. - *

- * Please note the name 'actionLink' is reserved as a control request - * parameter name and cannot be used as the name of the control. - * - * @param name the action link name - * @param label the action link label - * @param listener the listener target object - * @param method the listener method to call - * @throws IllegalArgumentException if the name, listener or method is null - * or if the method is blank - */ - public ActionLink(String name, String label, Object listener, - String method) { - - setName(name); - setLabel(label); - if (listener == null) { - throw new IllegalArgumentException("Null listener parameter"); - } - if (StringUtils.isBlank(method)) { - throw new IllegalArgumentException("Blank listener method"); - } - setListener(listener, method); - } - - /** - * Create an ActionLink with no name defined. Please note the - * control's name must be defined before it is valid. - */ - public ActionLink() { - } - - // Public Attributes ------------------------------------------------------ - - /** - * Returns true if the ActionLink was clicked, otherwise returns false. - * - * @return true if the ActionLink was clicked, otherwise returns false. - */ - public boolean isClicked() { - return clicked; - } - - /** - * Return the ActionLink anchor <a> tag href attribute for the - * given value. This method will encode the URL with the session ID - * if required using HttpServletResponse.encodeURL(). - * - * @param value the ActionLink value parameter - * @return the ActionLink HTML href attribute - */ - public String getHref(Object value) { - Context context = getContext(); - String uri = ClickUtils.getRequestURI(context.getRequest()); - - HtmlStringBuffer buffer = - new HtmlStringBuffer(uri.length() + getName().length() + 40); - - buffer.append(uri); - buffer.append("?"); - buffer.append(ACTION_LINK); - buffer.append("="); - buffer.append(getName()); - if (value != null) { - buffer.append("&"); - buffer.append(VALUE); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(value, context)); - } - - if (hasParameters()) { - for (String paramName : getParameters().keySet()) { - if (!paramName.equals(ACTION_LINK) && !paramName.equals(VALUE)) { - Object paramValue = getParameters().get(paramName); - - // Check for multivalued parameter - if (paramValue instanceof String[]) { - String[] paramValues = (String[]) paramValue; - for (int j = 0; j < paramValues.length; j++) { - buffer.append("&"); - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValues[j], - context)); - } - } else { - if (paramValue != null) { - buffer.append("&"); - buffer.append(paramName); - buffer.append("="); - buffer.append(ClickUtils.encodeUrl(paramValue, - context)); - } - } - } - } - } - - return context.getResponse().encodeURL(buffer.toString()); - } - - /** - * Return the ActionLink anchor <a> tag href attribute value. - * - * @return the ActionLink anchor <a> tag HTML href attribute value - */ - @Override - public String getHref() { - return getHref(getValue()); - } - - /** - * Set the name of the Control. Each control name must be unique in the - * containing Page model or the containing Form. - *

- * Please note the name 'actionLink' is reserved as a control request - * parameter name and cannot be used as the name of the control. - * - * @see org.apache.click.Control#setName(String) - * - * @param name of the control - * @throws IllegalArgumentException if the name is null - */ - @Override - public void setName(String name) { - if (ACTION_LINK.equals(name)) { - String msg = "Invalid name '" + ACTION_LINK + "'. This name is " - + "reserved for use as a control request parameter name"; - throw new IllegalArgumentException(msg); - } - super.setName(name); - } - - /** - * Set the parent of the ActionLink. - * - * @see org.apache.click.Control#setParent(Object) - * - * @param parent the parent of the Control - * @throws IllegalStateException if {@link #name} is not defined - * @throws IllegalArgumentException if the given parent instance is - * referencing this object: if (parent == this) - */ - @Override - public void setParent(Object parent) { - if (parent == this) { - throw new IllegalArgumentException("Cannot set parent to itself"); - } - if (getName() == null) { - String msg = "ActionLink name not defined."; - throw new IllegalArgumentException(msg); - } - this.parent = parent; - } - - /** - * Returns the ActionLink value if the action link was processed and has - * a value, or null otherwise. - * - * @return the ActionLink value if the ActionLink was processed - */ - public String getValue() { - return getParameter(VALUE); - } - - /** - * Returns the action link Double value if the action link was - * processed and has a value, or null otherwise. - * - * @return the action link Double value if the action link was processed - * - * @throws NumberFormatException if the value cannot be parsed into a Double - */ - public Double getValueDouble() { - String value = getValue(); - if (value != null) { - return Double.valueOf(value); - } else { - return null; - } - } - - /** - * Returns the ActionLink Integer value if the ActionLink was - * processed and has a value, or null otherwise. - * - * @return the ActionLink Integer value if the action link was processed - * - * @throws NumberFormatException if the value cannot be parsed into an Integer - */ - public Integer getValueInteger() { - String value = getValue(); - if (value != null) { - return Integer.valueOf(value); - } else { - return null; - } - } - - /** - * Returns the ActionLink Long value if the ActionLink was - * processed and has a value, or null otherwise. - * - * @return the ActionLink Long value if the action link was processed - * - * @throws NumberFormatException if the value cannot be parsed into a Long - */ - public Long getValueLong() { - String value = getValue(); - if (value != null) { - return Long.valueOf(value); - } else { - return null; - } - } - - /** - * Set the ActionLink value. - * - * @param value the ActionLink value - */ - public void setValue(String value) { - setParameter(VALUE, value); - } - - /** - * Set the value of the ActionLink using the given object. - * - * @param object the object value to set - */ - public void setValueObject(Object object) { - if (object != null) { - setValue(object.toString()); - } else { - setValue(null); - } - } - - /** - * This method binds the submitted request value to the ActionLink's - * value. - */ - @Override - public void bindRequestValue() { - Context context = getContext(); - if (context.isMultipartRequest()) { - return; - } - - clicked = getName().equals(context.getRequestParameter(ACTION_LINK)); - - if (clicked) { - String value = context.getRequestParameter(VALUE); - if (value != null) { - setValue(value); - } - bindRequestParameters(context); - } - } - - // Public Methods --------------------------------------------------------- - - /** - * This method will set the {@link #isClicked()} property to true if the - * ActionLink was clicked, and if an action callback listener was set - * this will be invoked. - * - * @see org.apache.click.Control#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - bindRequestValue(); - - if (isClicked()) { - dispatchActionEvent(); - } - return true; - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Button.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Button.java deleted file mode 100644 index bdf74b6e4b..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Button.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.apache.click.control.Reset; -import org.apache.click.control.Submit; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides a Button control:   <input type='button'/>. - * - * - * - *
- * - *
- * - * The Button control is used to render a JavaScript enabled button which can - * perform client side logic. The Button control provides no server side - * processing. If server side processing is required use {@link Submit} instead. - * - *

Button Example

- * - * The example below adds a back button to a form, which when clicked returns - * to the previous page. - * - *
- * Button backButton = new Button("back", " < Back ");
- * backButton.setOnClick("history.back();");
- * backButton.setTitle("Return to previous page");
- * form.add(backButton); 
- * - * HTML output: - *
- * <input type='button' name='back' value=' < Back ' onclick='history.back();'
- *        title='Return to previous page'/> 
- * - * See also W3C HTML reference - * INPUT - * - * @see Reset - * @see Submit - */ -public class Button extends Field { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - // Constructors ----------------------------------------------------------- - - /** - * Create a button with the given name. - * - * @param name the button name - */ - public Button(String name) { - super(name); - } - - /** - * Create a button with the given name and label. The button label is - * rendered as the HTML "value" attribute. - * - * @param name the button name - * @param label the button label - */ - public Button(String name, String label) { - setName(name); - setLabel(label); - } - - /** - * Create a button with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public Button() { - } - - // Public Attributes ------------------------------------------------------ - - /** - * Return the button's html tag: input. - * - * @see org.apache.click.control.AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "input"; - } - - /** - * Returns the button onclick attribute value, or null if not defined. - * - * @return the button onclick attribute value, or null if not defined. - */ - public String getOnClick() { - if (attributes != null) { - return attributes.get("onclick"); - } else { - return null; - } - } - - /** - * Sets the button onclick attribute value. - * - * @param value the onclick attribute value. - */ - public void setOnClick(String value) { - setAttribute("onclick", value); - } - - /** - * Return the input type: 'button'. - * - * @return the input type: 'button' - */ - public String getType() { - return "button"; - } - - // Public Methods -------------------------------------------------------- - - /** - * For non Ajax requests this method returns true, as buttons by default - * perform no server side logic. If the button is an Ajax target and a - * behavior is defined, the behavior action will be invoked. - * - * @see Field#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - Context context = getContext(); - - if (context.isAjaxRequest()) { - - if (isDisabled()) { - // Switch off disabled property if control has incoming request - // parameter. Normally this means the field was enabled via JS - if (context.hasRequestParameter(getName())) { - setDisabled(false); - } else { - // If field is disabled skip process event - return true; - } - } - - if (context.hasRequestParameter(getName())) { - dispatchActionEvent(); - } - } - - return true; - } - - /** - * @see AbstractControl#getControlSizeEst() - * - * @return the estimated rendered control size in characters - */ - @Override - public int getControlSizeEst() { - return 40; - } - - /** - * Render the HTML representation of the Button. Note the button label is - * rendered as the HTML "value" attribute. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - buffer.elementStart(getTag()); - - buffer.appendAttribute("type", getType()); - buffer.appendAttribute("name", getName()); - buffer.appendAttribute("id", getId()); - buffer.appendAttribute("value", getLabel()); - buffer.appendAttribute("title", getTitle()); - if (getTabIndex() > 0) { - buffer.appendAttribute("tabindex", getTabIndex()); - } - - appendAttributes(buffer); - - if (isDisabled()) { - buffer.appendAttributeDisabled(); - } - - buffer.elementEnd(); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Column.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Column.java deleted file mode 100644 index a9adbcf431..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Column.java +++ /dev/null @@ -1,1596 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.click.util.PropertyUtils; -import org.apache.commons.lang.math.NumberUtils; - -import java.io.Serializable; -import java.text.MessageFormat; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.StringTokenizer; - -/** - * Provides the Column table data <td> and table header <th> - * renderer. - * - * - *
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
IdNameCategoryAction
834501Alison SmartResidential PropertyView
238454Angus RobinsBondsView
784191Ann MelanResidential PropertyView
- * - *
- * - *

- * - * The Column object provide column definitions for the {@link org.apache.click.control.Table} object. - * - *

Column Options

- * - * The Column class supports a number of rendering options which include: - * - *
    - *
  • {@link #autolink} - the option to automatically render href links - * for email and URL column values
  • - *
  • {@link #attributes} - the CSS style attributes for the table data cell
  • - *
  • {@link #dataClass} - the CSS class for the table data cell
  • - *
  • {@link #dataStyles} - the CSS styles for the table data cell
  • - *
  • {@link #decorator} - the custom column value renderer
  • - *
  • {@link #format} - the MessageFormat pattern rendering - * the column value
  • - *
  • {@link #headerClass} - the CSS class for the table header cell
  • - *
  • {@link #headerStyles} - the CSS styles for the table header cell
  • - *
  • {@link #headerTitle} - the table header cell value to render
  • - *
  • {@link #sortable} - the table column sortable property
  • - *
  • {@link #width} - the table cell width property
  • - *
- * - *

Format Pattern

- * - * The {@link #format} property which specifies {@link MessageFormat} pattern - * a is very useful for formatting column values. For example to render - * formatted number and date values you simply specify: - * - *
- * Table table = new Table("table");
- * table.setClass("isi");
- *
- * Column idColumn = new Column("purchaseId", "ID");
- * idColumn.setFormat("{0,number,#,###}");
- * table.addColumn(idColumn);
- *
- * Column priceColumn = new Column("purchasePrice", "Price");
- * priceColumn.setFormat("{0,number,currency}");
- * priceColumn.setTextAlign("right");
- * table.addColumn(priceColumn);
- *
- * Column dateColumn = new Column("purchaseDate", "Date");
- * dateColumn.setFormat("{0,date,dd MMM yyyy}");
- * table.addColumn(dateColumn);
- *
- * Column orderIdColumn = new Column("order.id", "Order ID");
- * table.addColumn(orderIdColumn);  
- * - *

Column Decorators

- * - * The support custom column value rendering you can specify a {@link Decorator} - * class on columns. The decorator render method is passed the table - * row object and the page request Context. Using the table row you can access - * all the column values enabling you to render a compound value composed of - * multiple column values. For example: - * - *
- * Column column = new Column("email");
- *
- * column.setDecorator(new Decorator() {
- *     public String render(Object row, Context context) {
- *         Person person = (Person) row;
- *         String email = person.getEmail();
- *         String fullName = person.getFullName();
- *         return "<a href='mailto:" + email + "'>" + fullName + "</a>";
- *     }
- * });
- *
- * table.addColumn(column); 
- - * The Context parameter of the decorator render() method enables you to - * render controls to provide additional functionality. For example: - * - *
- * public class CustomerList extends BorderedPage {
- *
- *     private Table table = new Table("table");
- *     private ActionLink viewLink = new ActionLink("view");
- *
- *     public CustomerList() {
- *
- *         viewLink.setListener(this, "onViewClick");
- *         viewLink.setLabel("View");
- *         viewLink.setAttribute("title", "View customer details");
- *         table.addControl(viewLink);
- *
- *         table.addColumn(new Column("id"));
- *         table.addColumn(new Column("name"));
- *         table.addColumn(new Column("category"));
- *
- *         Column column = new Column("Action");
- *         column.setDecorator(new Decorator() {
- *             public String render(Object row, Context context) {
- *                 Customer customer = (Customer) row;
- *                 viewLink.setValue("" + customer.getId());
- *
- *                 return viewLink.toString();
- *             }
- *          });
- *         table.addColumn(column);
- *
- *         addControl(table);
- *     }
- *
- *     public boolean onViewClick() {
- *         String path = getContext().getPagePath(Logout.class);
- *         setRedirect(path + "?id=" + viewLink.getValue());
- *         return true;
- *     }
- * } 
- * - *

Internationalization

- * - * Column header titles can be localized using the controls parent messages. - * If the column header title value is null, the column will attempt to find a - * localized message in the parent messages using the key: - *
- * getName() + ".headerTitle" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - * If a value still cannot be found then the Column name will be converted - * into a header title using the method: {@link ClickUtils#toLabel(String)}. - *

- * - * @see Decorator - * @see org.apache.click.control.Table - */ -public class Column implements Serializable { - - private static final long serialVersionUID = 1L; - - // ----------------------------------------------------- Instance Variables - - /** The Column attributes Map. */ - protected Map attributes; - - /** - * The automatically hyperlink column URL and email address values flag, - * default value is false. - */ - protected boolean autolink; - - /** The column table data <td> CSS class attribute. */ - protected String dataClass; - - /** The Map of column table data <td> CSS style attributes. */ - protected Map dataStyles; - - /** The column row decorator. */ - protected Decorator decorator; - - /** The escape HTML characters flag. The default value is true. */ - protected boolean escapeHtml = true; - - /** The column message format pattern. */ - protected String format; - - /** The CSS class attribute of the column header. */ - protected String headerClass; - - /** The Map of column table header <th> CSS style attributes. */ - protected Map headerStyles; - - /** The title of the column header. */ - protected String headerTitle; - - /** - * The maximum column length. If maxLength is greater than 0 and the column - * data string length is greater than maxLength, the rendered value will be - * truncated with an eclipse(...). - *

- * Autolinked email or URL values will not be constrained. - *

- * The default value is 0. - */ - protected int maxLength; - - /** - * The optional MessageFormat used to render the column table cell value. - */ - protected MessageFormat messageFormat; - - /** The property name of the row object to render. */ - protected String name; - - /** The column render id attribute status. The default value is false. */ - protected Boolean renderId; - - /** The method cached for rendering column values. */ - protected transient Map methodCache; - - /** The column sortable status. The default value is false. */ - protected Boolean sortable; - - /** The parent Table. */ - protected Table table; - - /** - * The property name used to populate the <td> "title" attribute. - * The default value is null. - */ - protected String titleProperty; - - /** The column HTML <td> width attribute. */ - protected String width; - - /** The column comparator object, which is used to sort column row values. */ - @SuppressWarnings("unchecked") - Comparator comparator; - - // ----------------------------------------------------------- Constructors - - /** - * Create a table column with the given property name. The table header - * title will be set as the capitalized property name. - * - * @param name the name of the property to render - */ - public Column(String name) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - this.name = name; - } - - /** - * Create a table column with the given property name and header title. - * - * @param name the name of the property to render - * @param title the column header title to render - */ - public Column(String name, String title) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - if (title == null) { - throw new IllegalArgumentException("Null title parameter"); - } - this.name = name; - this.headerTitle = title; - } - - /** - * Create a Column with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public Column() { - } - - // ------------------------------------------------------ Public Properties - - /** - * Return the Column HTML attribute with the given name, or null if the - * attribute does not exist. - * - * @param name the name of column HTML attribute - * @return the Column HTML attribute - */ - public String getAttribute(String name) { - return getAttributes().get(name); - } - - /** - * Set the Column with the given HTML attribute name and value. These - * attributes will be rendered as HTML attributes, for example: - *

- * If there is an existing named attribute in the Column it will be replaced - * with the new value. If the given attribute value is null, any existing - * attribute will be removed. - * - * @param name the name of the column HTML attribute - * @param value the value of the column HTML attribute - * @throws IllegalArgumentException if attribute name is null - */ - public void setAttribute(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getAttributes().put(name, value); - } else { - getAttributes().remove(name); - } - } - - /** - * Return the Column attributes Map. - * - * @return the column attributes Map. - */ - public Map getAttributes() { - if (attributes == null) { - attributes = new HashMap(); - } - return attributes; - } - - /** - * Return true if the Column has attributes or false otherwise. - * - * @return true if the column has attributes on false otherwise - */ - public boolean hasAttributes() { - return (attributes != null && !getAttributes().isEmpty()); - } - - /** - * Return the flag to automatically render HTML hyperlinks for column URL - * and email addresses values. - * - * @return automatically hyperlink column URL and email addresses flag - */ - public boolean getAutolink() { - return autolink; - } - - /** - * Set the flag to automatically render HTML hyperlinks for column URL - * and email addresses values. - * - * @param autolink the flag to automatically hyperlink column URL and - * email addresses flag - */ - public void setAutolink(boolean autolink) { - this.autolink = autolink; - } - - /** - * Return the column comparator object, which is used to sort column row - * values. - * - * @return the column row data sorting comparator object. - */ - @SuppressWarnings("unchecked") - public Comparator getComparator() { - if (comparator == null) { - comparator = new ColumnComparator(this); - } - return comparator; - } - - /** - * Set the column comparator object, which is used to sort column row - * values. - * - * @param comparator the column row data sorting comparator object - */ - @SuppressWarnings("unchecked") - public void setComparator(Comparator comparator) { - this.comparator = comparator; - } - - /** - * Return the table data <td> CSS class. - * - * @return the table data CSS class - */ - public String getDataClass() { - return dataClass; - } - - /** - * Set the table data <td> CSS class. - * - * @param dataClass the table data CSS class - */ - public void setDataClass(String dataClass) { - this.dataClass = dataClass; - } - - /** - * Return the table data <td> CSS style. - * - * @param name the CSS style name - * @return the table data CSS style for the given name - */ - public String getDataStyle(String name) { - if (hasDataStyles()) { - return getDataStyles().get(name); - - } else { - return null; - } - } - - /** - * Set the table data <td> CSS style name and value pair. - * - * @param name the CSS style name - * @param value the CSS style value - */ - public void setDataStyle(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getDataStyles().put(name, value); - } else { - getDataStyles().remove(name); - } - } - - /** - * Return true if table data <td> CSS styles are defined. - * - * @return true if table data <td> CSS styles are defined - */ - public boolean hasDataStyles() { - return (dataStyles != null && !dataStyles.isEmpty()); - } - - /** - * Return the Map of table data <td> CSS styles. - * - * @return the Map of table data <td> CSS styles - */ - public Map getDataStyles() { - if (dataStyles == null) { - dataStyles = new HashMap(); - } - return dataStyles; - } - - /** - * Return the row column <td> decorator. - * - * @return the row column <td> decorator - */ - public Decorator getDecorator() { - return decorator; - } - - /** - * Set the row column <td> decorator. - * - * @param decorator the row column <td> decorator - */ - public void setDecorator(Decorator decorator) { - this.decorator = decorator; - } - - /** - * Return true if the HTML characters will be escaped when rendering the - * column data. By default this method returns true. - * - * @return true if the HTML characters will be escaped when rendered - */ - public boolean getEscapeHtml() { - return escapeHtml; - } - - /** - * Set the escape HTML characters when rendering column data flag. - * - * @param escape the flag to escape HTML characters - */ - public void setEscapeHtml(boolean escape) { - this.escapeHtml = escape; - } - - /** - * Return the row column message format pattern. - * - * @return the message row column message format pattern - */ - public String getFormat() { - return format; - } - - /** - * Set the row column message format pattern. For example: - * - *

-     * Column idColumn = new Column("purchaseId", "ID");
-     * idColumn.setFormat("{0,number,#,###}");
-     *
-     * Column priceColumn = new Column("purchasePrice", "Price");
-     * priceColumn.setFormat("{0,number,currency}");
-     *
-     * Column dateColumn = new Column("purchaseDate", "Date");
-     * dateColumn.setFormat("{0,date,dd MMM yyyy}"); 
- * - *

MesssageFormat Patterns

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Format Type - * Format Style - * Subformat Created - *
number - * (none) - * NumberFormat.getInstance(getLocale()) - *
integer - * NumberFormat.getIntegerInstance(getLocale()) - *
currency - * NumberFormat.getCurrencyInstance(getLocale()) - *
percent - * NumberFormat.getPercentInstance(getLocale()) - *
SubformatPattern - * new DecimalFormat(subformatPattern, new DecimalFormatSymbols(getLocale())) - *
date - * (none) - * DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale()) - *
short - * DateFormat.getDateInstance(DateFormat.SHORT, getLocale()) - *
medium - * DateFormat.getDateInstance(DateFormat.DEFAULT, getLocale()) - *
long - * DateFormat.getDateInstance(DateFormat.LONG, getLocale()) - *
full - * DateFormat.getDateInstance(DateFormat.FULL, getLocale()) - *
SubformatPattern - * new SimpleDateFormat(subformatPattern, getLocale()) - *
time - * (none) - * DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale()) - *
short - * DateFormat.getTimeInstance(DateFormat.SHORT, getLocale()) - *
medium - * DateFormat.getTimeInstance(DateFormat.DEFAULT, getLocale()) - *
long - * DateFormat.getTimeInstance(DateFormat.LONG, getLocale()) - *
full - * DateFormat.getTimeInstance(DateFormat.FULL, getLocale()) - *
SubformatPattern - * new SimpleDateFormat(subformatPattern, getLocale()) - *
choice - * SubformatPattern - * new ChoiceFormat(subformatPattern) - *
- * - *

DecimalFormat Pattern Characters

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Symbol - * Location - * Localized? - * Meaning - *
0 - * Number - * Yes - * Digit - *
# - * Number - * Yes - * Digit, zero shows as absent - *
. - * Number - * Yes - * Decimal separator or monetary decimal separator - *
- - * Number - * Yes - * Minus sign - *
, - * Number - * Yes - * Grouping separator - *
E - * Number - * Yes - * Separates mantissa and exponent in scientific notation. - * Need not be quoted in prefix or suffix. - *
; - * Subpattern boundary - * Yes - * Separates positive and negative subpatterns - *
% - * Prefix or suffix - * Yes - * Multiply by 100 and show as percentage - *
\u2030 - * Prefix or suffix - * Yes - * Multiply by 1000 and show as per mille - *
¤ (\u00A4) - * Prefix or suffix - * No - * Currency sign, replaced by currency symbol. If - * doubled, replaced by international currency symbol. - * If present in a pattern, the monetary decimal separator - * is used instead of the decimal separator. - *
' - * Prefix or suffix - * No - * Used to quote special characters in a prefix or suffix, - * for example, "'#'#" formats 123 to - * "#123". To create a single quote - * itself, use two in a row: "# o''clock". - *
- * - *

SimpleDateFormat Pattern Characters

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
Letter - * Date or Time Component - * Presentation - * Examples - *
G - * Era designator - * Text - * AD - *
y - * Year - * Year - * 1996; 96 - *
M - * Month in year - * Month - * July; Jul; 07 - *
w - * Week in year - * Number - * 27 - *
W - * Week in month - * Number - * 2 - *
D - * Day in year - * Number - * 189 - *
d - * Day in month - * Number - * 10 - *
F - * Day of week in month - * Number - * 2 - *
E - * Day in week - * Text - * Tuesday; Tue - *
a - * Am/pm marker - * Text - * PM - *
H - * Hour in day (0-23) - * Number - * 0 - *
k - * Hour in day (1-24) - * Number - * 24 - *
K - * Hour in am/pm (0-11) - * Number - * 0 - *
h - * Hour in am/pm (1-12) - * Number - * 12 - *
m - * Minute in hour - * Number - * 30 - *
s - * Second in minute - * Number - * 55 - *
S - * Millisecond - * Number - * 978 - *
z - * Time zone - * General time zone - * Pacific Standard Time; PST; GMT-08:00 - *
Z - * Time zone - * RFC 822 time zone - * -0800 - *
- * - * @param pattern the message format pattern - */ - public void setFormat(String pattern) { - this.format = pattern; - } - - /** - * The maximum column length. If maxLength is greater than 0 and the column - * data string length is greater than maxLength, the rendered value will be - * truncated with an eclipse(...). - * - * @return the maximum column length, or 0 if not defined - */ - public int getMaxLength() { - return maxLength; - } - - /** - * Set the maximum column length. If maxLength is greater than 0 and the - * column data string length is greater than maxLength, the rendered value - * will be truncated with an eclipse(...). - * - * @param value the maximum column length - */ - public void setMaxLength(int value) { - maxLength = value; - } - - /** - * Return the MessageFormat instance used to format the table cell value. - * - * @return the MessageFormat instance used to format the table cell value - */ - public MessageFormat getMessageFormat() { - return messageFormat; - } - - /** - * Set the MessageFormat instance used to format the table cell value. - * - * @param messageFormat the MessageFormat used to format the table cell - * value - */ - public void setMessageFormat(MessageFormat messageFormat) { - this.messageFormat = messageFormat; - } - - /** - * Return the property name. - * - * @return the name of the property - */ - public String getName() { - return name; - } - - /** - * Set the property name. - * - * @param name the property name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * Return the table header <th> CSS class. - * - * @return the table header CSS class - */ - public String getHeaderClass() { - return headerClass; - } - - /** - * Set the table header <th> CSS class. - * - * @param headerClass the table header CSS class - */ - public void setHeaderClass(String headerClass) { - this.headerClass = headerClass; - } - - /** - * Return the table header <th> CSS style. - * - * @param name the CSS style name - * @return the table header CSS style value for the given name - */ - public String getHeaderStyle(String name) { - if (hasHeaderStyles()) { - return getHeaderStyles().get(name); - - } else { - return null; - } - } - - /** - * Set the table header <th> CSS style name and value pair. - * - * @param name the CSS style name - * @param value the CSS style value - */ - public void setHeaderStyle(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getHeaderStyles().put(name, value); - } else { - getHeaderStyles().remove(name); - } - } - - /** - * Return true if table header <th> CSS styles are defined. - * - * @return true if table header <th> CSS styles are defined - */ - public boolean hasHeaderStyles() { - return (headerStyles != null && !headerStyles.isEmpty()); - } - - /** - * Return the Map of table header <th> CSS styles. - * - * @return the Map of table header <th> CSS styles - */ - public Map getHeaderStyles() { - if (headerStyles == null) { - headerStyles = new HashMap(); - } - return headerStyles; - } - - /** - * Return the table header <th> title. - *

- * If the header title value is null, this method will attempt to find a - * localized message in the parent messages using the key: - *

- * getName() + ".headerTitle" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - * If a value still cannot be found then the Column name will be converted - * into a header title using the method: {@link ClickUtils#toLabel(String)} - *

- * - * @return the table header title - */ - public String getHeaderTitle() { - if (headerTitle == null) { - headerTitle = getTable().getMessage(getName() + ".headerTitle"); - } - if (headerTitle == null) { - headerTitle = ClickUtils.toLabel(getName()); - } - return headerTitle; - } - - /** - * Set the table header <th> title. - * - * @param title the table header title - */ - public void setHeaderTitle(String title) { - headerTitle = title; - } - - /** - * Return the Table and Column id appended:   "table-column" - *

- * Use the field the "id" attribute value if defined, or the name otherwise. - * - * @return HTML element identifier attribute "id" value - */ - public String getId() { - if (hasAttributes() && getAttributes().containsKey("id")) { - return getAttribute("id"); - - } else { - String tableId = (getTable() != null) - ? getTable().getId() + "-" : ""; - - String id = tableId + getName(); - - if (id.indexOf('/') != -1) { - id = id.replace('/', '_'); - } - if (id.indexOf(' ') != -1) { - id = id.replace(' ', '_'); - } - - return id; - } - } - - /** - * Return the column sortable status. If the column sortable status is not - * defined the value will be inherited from the - * {@link org.openidentityplatform.openam.click.control.Table#sortable} property. - * - * @return the column sortable status - */ - public boolean getSortable() { - if (sortable == null) { - if (getTable() != null) { - return getTable().getSortable(); - } else { - return false; - } - - } else { - return sortable; - } - } - - /** - * Set the column render id attribute status. - * - * @param value set the column render id attribute status - */ - public void setRenderId(boolean value) { - renderId = value; - } - - /** - * Returns the column render id attribute status. If the column render id - * status is not defined the value will be inherited from the - * {@link org.openidentityplatform.openam.click.control.Table#renderId} property. - * - * @return the column render id attribute status - */ - public boolean getRenderId() { - if (renderId == null) { - if (getTable() != null) { - return getTable().getRenderId(); - } else { - return false; - } - - } else { - return renderId; - } - } - - /** - * Set the column sortable status. - * - * @param value the column sortable status - */ - public void setSortable(boolean value) { - sortable = value; - } - - /** - * Return the parent Table containing the Column. - * - * @return the parent Table containing the Column - */ - public Table getTable() { - return table; - } - - /** - * Set the Column's the parent Table. - * - * @param table Column's parent Table - */ - public void setTable(Table table) { - this.table = table; - } - - /** - * Set the column CSS "text-align" style for the header <th> and - * data <td> elements. Valid values include: - * [left, right, center] - * - * @param align the CSS "text-align" value: [left, right, center] - */ - public void setTextAlign(String align) { - if (align != null && "middle".equalsIgnoreCase(align)) { - String msg = - "\"middle\" is not a valid CSS \"text-align\" " - + "value, use \"center\" instead"; - throw new IllegalArgumentException(msg); - } - if (!getSortable()) { - setHeaderStyle("text-align", align); - } - setDataStyle("text-align", align); - } - - /** - * Return the property name used to populate the <td> "title" attribute. - * - * @return the property name used to populate the <td> "title" attribute - */ - public String getTitleProperty() { - return titleProperty; - } - - /** - * Set the property name used to populate the <td> "title" attribute. - * - * @param property the property name used to populate the <td> "title" attribute - */ - public void setTitleProperty(String property) { - titleProperty = property; - } - - /** - * Set the column CSS "vertical-align" style for the header <th> and - * data <td> elements. Valid values include: - * [baseline | sub | super | top | text-top | middle | bottom | - * text-bottom | <percentage> | <length> | inherit] - * - * @param align the CSS "vertical-align" value - */ - public void setVerticalAlign(String align) { - if (align != null && "center".equalsIgnoreCase(align)) { - String msg = - "\"center\" is not a valid CSS \"vertical-align\" " - + "value, use \"middle\" instead"; - throw new IllegalArgumentException(msg); - } - setHeaderStyle("vertical-align", align); - setDataStyle("vertical-align", align); - } - - /** - * Return the column HTML <td> width attribute. - * - * @return the column HTML <td> width attribute - */ - public String getWidth() { - return width; - } - - /** - * Set the column HTML <td> width attribute. - * - * @param value the column HTML <td> width attribute - */ - public void setWidth(String value) { - width = value; - } - - // --------------------------------------------------------- Public Methods - - /** - * Render the column table data <td> element to the given buffer using - * the passed row object. - * - * @param row the row object to render - * @param buffer the string buffer to render to - * @param context the request context - * @param rowIndex the index of the current row within the parent table - */ - public void renderTableData(Object row, HtmlStringBuffer buffer, - Context context, int rowIndex) { - - if (getMessageFormat() == null && getFormat() != null) { - Locale locale = context.getLocale(); - setMessageFormat(new MessageFormat(getFormat(), locale)); - } - - buffer.elementStart("td"); - if (getRenderId()) { - String id = getId(); - buffer.append(" id=\"").append(id).append("_").append(rowIndex).append("\""); - } - buffer.appendAttribute("class", getDataClass()); - - if (getTitleProperty() != null) { - Object titleValue = getProperty(getTitleProperty(), row); - buffer.appendAttributeEscaped("title", titleValue); - } - if (hasAttributes()) { - buffer.appendAttributes(getAttributes()); - } - if (hasDataStyles()) { - buffer.appendStyleAttributes(getDataStyles()); - } - buffer.appendAttribute("width", getWidth()); - buffer.closeTag(); - - renderTableDataContent(row, buffer, context, rowIndex); - - buffer.elementEnd("td"); - } - - /** - * Render the column table header <tr> element to the given buffer. - * - * @param buffer the string buffer to render to - * @param context the request context - */ - public void renderTableHeader(HtmlStringBuffer buffer, Context context) { - buffer.elementStart("th"); - - boolean isSortable = getSortable() && !getTable().getRowList().isEmpty(); - boolean sortedColumn = getName().equals(getTable().getSortedColumn()); - boolean ascending = getTable().isSortedAscending(); - - if (isSortable) { - String classValue = - (getHeaderClass() != null) ? getHeaderClass() + " " : ""; - - if (sortedColumn) { - classValue += (ascending) ? "ascending" : "descending"; - } else { - classValue += "sortable"; - } - buffer.appendAttribute("class", classValue); - - } else { - buffer.appendAttribute("class", getHeaderClass()); - } - - if (hasHeaderStyles()) { - buffer.appendStyleAttributes(getHeaderStyles()); - } - - if (hasAttributes()) { - buffer.appendAttributes(getAttributes()); - } - - buffer.closeTag(); - - if (isSortable) { - ActionLink controlLink = getTable().getControlLink(); - - controlLink.setParameter(org.apache.click.control.Table.COLUMN, getName()); - controlLink.setParameter(org.apache.click.control.Table.PAGE, String.valueOf(getTable().getPageNumber())); - - if (sortedColumn) { - controlLink.setParameter(org.apache.click.control.Table.ASCENDING, String.valueOf(ascending)); - controlLink.setParameter(org.apache.click.control.Table.SORT, "true"); - - } else { - controlLink.setParameter(org.apache.click.control.Table.ASCENDING, null); - controlLink.setParameter(org.apache.click.control.Table.SORT, null); - } - - // Provide spacer for sorting icon - controlLink.setLabel(getHeaderTitle() + "   "); - - controlLink.render(buffer); - - } else { - if (getEscapeHtml()) { - buffer.appendEscaped(getHeaderTitle()); - } else { - buffer.append(getHeaderTitle()); - } - } - - buffer.elementEnd("th"); - } - - /** - * Return the column name property value from the given row object. - *

- * If the row object is a Map this method will attempt to return - * the map value for the column name. The row map lookup will be performed - * using the property name, if a value is not found the property name in - * uppercase will be used, if a value is still not found the property name - * in lowercase will be used. If a map value is still not found then this - * method will return null. - *

- * Object property values can also be specified using a path expression. - * - * @param row the row object to obtain the property from - * @return the named row object property value - * @throws RuntimeException if an error occurred obtaining the property - */ - public Object getProperty(Object row) { - return getProperty(getName(), row); - } - - /** - * Return the column property value from the given row object and property name. - *

- * If the row object is a Map this method will attempt to return - * the map value for the column. The row map lookup will be performed using - * the property name, if a value is not found the property name in uppercase - * will be used, if a value is still not found the property name in lowercase - * will be used. If a map value is still not found then this method will - * return null. - *

- * Object property values can also be specified using a path expression. - * - * @param name the name of the property - * @param row the row object to obtain the property from - * @return the named row object property value - * @throws RuntimeException if an error occurred obtaining the property - */ - @SuppressWarnings("unchecked") - public Object getProperty(String name, Object row) { - if (row instanceof Map) { - Map map = (Map) row; - - Object object = map.get(name); - if (object != null) { - return object; - } - - String upperCaseName = name.toUpperCase(); - object = map.get(upperCaseName); - if (object != null) { - return object; - } - - String lowerCaseName = name.toLowerCase(); - object = map.get(lowerCaseName); - if (object != null) { - return object; - } - - return null; - - } else { - if (methodCache == null) { - methodCache = new HashMap(); - } - - return PropertyUtils.getValue(row, name, methodCache); - } - } - - // ------------------------------------------------------ Protected Methods - - /** - * Render the content within the column table data <td> element. - * - * @param row the row object to render - * @param buffer the string buffer to render to - * @param context the request context - * @param rowIndex the index of the current row within the parent table - */ - protected void renderTableDataContent(Object row, HtmlStringBuffer buffer, - Context context, int rowIndex) { - - if (getDecorator() != null) { - String value = getDecorator().render(row, context); - if (value != null) { - buffer.append(value); - } - - } else { - Object columnValue = getProperty(row); - if (columnValue != null) { - if (getAutolink() && renderLink(columnValue, buffer)) { - // Has been rendered - - } else if (getMessageFormat() != null) { - Object[] args = new Object[] { columnValue }; - - String value = getMessageFormat().format(args); - - if (getMaxLength() > 0) { - value = ClickUtils.limitLength(value, getMaxLength()); - } - - if (getEscapeHtml()) { - buffer.appendEscaped(value); - } else { - buffer.append(value); - } - - } else { - String value = columnValue.toString(); - - if (getMaxLength() > 0) { - value = ClickUtils.limitLength(value, getMaxLength()); - } - - if (getEscapeHtml()) { - buffer.appendEscaped(value); - } else { - buffer.append(value); - } - } - } - } - } - - /** - * Render the given table cell value to the buffer as a mailto: - * or http: hyperlink, or as an ordinary string if the value is - * determined not be linkable. - * - * @param value the table cell value to render - * @param buffer the StringBuffer to render to - * @return a rendered email or web hyperlink if applicable - */ - protected boolean renderLink(Object value, HtmlStringBuffer buffer) { - if (value != null) { - String valueStr = value.toString(); - - // If email - if (valueStr.indexOf('@') != -1 - && !valueStr.startsWith("@") - && !valueStr.endsWith("@")) { - - buffer.append(""); - buffer.append(valueStr); - buffer.append(""); - return true; - - } else if (valueStr.startsWith("http")) { - int index = valueStr.indexOf("//"); - if (index != -1) { - index += 2; - } else { - index = 0; - } - buffer.append(""); - buffer.append(valueStr.substring(index)); - buffer.append(""); - return true; - - } else if (valueStr.startsWith("www")) { - buffer.append(""); - buffer.append(valueStr); - buffer.append(""); - return true; - } - } - return false; - } - - // ---------------------------------------------------------- Inner Classes - - /** - * Provides a table Column comparator for sorting table rows. - * - * @see Column - * @see Table - */ - @SuppressWarnings("unchecked") - static class ColumnComparator implements Comparator, Serializable { - - private static final long serialVersionUID = 1L; - - /** The sort ascending flag. */ - protected int ascendingSort; - - /** The column to sort on. */ - protected final Column column; - - /** - * Create a new Column based row comparator. - * - * @param column the column to sort on - */ - public ColumnComparator(Column column) { - this.column = column; - } - - /** - * @see Comparator#compare(Object, Object) - * - * @param row1 the first row to compare - * @param row2 the second row to compare - * @return the comparison result - */ - public int compare(Object row1, Object row2) { - - this.ascendingSort = column.getTable().isSortedAscending() ? 1 : -1; - - Object value1 = column.getProperty(row1); - Object value2 = column.getProperty(row2); - - if (value1 instanceof Comparable && value2 instanceof Comparable) { - - if (value1 instanceof String || value2 instanceof String) { - return stringCompare(value1, value2) * ascendingSort; - - } else { - - return ((Comparable) value1).compareTo(value2) * ascendingSort; - } - - } else if (value1 != null && value2 != null) { - - return value1.toString().compareToIgnoreCase(value2.toString()) - * ascendingSort; - - } else if (value1 != null && value2 == null) { - - return +1 * ascendingSort; - - } else if (value1 == null && value2 != null) { - - return -1 * ascendingSort; - - } else { - return 0; - } - } - - // ------------------------------------------------------ Protected Methods - - /** - * Perform a comparison on the given string values. - * - * @param value1 the first value to compare - * @param value2 the second value to compare - * @return the string comparison result - */ - protected int stringCompare(Object value1, Object value2) { - String string1 = value1.toString().trim(); - String string2 = value2.toString().trim(); - - StringTokenizer st1 = new StringTokenizer(string1); - StringTokenizer st2 = new StringTokenizer(string2); - - String token1 = null; - String token2 = null; - - while (st1.hasMoreTokens()) { - token1 = st1.nextToken(); - - if (st2.hasMoreTokens()) { - token2 = st2.nextToken(); - - int comp = 0; - - if (useNumericSort(token1, token2)) { - comp = numericCompare(token1, token2); - - } else { - comp = token1.compareToIgnoreCase(token2); - } - - if (comp != 0) { - return comp; - } - - } else { - return -1; - } - } - - return 0; - } - - /** - * Return true if a numeric sort should be used. - * - * @param value1 the first value to test - * @param value2 the second value to test - * @return true if a numeric sort should be used - */ - protected boolean useNumericSort(String value1, String value2) { - return NumberUtils.isDigits(value1) && NumberUtils.isDigits(value2); - } - - /** - * Perform a numeric compare on the given string values. - * - * @param string1 the first string value to compare - * @param string2 the second string value to compare - * @return the numeric comparison result - */ - protected int numericCompare(String string1, String string2) { - if (string1.length() > 0 && string2.length() > 0) { - Double double1 = Double.valueOf(string1); - Double double2 = Double.valueOf(string2); - - return double1.compareTo(double2); - - } else if (string1.length() > 0) { - return 1; - - } else if (string2.length() > 0) { - return -1; - - } else { - return 0; - } - } - - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Container.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Container.java deleted file mode 100644 index c960e6239f..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Container.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import java.util.List; -import org.openidentityplatform.openam.click.Control; -import org.apache.click.control.AbstractContainer; - -/** - * Provides the interface for a container which holds a list of child controls. - *

- * Container extends {@link org.apache.click.Control} and enables the creation of - * nested controls. - *

- * Container allows one to add, remove and retrieve child controls. - *

- * Please note {@link AbstractContainer} provides - * a default implementation of the Container interface to make it easier for - * developers to create their own containers. - * - * @see org.apache.click.util.ContainerUtils - */ -public interface Container extends Control { - - /** - * Add the control to the container and return the added instance. - * - * @param control the control to add to the container and return - * @return the control that was added to the container - */ - Control add(Control control); - - /** - * Add the control to the container at the specified index, and return the - * added instance. - * - * @param control the control to add to the container and return - * @param index the index at which the control is to be inserted - * @return the control that was added to the container - * @throws IndexOutOfBoundsException if the index is out of range - * (index < 0 || index > getControls().size()). - */ - Control insert(Control control, int index); - - /** - * Replace the current control with the new control, and return the newly - * added control. - * - * @deprecated this method was used for stateful pages, which have been deprecated - * - * @param currentControl the control currently contained in the container - * @param newControl the control to replace the current control contained in - * the container - * @return the new control that replaced the current control - */ - Control replace(Control currentControl, Control newControl); - - /** - * Remove the given control from the container, returning true if the - * control was found in the container and removed, or false if the control - * was not found. - * - * @param control the control to remove from the container - * @return true if the control was removed from the container - */ - boolean remove(Control control); - - /** - * Return the sequential list of controls held by the container. - * - * @return the sequential list of controls held by the container - */ - List getControls(); - - /** - * Return the named control from the container if found or null otherwise. - * - * @param controlName the name of the control to get from the container - * @return the named control from the container if found or null otherwise - */ - Control getControl(String controlName); - - /** - * Return true if the container contains the specified control. - * - * @param control the control whose presence in this container is to be tested - * @return true if the container contains the specified control - */ - boolean contains(Control control); - - /** - * Returns true if this container has existing controls, false otherwise. - * - * @return true if the container has existing controls, false otherwise. - */ - public boolean hasControls(); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Decorator.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Decorator.java deleted file mode 100644 index 275aa8ca16..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Decorator.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; - -/** - * Provides a decorator interface for delegating object rendering. - * - *

Decorator Example

- * - * The following example illustrates how to render a email hyperlink in a - * email table column. - * - *
- * Column column = new Column("email");
- *
- * column.setDecorator(new Decorator() {
- *     public String render(Object row, Context context) {
- *         Customer customer = (Customer) row;
- *         String email = customer.getEmail();
- *         String fullName = customer.getFullName();
- *         return "<a href='mailto:" + email + "'>" + fullName + "</a>";
- *     }
- * });
- *
- * table.addColumn(column); 
- * - * @see Column - * @see Table - */ -public interface Decorator { - - /** - * Returns a decorated string representation of the given object. - * - * @param object the object to render - * @param context the request context - * @return a decorated string representation of the given object - */ - public String render(Object object, Context context); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Field.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Field.java deleted file mode 100644 index 400beee427..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Field.java +++ /dev/null @@ -1,1288 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.apache.click.Stateful; -import org.apache.click.control.Checkbox; -import org.apache.click.control.FieldSet; -import org.apache.commons.lang.StringUtils; -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.Page; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.ContainerUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides an abstract form Field control. Field controls are contained by - * the {@link Form} control which will orchestrate the processing and - * rendering of the contained fields. All Form field controls must extend this - * abstract class. - * - *

Field Processing

- * - *

Post Requests

- * - * When processing POST requests forms typically invoke the {@link #onProcess()} - * method on all its fields. The Field onProcess() method is used - * to bind the fields request value, validate the submission and invoke any - * control listener method. If the onProcess() method returns true - * the form will continue processing fields, otherwise the form will abort - * further processing. - *

- * The body of the Field onProcess() method is detailed below. - * - *

- * public boolean onProcess() {
- *     bindRequestValue();
- *
- *     if (getValidate()) {
- *         validate();
- *     }
- *
- *     registerActionEvent();
- *
- *     return true;
- * } 
- * - * The Field methods called by onProcess() include: - * - *
- *
{@link #bindRequestValue()}
- *
This method will bind the HTTP request value to the Field's value. - *
- *
{@link #getValidate()}
- *
This method will return true if the Field should validate itself. This - * value is generally inherited from the parent Form, however the Field can - * override this value and specify whether it should be validated. - *
- *
{@link #validate()}
- *
This method will validate the submitted Field value. If the submitted - * value is not valid this method should set the Field {@link #error} property, - * which can be rendered by the Form. - *
- *
{@link #dispatchActionEvent()}
- *
This method will register any Control action listener method which has be - * defined for the Field. - *
- *
- * - * Field subclasses generally only have to override the validate() - * method, and possibly the bindRequestValue() method, to provide their - * own behaviour. - * - *

Get Requests

- * - * When processing GET requests a Page's Form will typically perform no - * processing and simply render itself and its Fields. - * - *

Rendering

- * - * Field subclasses must override the {@link #render(org.openidentityplatform.openam.click.util.HtmlStringBuffer)} - * method to enable themselves to be rendered as HTML. With the increasing use of - * AJAX, Fields should render themselves as valid XHTML, so that they may be parsed - * correctly and used as the innerHtml in the DOM. - *

- * When a Form object renders a Field using autolayout, it renders the - * Field in a table row using the Field's {@link #label} attribute, its - * {@link #error} attribute if defined, and the Fields - * {@link #render(org.openidentityplatform.openam.click.util.HtmlStringBuffer)} method. - *

- * To assist with rendering valid HTML Field subclasses can use the - * {@link org.apache.click.util.HtmlStringBuffer} class. - * - *

Message Resources and Internationalization (i18n)

- * - * Fields support a hierarchy of resource bundles for displaying validation - * error messages and display messages. These localized messages can be accessed - * through the methods: - * - *
    - *
  • {@link #getMessage(String)}
  • - *
  • {@link #getMessage(String, Object...)}
  • - *
  • {@link #getMessages()}
  • - *
  • {@link #setErrorMessage(String)}
  • - *
  • {@link #setErrorMessage(String, Object)}
  • - *
- * - * Fields automatically pick up localized messages where applicable. Please see - * the following methods on how to customize these messages: - *
    - *
  • {@link #getLabel()}
  • - *
  • {@link #getTitle()}
  • - *
  • {@link #getHelp()}
  • - *
  • {@link #setErrorMessage(String)}
  • - *
  • {@link #setErrorMessage(String, Object)}
  • - *
- * - * - * The order in which localized messages resolve are: - *
- *
Page scope messages
- *
Message lookups are first resolved to the Pages message bundle if it - * exists. For example a Login page may define the message properties: - * - *
- * /com/mycorp/page/Login.properties 
- * - * If you want messages to be used only for a specific Page, this is where - * to place them. - *
- * - *
Global page scope messages
- *
Next message lookups are resolved to the global pages message bundle if it - * exists. - * - *
- * /click-page.properties 
- * - * If you want messages to be used across your entire application this is where - * to place them. - *
- * - *
Control scope messages
- *
Next message lookups are resolved to the Control message bundle if it - * exists. For example a CustomTextField control may define the - * message properties: - * - *
- * /com/mycorp/control/CustomTextField.properties 
- *
- * - *
Global control scope messages
- *
Finally message lookups are resolved to the global application control - * message bundle if the message has not already found. The global control - * properties file is: - * - *
- * /click-control.properties 
- * - * You can modify these properties by copying this file into your applications - * root class path and editing these properties. - *

- * Note when customizing the message properties you must include all the - * properties, not just the ones you want to override. - *

- *
- */ -public abstract class Field extends AbstractControl implements Stateful { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - // Instance Variables ----------------------------------------------------- - - /** The Field disabled value. */ - protected boolean disabled; - - /** The Field error message. */ - protected String error; - - /** The request focus flag. */ - protected boolean focus; - - /** The parent Form. */ - protected Form form; - - /** The Field help text. */ - protected String help; - - /** The Field label. */ - protected String label; - - /** The field label "style" attribute value. */ - protected String labelStyle; - - /** The field label "class" attribute value. */ - protected String labelStyleClass; - - /** The field's parent element "style" attribute hint. */ - protected String parentStyleHint; - - /** The field's parent element "class" attribute hint. */ - protected String parentStyleClassHint; - - /** The Field is readonly flag. */ - protected boolean readonly; - - /** The Field is required flag. */ - protected boolean required; - - /** The Field 'tabindex' attribute. */ - protected int tabindex; - - /** The Field 'title' attribute, which acts as a tooltip help message. */ - protected String title; - - /** The Field is trimmed flag, default value is true. */ - protected boolean trim = true; - - /** - * The validate Field value onProcess() invocation flag. - */ - protected Boolean validate; - - /** The Field value. */ - protected String value; - - // Constructors ----------------------------------------------------------- - - /** - * Construct a new Field object. - */ - public Field() { - } - - /** - * Construct the Field with the given name. - * - * @param name the name of the Field - */ - public Field(String name) { - setName(name); - } - - /** - * Construct the Field with the given name and label. - * - * @param name the name of the Field - * @param label the label of the Field - */ - public Field(String name, String label) { - setName(name); - setLabel(label); - } - - // Public Attributes ------------------------------------------------------ - - /** - * Set the parent of the Field. - * - * @see org.apache.click.Control#setParent(Object) - * - * @param parent the parent of the Control - * @throws IllegalStateException if {@link #name} is not defined - * @throws IllegalArgumentException if the given parent instance is - * referencing this object: if (parent == this) - */ - @Override - public void setParent(Object parent) { - if (parent == this) { - throw new IllegalArgumentException("Cannot set parent to itself"); - } - // Guard against fields without names, as fields would throw - // exceptions when binding to request value. - if (StringUtils.isBlank(getName())) { - String msg = "Field name not defined: " + getClass().getName(); - throw new IllegalArgumentException(msg); - } - this.parent = parent; - } - - /** - * Return true if the Field is disabled. The Field will also be disabled - * if the parent FieldSet or Form is disabled. - * - * @see #setDisabled(boolean) - * - * @return true if the Field is disabled - */ - public boolean isDisabled() { - Control control = this; - - // Check parents for instances of either FieldSet or Form - while (control.getParent() != null && !(control.getParent() instanceof Page)) { - control = (Control) control.getParent(); - if (control instanceof FieldSet) { - FieldSet fieldSet = (FieldSet) control; - if (fieldSet.isDisabled()) { - return true; - } else { - return disabled; - } - } else if (control instanceof Form) { - Form localForm = (Form) control; - if (localForm.isDisabled()) { - return true; - } else { - return disabled; - } - } - } - return disabled; - } - - /** - * Set the Field disabled flag. Disabled fields are not processed nor - * validated and their action event is not fired. - *

- * Important Note: an HTML form POST does not submit disabled fields - * values. Similarly disabled Click fields do not get processed or validated. - * However, JavaScript is often used to enable fields prior to - * submission. Click is smart enough to recognize when a field was enabled - * this way by checking if the field has an incoming request parameter. - * If a field is disabled but has an incoming request parameter, Click will - * "enable" the field and process it. - *

- * Caveat: Unfortunately the above behavior does not apply to - * {@link Checkbox} and {@link Radio} buttons. An HTML form POST for a - * disabled checkbox/radio is the same as for an unchecked - * checkbox/radio. In neither case is a value submitted to the server and - * Click cannot make the distinction whether the checkbox/radio is disabled - * or unchecked. - * - * @param disabled the Field disabled flag - */ - public void setDisabled(boolean disabled) { - this.disabled = disabled; - } - - /** - * Return the validation error message if the Field is not valid, or null - * if valid. - * - * @return the Field validation error message, or null if valid - */ - public String getError() { - return error; - } - - /** - * Set the Field validation error message. If the error message is not null - * the Field is invalid, otherwise it is valid. - * - * @param error the validation error message - */ - public void setError(String error) { - this.error = error; - } - - /** - * Return true if the field has requested focus. - * - * @return true if the field has requested focus - */ - public boolean getFocus() { - return focus; - } - - /** - * Set the Field request focus flag. - * - * @param focus the request focus flag - */ - public void setFocus(boolean focus) { - this.focus = focus; - } - - /** - * Return the Field focus JavaScript. - * - * @return the Field focus JavaScript - */ - public String getFocusJavaScript() { - HtmlStringBuffer buffer = new HtmlStringBuffer(32); - - buffer.append("setFocus('"); - buffer.append(getId()); - buffer.append("');"); - - return buffer.toString(); - } - - /** - * Return the parent Form containing the Field or null if no form is present - * in the parent hierarchy. - * - * @return the parent Form containing the Field - */ - public Form getForm() { - if (form == null) { - // Find form in parent hierarchy - form = ContainerUtils.findForm(this); - } - return form; - } - - /** - * Set the Field's the parent Form. - * - * @param form Field's parent Form - */ - public void setForm(Form form) { - this.form = form; - } - - /** - * Return the field help text. - *

- * If the help value is null, this method will attempt to find a - * localized help message in the parent messages using the key: - *

- * getName() + ".help" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - *

- * For example given a CustomerPage with the properties file - * CustomerPage.properties: - * - *

-     * name.label=Customer Name
-     * name.help=Full name or Business name 
- * - * The page TextField code: - *
-     * public class CustomerPage extends Page {
-     *     TextField nameField = new TextField("name");
-     *     ..
-     * } 
- * - * Will render the TextField label and title properties as: - *
-     * <td><label>Customer Name</label></td>
-     * <td><input type="text" name="name"/> <span="Full name or Business name"/>/td> 
- * - * How the help text is rendered is depends upon the Field subclass. - * - * @return the help text of the Field - */ - public String getHelp() { - if (help == null) { - help = getMessage(getName() + ".help"); - - if (help != null) { - if (help.indexOf("$context") != -1) { - help = StringUtils.replace(help, "$context", getContext().getRequest().getContextPath()); - - } else if (help.indexOf("${context}") != -1) { - help = StringUtils.replace(help, "${context}", getContext().getRequest().getContextPath()); - } - } - } - return help; - } - - /** - * Set the Field help text. - * - * @param help the help text of the Field - */ - public void setHelp(String help) { - this.help = help; - } - - /** - * Return true if the Field type is hidden (<input type="hidden"/>) or - * false otherwise. By default this method returns false. - * - * @return false - */ - public boolean isHidden() { - return false; - } - - /** - * Return the Form and Field id appended:   "form-field" - *

- * Use the field the "id" attribute value if defined, or the name otherwise. - * - * @see org.apache.click.Control#getId() - * - * @return HTML element identifier attribute "id" value - */ - @Override - public String getId() { - if (hasAttributes() && getAttributes().containsKey("id")) { - return getAttribute("id"); - - } else { - String fieldName = getName(); - - if (fieldName == null) { - // If fieldName is null, exit early - return null; - } - - Form parentForm = getForm(); - String formId = (parentForm != null) ? parentForm.getId() + "_" : ""; - String id = formId + fieldName; - - if (id.indexOf('/') != -1) { - id = id.replace('/', '_'); - } - if (id.indexOf(' ') != -1) { - id = id.replace(' ', '_'); - } - if (id.indexOf('<') != -1) { - id = id.replace('<', '_'); - } - if (id.indexOf('>') != -1) { - id = id.replace('>', '_'); - } - if (id.indexOf('.') != -1) { - id = id.replace('.', '_'); - } - - return id; - } - } - - /** - * Return the field display label. - *

- * If the label value is null, this method will attempt to find a - * localized label message in the parent messages using the key: - *

- * getName() + ".label" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - * If a value is still not found, the Field name will be converted - * into a label using the method: {@link ClickUtils#toLabel(String)} - *

- * For example given a CustomerPage with the properties file - * CustomerPage.properties: - * - *

-     * name.label=Customer Name
-     * name.title=Full name or Business name 
- * - * The page TextField code: - *
-     * public class CustomerPage extends Page {
-     *     TextField nameField = new TextField("name");
-     *     ..
-     * } 
- * - * Will render the TextField label and title properties as: - *
-     * <td><label>Customer Name</label></td>
-     * <td><input type="text" name="name" title="Full name or Business name"/></td> 
- * - * When a label value is not set, or defined in any properties files, then - * its value will be created from the Fields name. - *

- * For example given the TextField code: - * - *

-     * TextField nameField = new TextField("faxNumber");  
- * - * Will render the TextField label as: - *
-     * <td><label>Fax Number</label></td>
-     * <td><input type="text" name="faxNumber"/></td> 
- * - * @return the display label of the Field - */ - public String getLabel() { - if (label == null) { - label = getMessage(getName() + ".label"); - } - if (label == null) { - label = ClickUtils.toLabel(getName()); - } - return label; - } - - /** - * Set the Field display caption. - * - * @param label the display label of the Field - */ - public void setLabel(String label) { - this.label = label; - } - - /** - * Return the field label "style" attribute value. - * - * @see #setLabelStyle(java.lang.String) - * - * @return the field label "style" attribute value - */ - public String getLabelStyle() { - return labelStyle; - } - - /** - * Set the field label "style" attribute value. - *

- * Please note: the label is rendered by the containing Form - * or container, not the field itself. It is up to the parent Form - * (or container) on how to apply the label style. - *

- *

-     * nameField.setLabelStyle("color: green; font-weight: bold");
- * - * @param value the field label "style" attribute value - */ - public void setLabelStyle(String value) { - this.labelStyle = value; - } - - /** - * Return the field label "class" attribute value. - * - * @see #setLabelStyleClass(java.lang.String) - * - * @return the field label "class" attribute value - */ - public String getLabelStyleClass() { - return labelStyleClass; - } - - /** - * Set the field label "class" attribute value. - *

- * Please note: the label is rendered by the containing Form - * or container, not the field itself. It is up to the parent Form - * (or container) on how to apply the label style class. - * - * @param value the field label "class" attribute value - */ - public void setLabelStyleClass(String value) { - this.labelStyleClass = value; - } - - /** - * Return the field's parent "style" attribute hint. - * - * @see #setParentStyleHint(java.lang.String) - * - * @return the field's parent "style" attribute hint - */ - public String getParentStyleHint() { - return parentStyleHint; - } - - /** - * Set the field's parent "style" attribute hint. - *

- *

-     * nameField.setParentStyleHint("margin-bottom; 10px");
- * - * Please note:The field's parent style provides a hint to Forms - * (or other containers) what style to render, but it is up to the - * Form (or container) implementation how the style will be applied. - * For example, Form will render the parent style on the table cells - * containing the field and label. - * - * @param styleHint the field's parent "style" attribute hint - */ - public void setParentStyleHint(String styleHint) { - this.parentStyleHint = styleHint; - } - - /** - * Return the field's parent "class" attribute hint. - * - * @see #setParentStyleClassHint(java.lang.String) - * - * @return the field's parent "class" attribute hint - */ - public String getParentStyleClassHint() { - return parentStyleClassHint; - } - - /** - * Set the field's parent "class" attribute hint. - *

- * Please note:The parent style class provides a hint to Forms - * (or other containers) which CSS class to render, but it is up to the - * Form (or container) implementation how the style class will be applied. - * For example, Form will render the parent style class on the table cells - * containing the field and label. - * - * @param styleClassHint the field parent "class" attribute hint - */ - public void setParentStyleClassHint(String styleClassHint) { - this.parentStyleClassHint = styleClassHint; - } - - /** - * The callback listener will only be called during processing if the Field - * value is valid. If the field has validation errors the listener will not - * be called. - * - * @see org.apache.click.Control#getName() - * - * @param listener the listener object with the named method to invoke - * @param method the name of the method to invoke - */ - @Override - public void setListener(Object listener, String method) { - super.setListener(listener, method); - } - - /** - * Return true if the Field is a readonly. The Field will also be readonly - * if the parent FieldSet or Form is readonly. - * - * @return true if the Field is a readonly - */ - public boolean isReadonly() { - Control control = this; - - // Check parents for instances of either FieldSet or Form - while (control.getParent() != null && !(control.getParent() instanceof Page)) { - control = (Control) control.getParent(); - if (control instanceof FieldSet) { - FieldSet fieldSet = (FieldSet) control; - if (fieldSet.isReadonly()) { - return true; - } else { - return readonly; - } - } else if (control instanceof Form) { - Form localForm = (Form) control; - if (localForm.isReadonly()) { - return true; - } else { - return readonly; - } - } - } - return readonly; - } - - /** - * Set the Field readonly flag. - * - * @param readonly the Field readonly flag - */ - public void setReadonly(boolean readonly) { - this.readonly = readonly; - } - - /** - * Return true if the Field's value is required. - * - * @return true if the Field's value is required - */ - public boolean isRequired() { - return required; - } - - /** - * Set the Field required status. - * - * @param required set the Field required status - */ - public void setRequired(boolean required) { - this.required = required; - } - - /** - * Return the field "tabindex" attribute value. - * - * @return the field "tabindex" attribute value - */ - public int getTabIndex() { - return tabindex; - } - - /** - * Set the field "tabindex" attribute value. - * - * @param tabindex the field "tabindex" attribute value - */ - public void setTabIndex(int tabindex) { - this.tabindex = tabindex; - } - - /** - * Return the field CSS "text-align" style, or null if not defined. - * - * @return the field CSS "text-align" style, or null if not defined. - */ - public String getTextAlign() { - return getStyle("text-align"); - } - - /** - * Set the field CSS horizontal "text-align" style. - * - * @param align the CSS "text-align" value: ["left", "right", "center"] - */ - public void setTextAlign(String align) { - setStyle("text-align", align); - } - - /** - * Return the 'title' attribute, or null if not defined. The title - * attribute acts like tooltip message over the Field. - *

- * If the title value is null, this method will attempt to find a - * localized title message in the parent messages using the key: - *

- * getName() + ".title" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. If still - * not found the title will be left as null and will not be rendered. - *

- * For example given a CustomerPage with the properties file - * CustomerPage.properties: - * - *

-     * name.label=Customer Name
-     * name.title=Full name or Business name 
- * - * The page TextField code: - *
-     * public class CustomerPage extends Page {
-     *     TextField nameField = new TextField("name");
-     *     ..
-     * } 
- * - * Will render the TextField label and title properties as: - *
-     * <td><label>Customer Name</label></td>
-     * <td><input type="text" name="name" title="Full name or Business name"/></td> 
- * - * @return the 'title' attribute tooltip message - */ - public String getTitle() { - if (title == null) { - title = getMessage(getName() + ".title"); - } - return title; - } - - /** - * Set the 'title' attribute tooltip message. - * - * @param value the 'title' attribute tooltip message - */ - public void setTitle(String value) { - title = value; - } - - /** - * Return true if the Field request value should be trimmed, false otherwise. - * The default value is "true". - * - * @return true if the Field request value should be trimmed, false otherwise - */ - public boolean isTrim() { - return trim; - } - - /** - * Set the trim flag to true if the Field request value should be trimmed, - * false otherwise. - * - * @param trim true if the Field request value should be trimmed, false - * otherwise - */ - public void setTrim(boolean trim) { - this.trim = trim; - } - - /** - * Return true if the Field should validate itself when being processed. - *

- * If the validate attribute for the Field is not explicitly set, this - * method will return the validation status of its parent Form, see - * {@link Form#getValidate()}. If the Field validate attribute is not set - * and the parent Form is not set this method will return true. - *

- * This method is called by the {@link #onProcess()} method to determine - * whether the the Field {@link #validate()} method should be invoked. - * - * @return true if the Field should validate itself when being processed. - */ - public boolean getValidate() { - if (validate != null) { - return validate; - - } else if (getForm() != null) { - return getForm().getValidate(); - - } else { - return true; - } - } - - /** - * Set the validate Field value when being processed flag. - * - * @param validate the field value when processed - */ - public void setValidate(boolean validate) { - this.validate = validate; - } - - /** - * Return the field JavaScript client side validation function. - *

- * The function name must follow the format validate_[id], where - * the id is the DOM element id of the fields focusable HTML element, to - * ensure the function has a unique name. - * - * @return the field JavaScript client side validation function - */ - public String getValidationJavaScript() { - return null; - } - - /** - * Return true if the Field is valid after being processed, or false - * otherwise. If the Field has no error message after - * {@link org.apache.click.Control#onProcess()} has been invoked it is considered to be - * valid. - * - * @return true if the Field is valid after being processed - */ - public boolean isValid() { - return (error == null); - } - - /** - * Return the Field value. - * - * @return the Field value - */ - public String getValue() { - return (value != null) ? value : ""; - } - - /** - * Set the Field value. - * - * @param value the Field value - */ - public void setValue(String value) { - this.value = value; - } - - /** - * Return the object representation of the Field value. This method will - * return a string value, or null if the string value is null or is zero - * length. - *

- * Specialized object field subclasses should override this method to - * return a non string object. For examples a DoubleField would - * return a Double value instead. - * - * @return the object representation of the Field value - */ - public Object getValueObject() { - if (value == null || value.length() == 0) { - return null; - } else { - return value; - } - } - - /** - * Set the value of the field using the given object. - * - * @param object the object value to set - */ - public void setValueObject(Object object) { - if (object != null) { - value = object.toString(); - } - } - - /** - * Return the width CSS "width" style, or null if not defined. - * - * @return the CSS "width" style attribute, or null if not defined - */ - public String getWidth() { - return getStyle("width"); - } - - /** - * Set the the CSS "width" style attribute. - * - * @param value the CSS "width" style attribute - */ - public void setWidth(String value) { - setStyle("width", value); - } - - // Public Methods --------------------------------------------------------- - - /** - * This method binds the submitted request value to the Field's value. - *

- * Please note: while it is possible to explicitly bind the field - * value by invoking this method directly, it is recommended to use the - * "bind" utility methods in {@link org.apache.click.util.ClickUtils} - * instead. See {@link org.apache.click.util.ClickUtils#bind(org.apache.click.control.Field)} - * for more details. - */ - public void bindRequestValue() { - setValue(getRequestValue()); - } - - /** - * Return the Field state. The following state is returned: - *

    - *
  • {@link #getValue() field value}
  • - *
- * - * @return the Field state - */ - public Object getState() { - String state = getValue(); - if (StringUtils.isEmpty(state)) { - return null; - } - return state; - } - - /** - * Set the Field state. - * - * @param state the Field state to set - */ - public void setState(Object state) { - if (state == null) { - return; - } - - String fieldState = (String) state; - setValue(fieldState); - } - - /** - * This method processes the page request returning true to continue - * processing or false otherwise. The Field onProcess() method is - * typically invoked by the Form onProcess() method when - * processing POST request. - *

- * This method will bind the Field request parameter value to the field, - * validate the submission and invoke its callback listener if defined. - *

- * Below is a typical onProcess implementation: - * - *

-     * public boolean onProcess() {
-     *     bindRequestValue();
-     *
-     *     if (getValidate()) {
-     *         validate();
-     *     }
-     *
-     *     registerActionEvent();
-     *
-     *     return true
-     * } 
- * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - Context context = getContext(); - - if (context.hasRequestParameter(getName())) { - // Only process field if it participated in the incoming request - setDisabled(false); - - bindRequestValue(); - - if (getValidate()) { - validate(); - } - - dispatchActionEvent(); - } - - return true; - } - - /** - * Remove the Field state from the session for the given request context. - * - * @see #saveState(Context) - * @see #restoreState(Context) - * - * @param context the request context - */ - public void removeState(Context context) { - ClickUtils.removeState(this, getName(), context); - } - - /** - * Restore the Field state from the session for the given request context. - *

- * This method delegates to {@link #setState(java.lang.Object)} to set the - * field restored state. - * - * @see #saveState(Context) - * @see #removeState(Context) - * - * @param context the request context - */ - public void restoreState(Context context) { - ClickUtils.restoreState(this, getName(), context); - } - - /** - * Save the Field state to the session for the given request context. - *

- * * This method delegates to {@link #getState()} to retrieve the field state - * to save. - * - * @see #restoreState(Context) - * @see #removeState(Context) - * - * @param context the request context - */ - public void saveState(Context context) { - ClickUtils.saveState(this, getName(), context); - } - - /** - * The validate method is invoked by onProcess() to validate - * the request submission. Field subclasses should override this method - * to implement request validation logic. - *

- * If the field determines that the submission is invalid it should set the - * {@link #error} property with the error message. - */ - public void validate() { - } - - // Protected Methods ------------------------------------------------------ - - /** - * Return a normalized label for display in error messages. - *

- * The error label is a normalized version of {@link #getLabel()}. - * - * @return a normalized label for error message display - */ - protected String getErrorLabel() { - String localLabel = getLabel().trim(); - localLabel = (localLabel.endsWith(":")) - ? localLabel.substring(0, localLabel.length() - 1) : localLabel; - return localLabel; - } - - /** - * Set the error with the a label formatted message specified by the given - * message bundle key. The message will be formatted with the field label - * using {@link #getErrorLabel()}. - *

- * setErrorMessage will attempt to find a localized error message as - * described here, using the following - * lookup strategy: - *

    - *
  • - * an error message is looked up for a specific Field instance by - * prefixing the key with the Field's name: - *
    - * getMessage(getName() + "." + key); - *
    - *
  • - *
  • - * if no message is found for a specific Field instance, an error - * message is looked up for the Field class based on the key: - *
    - * getMessage(key); - *
    - *
  • - *
- * - * @param key the key of the localized message bundle string - */ - protected void setErrorMessage(String key) { - String errorLabel = getErrorLabel(); - String msg = getMessage(getName() + "." + key, errorLabel); - if (msg == null) { - msg = getMessage(key, errorLabel); - } - setError(msg); - } - - /** - * Set the error with the a label and value formatted message specified by - * the given message bundle key. The message will be formatted with the - * field label {0} using {@link #getErrorLabel()} and the given value {1}. - *

- * Also see {@link #setErrorMessage(java.lang.String)} on how to - * specify error messages for specific Field instances. - * - * @param key the key of the localized message bundle string - * @param value the value to format in the message - */ - protected void setErrorMessage(String key, T value) { - String msg = getMessage(getName() + "." + key, getErrorLabel(), value); - if (msg == null) { - msg = getMessage(key, getErrorLabel(), value); - } - setError(msg); - } - - /** - * Return the field's value from the request. - * - * @return the field's value from the request - */ - protected String getRequestValue() { - String requestValue = getContext().getRequestParameter(getName()); - if (requestValue != null) { - if (isTrim()) { - return requestValue.trim(); - } else { - return requestValue; - } - } else { - return ""; - } - } - - /** - * Render the Field tag and common attributes including {@link #getName() name}, - * {@link #getId() id}, class and style. - * - * @see org.openidentityplatform.openam.click.control.AbstractControl#renderTagBegin(java.lang.String, HtmlStringBuffer) - * - * @param tagName the name of the tag to render - * @param buffer the buffer to append the output to - */ - @Override - protected void renderTagBegin(String tagName, HtmlStringBuffer buffer) { - if (tagName == null) { - throw new IllegalStateException("Tag cannot be null"); - } - - buffer.elementStart(tagName); - - String controlName = getName(); - if (controlName != null) { - buffer.appendAttribute("name", controlName); - } - - String id = getId(); - if (id != null) { - buffer.appendAttribute("id", id); - } - - appendAttributes(buffer); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/FileField.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/FileField.java deleted file mode 100644 index 91c6b007af..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/FileField.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.lang.StringUtils; - -import java.text.MessageFormat; - -/** - * Provides a File Field control:   <input type='file'>. - * - * - * - * - * - * - *
File Field
- * - * The FileField control uses the Jakarta Commons - * FileUpload - * library to provide file processing functionality. - *

- * You can control the {@link org.apache.click.service.CommonsFileUploadService#sizeMax maximum request size} - * and {@link org.apache.click.service.CommonsFileUploadService#fileSizeMax maximum file size} - * by configuring {@link org.apache.click.service.CommonsFileUploadService}. - *

- * Note Browsers enforce the JavaScript value property as readonly - * to prevent script based stealing of users files. - *

- * You can make the file field invisible by setting the CSS display attribute, for - * example: - * - *

- * <form method="POST" enctype="multipart/form-data">
- *    <input type="file" name="myfile" style="display:none" onchange="fileName=this.value">
- *    <input type="button" value="open file" onclick="myfile.click()">
- *    <input type="button" value="show value" onclick="alert(fileName)">
- * </form> 
- * - *

- * Please also see the references: - *

- */ -public class FileField extends Field { - - // -------------------------------------------------------------- Constants - - private static final long serialVersionUID = 1L; - - /** - * The field validation JavaScript function template. - * The function template arguments are:
    - *
  • 0 - is the field id
  • - *
  • 1 - is the Field required status
  • - *
  • 2 - is the localized error message
  • - *
- */ - protected final static String VALIDATE_FILEFIELD_FUNCTION = - "function validate_{0}() '{'\n" - + " var msg = validateFileField(''{0}'',{1}, [''{2}'']);\n" - + " if (msg) '{'\n" - + " return msg + ''|{0}'';\n" - + " '}' else '{'\n" - + " return null;\n" - + " '}'\n" - + "'}'\n"; - - // ----------------------------------------------------- Instance Variables - - /** The text field size attribute. The default size is 20. */ - protected int size = 20; - - /** - * The - * DefaultFileItem - * after processing a file upload request. - */ - protected FileItem fileItem; - - // ----------------------------------------------------------- Constructors - - /** - * Construct the FileField with the given name. - * - * @param name the name of the field - */ - public FileField(String name) { - super(name); - } - - /** - * Construct the FileField with the given name and required status. - * - * @param name the name of the field - * @param required the field required status - */ - public FileField(String name, boolean required) { - super(name); - setRequired(required); - } - - /** - * Construct the FileField with the given name and label. - * - * @param name the name of the field - * @param label the label of the field - */ - public FileField(String name, String label) { - super(name, label); - } - - - /** - * Construct the FileField with the given name, label and required status. - * - * @param name the name of the field - * @param label the label of the field - * @param required the required status - */ - public FileField(String name, String label, boolean required) { - super(name, label); - setRequired(required); - } - - /** - * Construct the FileField with the given name, label and size. - * - * @param name the name of the field - * @param label the label of the field - * @param size the size of the field - */ - public FileField(String name, String label, int size) { - this(name, label); - setSize(size); - } - - /** - * Create an FileField with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public FileField() { - super(); - } - - // ------------------------------------------------------ Public Attributes - - /** - * Return the FileField's html tag: input. - * - * @see org.apache.click.control.AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "input"; - } - - /** - * Return the FileItem - * after processing the request, or null otherwise. - * - * @return the FileItem after processing a request - */ - public FileItem getFileItem() { - return fileItem; - } - - /** - * Return the field size. - * - * @return the field size - */ - public int getSize() { - return size; - } - - /** - * Set the field size. - * - * @param size the field size - */ - public void setSize(int size) { - this.size = size; - } - - /** - * Return the input type: 'file'. - * - * @return the input type: 'file' - */ - public String getType() { - return "file"; - } - - /** - * Return the FileField JavaScript client side validation function. - * - * @return the field JavaScript client side validation function - */ - @Override - public String getValidationJavaScript() { - if (isRequired()) { - Object[] args = new Object[3]; - args[0] = getId(); - args[1] = String.valueOf(isRequired()); - args[2] = getMessage("file-required-error", getErrorLabel()); - - return MessageFormat.format(VALIDATE_FILEFIELD_FUNCTION, args); - - } else { - return null; - } - } - - // --------------------------------------------------------- Public Methods - - /** - * Set the {@link #fileItem} property from the multi-part form data - * submission. - */ - @Override - public void bindRequestValue() { - fileItem = getContext().getFileItem(getName()); - } - - /** - * @see AbstractControl#getControlSizeEst() - * - * @return the estimated rendered control size in characters - */ - @Override - public int getControlSizeEst() { - return 96; - } - - /** - * Overrides onProcess to use {@link Context#getFileItem(String)}. - * - * @see Field#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - Context context = getContext(); - - if (context.getFileItemMap().containsKey(getName())) { - // Only process field if it participated in the incoming request - setDisabled(false); - - bindRequestValue(); - - if (getValidate()) { - validate(); - } - - dispatchActionEvent(); - } - - return true; - } - - /** - * Render the HTML representation of the FileField. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - buffer.elementStart(getTag()); - - buffer.appendAttribute("type", getType()); - buffer.appendAttribute("name", getName()); - buffer.appendAttribute("id", getId()); - buffer.appendAttributeEscaped("value", getValue()); - buffer.appendAttribute("size", getSize()); - buffer.appendAttribute("title", getTitle()); - if (isValid()) { - removeStyleClass("error"); - } else { - addStyleClass("error"); - } - if (getTabIndex() > 0) { - buffer.appendAttribute("tabindex", getTabIndex()); - } - - appendAttributes(buffer); - - if (isDisabled()) { - buffer.appendAttributeDisabled(); - } - if (isReadonly()) { - buffer.appendAttributeReadonly(); - } - - buffer.elementEnd(); - - if (getHelp() != null) { - buffer.append(getHelp()); - } - } - - /** - * Validate the FileField request submission. - *

- * A field error message is displayed if a validation error occurs. - * These messages are defined in the resource bundle: - *

- *
    - *
  • /click-control.properties - *
      - *
    • file-required-error
    • - *
    - *
  • - *
- *
- */ - @Override - public void validate() { - setError(null); - - if (isRequired()) { - FileItem localFileItem = getFileItem(); - if (localFileItem == null || StringUtils.isBlank(localFileItem.getName())) { - setErrorMessage("file-required-error"); - } - } - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Form.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Form.java deleted file mode 100644 index c53a15c637..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Form.java +++ /dev/null @@ -1,3175 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.Page; -import org.apache.click.Stateful; -import org.apache.click.control.FieldSet; -import org.apache.click.control.Submit; -import org.openidentityplatform.openam.click.element.CssImport; -import org.openidentityplatform.openam.click.element.Element; -import org.openidentityplatform.openam.click.element.JsImport; -import org.apache.click.service.FileUploadService; -import org.openidentityplatform.openam.click.service.LogService; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.ContainerUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException; -import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException; -import org.apache.commons.fileupload.FileUploadException; -import org.apache.commons.lang.StringUtils; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; - -/** - * Provides a Form control:   <form method='post'>. - * - * - * - * - * - *
- * - * - * - * - * - * - * - * - * - * - *
*
*
- * - * - *
- *   - *
- * - *
- * - * When a Form is processed it will process its {@link Field} controls - * in the order they were added to the form, and then it will process the - * {@link Button} controls in the added order. Once all the Fields have been - * processed the form will invoke its action listener if defined. - * - *

Form Example

- * - * The example below illustrates a Form being used in a login Page. - * - *
- * public class Login extends Page {
- *
- *     public Form form = new Form();
- *
- *     public Login() {
- *         form.add(new TextField("username", true));
- *         form.add(new PasswordField("password", true));
- *         form.add(new Submit("ok", "  OK  ", this, "onOkClick"));
- *         form.add(new Submit("cancel", this, "onCancelClick"));
- *     }
- *
- *     public boolean onOkClick() {
- *         if (form.isValid()) {
- *             User user = new User();
- *             form.copyTo(user);
- *
- *             if (getUserService().isAuthenticatedUser(user)) {
- *                 getContext().setSessionAttribute("user", user);
- *                 setRedirect(HomePage.class);
- *             } else {
- *                 form.setError(getMessage("authentication-error"));
- *             }
- *         }
- *         return true;
- *     }
- *
- *     public boolean onCancelClick() {
- *         setRedirect(WelcomePage.class);
- *         return false;
- *     }
- * } 
- * - * The forms corresponding template code is below. Note the form automatically - * renders itself when Velocity invokes its {@link #toString()} method. - * - *
- * $form 
- * - * If a Form has been posted and processed, if it has an {@link #error} defined or - * any of its Fields have validation errors they will be automatically - * rendered, and the {@link #isValid()} method will return false. - * - * - *

Data Binding

- * - * To bind value objects to a forms fields use the copy methods: - *
    - *
  • value object   ->   form fields       - * {@link #copyFrom(Object)}
  • - *
  • form fields   ->   value object       - * {@link #copyTo(Object)}
  • - *
- * To debug the data binding being performed, use the Click application mode to - * "debug" or use the debug copy methods. - *

- * Binding of nested data objects is supported using the - * OGNL library. To use - * nested objects in your form, simply specify the object path as the Field - * name. Note in the object path you exclude the root object, so the path - * customer.address.state is specified as address.state. - *

- * For example: - * - *

- * // The customer.address.state field
- * TextField stateField = new TextField("address.state");
- * form.add(stateField);
- * ..
- *
- * // Loads the customer address state into the form stateField
- * Customer customer = getCustomer();
- * form.copyFrom(customer);
- * ..
- *
- * // Copies form stateField value into the customer address state
- * Customer customer = new Customer();
- * form.copyTo(customer); 
- * - * When populating an object from a form post Click will automatically create - * any null nested objects so their properties can be set. To do this Click - * uses the no-args constructor of the nested objects class. - *

- * {@link #copyTo(Object)} and {@link #copyFrom(Object)} also supports - * java.util.Map as an argument. Examples of using - * java.util.Map are shown in the respective method descriptions. - * - * - *

Form Validation

- * - * The Form control supports automatic field validation. By default when a POST - * request is made the form will validate the field values. To disable - * automatic validation set {@link #setValidate(boolean)} to false. - *

- * Form also provides a {@link #validate()} method where subclasses can provide - * custom cross-field validation. - *

- * File Upload Validation - *

- * The Form's {@link #validateFileUpload()} provides validation for multipart - * requests (multipart requests are used for uploading files from the browser). - * The {@link #validateFileUpload()} method checks that files being uploaded do not exceed the - * {@link org.apache.click.service.CommonsFileUploadService#sizeMax maximum request size} - * or the {@link org.apache.click.service.CommonsFileUploadService#fileSizeMax maximum file size}. - *

- * Note: if the maximum request size or maximum file size - * is exceeded, the request is deemed invalid ({@link #hasPostError hasPostError} - * will return true), and no further processing is performed on the form or fields. - * Instead the form will display the appropriate error message for the invalid request. - * See {@link #validateFileUpload()} for details of the error message properties. - *

- * JavaScript Validation - *

- * The Form control also supports client side JavaScript validation. By default - * JavaScript validation is not enabled. To enable JavaScript validation set - * {@link #setJavaScriptValidation(boolean)} to true. For example: - * - *

- * Form form = new Form("form");
- * form.setJavaScriptValidation(true);
- *
- * // Add form fields
- * ..
- *
- * form.add(new Submit("ok", " OK ", this, "onOkClicked");
- *
- * Submit cancel = new Submit("cancel", "Cancel", this, "onCancelClicked");
- * cancel.setCancelJavaScriptValidation(true);
- *
- * addControl(form); 
- * - * Please note in that is this example the cancel submit button has - * {@link Submit#setCancelJavaScriptValidation(boolean)} set to true. This - * prevents JavaScript form validation being performed when the cancel button is - * clicked. - * - * - *

CSS and JavaScript resources

- * - * The Form control makes use of the following resources (which Click automatically - * deploys to the application directory, /click): - * - *
    - *
  • click/control.css
  • - *
  • click/control.js
  • - *
- * - * To import these files and any form control imports simply reference - * the variables $headElements and - * $jsElements in the page template. For example: - * - *
- * <html>
- * <head>
- * $headElements
- * </head>
- * <body>
- *
- * $form
- *
- * $jsElements
- * </body>
- * </html> 
- * - * - *

Form Layout

- * The Form control supports rendering using automatic and manual layout - * techniques. - * - * - *

Auto Layout

- * - * If you include a form variable in your template the form will be - * automatically laid out and rendered. Auto layout, form and field rendering - * options include: - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
{@link #buttonAlign} button alignment:   ["left", "center", "right"]
{@link #buttonStyle} button <td> "style" attribute value
{@link #columns} number of form table columns, the default value number is 1
{@link #errorsAlign} validation error messages alignment:   ["left", "center", "right"]
{@link #errorsPosition} validation error messages position:   ["top", "middle", "bottom"]
{@link #errorsStyle} errors <td> "style" attribute value
{@link #fieldStyle} field <td> "style" attribute value
{@link #labelAlign} field label alignment:   ["left", "center", "right"]
{@link #labelsPosition} label position relative to field:   ["left", "top"]
{@link #labelStyle} label <td> "style" attribute value
click/control.css control CSS styles, automatically deployed to the click web directory
/click-control.properties form and field messages and HTML, located under classpath
- * - * - *

Manual Layout

- * - * You can also manually layout the Form in the page template specifying - * the fields using the named field notation: - * - *
- * $form.{@link #getFields fields}.usernameField 
- * - * Whenever including your own Form markup in a page template or Velocity macro - * always specify: - *
    - *
  • method - * - the form submission method ["post" | "get"]
  • - *
  • name - * - the name of your form, important when using JavaScript
  • - *
  • action - * - directs the Page where the form should be submitted to
  • - *
  • form_name - * - include a hidden field which specifies the {@link #name} of the Form
  • - *
- * The hidden field is used by Click to determine which form was posted on - * a page which may contain multiple forms. - *

- * Alternatively you can use the Form {@link #startTag()} and {@link #endTag()} - * methods to render this information. - *

- * An example of a manually laid out Login form is provided below: - * - *

- * $form.startTag()
- *
- *   <table style="margin: 1em;">
- *
- *     #if ($form.error)
- *     <tr>
- *       <td colspan="2" style="color: red;"> $form.error </td>
- *     </tr>
- *     #end
- *     #if ($form.fields.usernameField.error)
- *     <tr>
- *       <td colspan="2" style="color: red;"> $form.fields.usernameField.error </td>
- *     </tr>
- *     #end
- *     #if ($form.fields.passwordField.error)
- *     <tr>
- *       <td colspan="2" style="color: red;"> $form.fields.passwordField.error </td>
- *     </tr>
- *     #end
- *
- *     <tr>
- *       <td> Username: </td>
- *       <td> $form.fields.usernameField </td>
- *     </tr>
- *     <tr>
- *       <td> Password: </td>
- *       <td> $form.fields.passwordField </td>
- *     </tr>
- *
- *     <tr>
- *       <td>
- *         $form.fields.okSubmit
- *         $form.fields.cancelSubmit
- *       </td>
- *     </tr>
- *
- *   </table>
- *
- * $form.endTag() 
- * - * As you can see in this example most of the code and markup is generic and - * could be reused. This is where Velocity Macros come in. - * - * - *

Velocity Macros

- * - * Velocity Macros - * (velocimacros) - * are a great way to encapsulate customized forms. - *

- * To create a generic form layout you can use the Form {@link #getFieldList()} and - * {@link #getButtonList()} properties within a Velocity macro. If you want to - * access all Form Controls from within a Velocity template or macro use - * {@link #getControls()}. - *

- * The example below provides a generic writeForm() - * macro which you could use through out an application. This Velocity macro code - * would be contained in a macro file, e.g. macro.vm. - * - *

 #* Custom Form Macro Code *#
- * #macro( writeForm[$form] )
- *
- * $form.startTag()
- *
- * <table width="100%">
- *
- * #if ($form.error)
- *   <tr>
- *     <td colspan="2" style="color: red;"> $form.error </td>
- *   </tr>
- * #end
- *
- * #foreach ($field in $form.fieldList)
- *   #if (!$field.hidden)
- *     #if (!$field.valid)
- *     <tr>
- *       <td colspan="2"> $field.error </td>
- *     </tr>
- *     #end
- *
- *   <tr>
- *     <td> $field.label: </td><td> $field </td>
- *   </tr>
- *   #end
- * #end
- *
- *  <tr>
- *    <td colspan="2">
- *    #foreach ($button in $form.buttonList)
- *      $button &nbsp;
- *    #end
- *    </td>
- *  </tr>
- *
- * </table>
- *
- * $form.endTag()
- *
- * #end 
- * - * You would then call this macro in your Page template passing it your - * form object: - * - *
 #writeForm($form) 
- * - * At render time Velocity will execute the macro using the given form and render - * the results to the response output stream. - * - *

Configuring Macros

- * - * To configure your application to use your macros you can: - *
    - *
  • - * Put your macros if a file called macro.vm - * in your applications root directory. - *
  • - *
  • - * Put your macros in the auto deployed - * click/VM_global_macro.vm file. - *
  • - *
  • - * Create a custom named macro file and reference it in a - * WEB-INF/velocity.properties - * file under the property named - * velocimacro.library. - *
  • - *
- * - * - *

Preventing Accidental Form Posts

- * - * Users may accidentally make multiple form submissions by refreshing a page - * or by pressing the back button. - *

- * To prevent multiple form posts from page refreshes use the Post - * Redirect pattern. With this pattern once the user has posted a form you - * redirect to another page. If the user then presses the refresh button, they - * will making a GET request on the current page. Please see the - * Redirect After Post - * article for more information on this topic. - *

- * To prevent multiple form posts from use of the browser back button use one - * of the Form {@link #onSubmitCheck(Page, String)} methods. For example: - * - *

- * public class Purchase extends Page {
- *     ..
- *
- *     public boolean onSecurityCheck() {
- *         return form.onSubmitCheck(this, "/invalid-submit.html");
- *     }
- * } 
- * - * The form submit check methods store a special token in the users session - * and in a hidden field in the form to ensure a form post isn't replayed. - * - * - *

Dynamic Forms and not validating a request

- * - * A common use case for web applications is to create Form fields dynamically - * based upon user selection. For example if a checkbox is ticked another Field - * is added to the Form. A simple way to achieve this is using JavaScript - * to submit the Form when the Field is changed or clicked. - *

- * When submitting a Form using JavaScript, it is often desirable to not - * validate the fields since the user is still filling out the form. - * To cater for this use case, Form provides the {@link #setValidate(boolean)} - * to switch off form and field validation. For example: - * - *

- * public void onInit() {
- *     checkbox.setAttribute("onclick", "form.submit()");
- *
- *     // Since onInit occurs before the onProcess event,
- *     // we have to explicitly bind the submit button in the onInit event if we
- *     // want to check if it was clicked.
- *     // If the submit button wasn't clicked it means the Form was submitted
- *     // using JavaScript and we don't want to validate yet
- *     ClickUtils.bind(submit);
- *
- *     // If submit was not clicked, don't validate
- *     if(form.isFormSubmission() && !submit.isClicked()) {
- *         form.setValidate(false);
- *     }
- * } 
- * - *

 

- * See also the W3C HTML reference: - * FORM - * - * @see org.openidentityplatform.openam.click.control.Field - * @see Submit - */ -public class Form extends AbstractContainer implements Stateful { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - /** The align left, form layout constant:   "left". */ - public static final String ALIGN_LEFT = "left"; - - /** The align center, form layout constant:   "center". */ - public static final String ALIGN_CENTER = "center"; - - /** The align right, form layout constant:   "right". */ - public static final String ALIGN_RIGHT = "right"; - - /** - * The position top, errors and labels form layout constant:   - * "top". - */ - public static final String POSITION_TOP = "top"; - - /** - * The position middle, errors in middle form layout constant:   - * "middle". - */ - public static final String POSITION_MIDDLE = "middle"; - - /** - * The position bottom, errors on bottom form layout constant:   - * "top". - */ - public static final String POSITION_BOTTOM = "bottom"; - - /** - * The position left, labels of left form layout constant:   - * "left". - */ - public static final String POSITION_LEFT = "left"; - - /** - * The form name parameter for multiple forms:   "form_name". - */ - public static final String FORM_NAME = "form_name"; - - /** The HTTP content type header for multipart forms. */ - public static final String MULTIPART_FORM_DATA = "multipart/form-data"; - - /** - * The submit check reserved request parameter prefix:   - * SUBMIT_CHECK_. - */ - public static final String SUBMIT_CHECK = "SUBMIT_CHECK_"; - - /** The Form set field focus JavaScript. */ - protected static final String FOCUS_JAVASCRIPT = - "\n"; - - // Instance Variables ----------------------------------------------------- - - /** The form action URL. */ - protected String actionURL; - - /** The form disabled value. */ - protected boolean disabled; - - /** The form "enctype" attribute. */ - protected String enctype; - - /** The form level error message. */ - protected String error; - - /** The ordered list of fields, excluding buttons. */ - protected final List fieldList = new ArrayList<>(); - - /** - * The form method ["post, "get"], default value:   - * post. - */ - protected String method = "post"; - - /** The form is readonly flag. */ - protected boolean readonly; - - /** The form validate fields when processing flag. */ - protected boolean validate = true; - - /** The button align, default value is "left". */ - protected String buttonAlign = ALIGN_LEFT; - - /** The ordered list of button values. */ - protected final List

\n"); - - renderControls(buffer, this, getControls(), getFieldWidths(), getColumns()); - buffer.append("
\n"); - - int column = 1; - boolean openTableRow = false; - - for (Control control : controls) { - - // Buttons are rendered separately - if (control instanceof Button) { - continue; - } - - if (!isHidden(control)) { - - // Control width - Integer width = fieldWidths.get(control.getName()); - - if (column == 1) { - buffer.append("\n"); - openTableRow = true; - } - - if (control instanceof FieldSet) { - FieldSet fieldSet = (FieldSet) control; - buffer.append("\n"); - - } else if (control instanceof Label) { - Label label = (Label) control; - buffer.append("\n"); - - } else if (control instanceof Field) { - Field field = (Field) control; - // Write out label - if (POSITION_LEFT.equals(getLabelsPosition())) { - buffer.append("\n"); - buffer.append(""); - } else { - buffer.append("
"); - } - - // Write out field - field.render(buffer); - buffer.append("\n"); - - } else { - buffer.append("
\n"); - } - - if (width != null) { - if (control instanceof Label || !(control instanceof Field)) { - column += width.intValue(); - - } else { - column += (width.intValue() - 1); - } - } - - if (column >= columns) { - buffer.append("\n"); - openTableRow = false; - column = 1; - - } else { - column++; - } - } - } - - if (openTableRow) { - buffer.append("\n"); - } - - buffer.append("
\n"); - control.render(buffer); - buffer.append(" labelAttributes = label.getAttributes(); - for (Map.Entry entry : labelAttributes.entrySet()) { - String labelAttrName = entry.getKey(); - if (!labelAttrName.equals("id") && !labelAttrName.equals("style")) { - buffer.appendAttributeEscaped(labelAttrName, entry.getValue()); - } - } - } - buffer.append(">"); - label.render(buffer); - buffer.append(""); - } else { - buffer.append(""); - } - - // Store the field id and label (the values could be null) - String fieldId = field.getId(); - String fieldLabel = field.getLabel(); - - // Only render a label if the fieldId and fieldLabel is set - if (fieldId != null && fieldLabel != null) { - if (field.isRequired()) { - buffer.append(getMessage("label-required-prefix")); - } else { - buffer.append(getMessage("label-not-required-prefix")); - } - buffer.elementStart("label"); - buffer.appendAttribute("for", fieldId); - buffer.appendAttribute("style", field.getLabelStyle()); - if (field.isDisabled()) { - buffer.appendAttributeDisabled(); - } - String cellClass = field.getLabelStyleClass(); - if (field.getError() == null) { - buffer.appendAttribute("class", cellClass); - } else { - buffer.append(" class=\"error"); - if (cellClass != null) { - buffer.append(" "); - buffer.append(cellClass); - } - buffer.append("\""); - } - buffer.closeTag(); - buffer.append(fieldLabel); - buffer.elementEnd("label"); - if (field.isRequired()) { - buffer.append(getMessage("label-required-suffix")); - } else { - buffer.append(getMessage("label-not-required-suffix")); - } - } - - if (POSITION_LEFT.equals(getLabelsPosition())) { - buffer.append("\n"); - - control.render(buffer); - - buffer.append("
\n"); - } - - /** - * Render the form errors to the given buffer is form processed. - * - * @param buffer the string buffer to render the errors to - * @param processed the flag indicating whether has been processed - */ - protected void renderErrors(HtmlStringBuffer buffer, boolean processed) { - - if (processed && !isValid()) { - - buffer.append("
\n"); - buffer.append("\n"); - - if (getError() != null) { - buffer.append(""); - buffer.append("\n"); - } - - for (Field field : getErrorFields()) { - - // Certain fields might be invalid because - // one of their contained fields are invalid. However these - // fields might not have an error message to display. - // If the outer field's error message is null don't render. - if (field.getError() == null) { - continue; - } - - buffer.append(""); - buffer.append("\n"); - } - - buffer.append("
\n"); - buffer.append(""); - buffer.append(getError()); - buffer.append("\n"); - buffer.append("
"); - - buffer.append(""); - buffer.append(field.getError()); - buffer.append(""); - buffer.append("
\n"); - buffer.append("
\n"); - buffer.append("
\n"); - buffer.append("
\n"); - buffer.append(""); - - for (Button button : buttons) { - buffer.append("\n"); - buffer.append("
"); - } - - buffer.append("
\n"); - buffer.append("\n"); - } - } - - /** - * Close the form tag and render any additional content after the Form. - *

- * Additional content includes javascript validation and - * javascript focus scripts. - * - * @param formFields all fields contained within the form - * @param buffer the buffer to render to - */ - protected void renderTagEnd(List formFields, HtmlStringBuffer buffer) { - - buffer.elementEnd(getTag()); - buffer.append("\n"); - - renderFocusJavaScript(buffer, formFields); - - renderValidationJavaScript(buffer, formFields); - } - - /** - * Render the Form field focus JavaScript to the string buffer. - * - * @param buffer the StringBuffer to render to - * @param formFields the list of form fields - */ - protected void renderFocusJavaScript(HtmlStringBuffer buffer, List formFields) { - - // Set field focus - boolean errorFieldFound = false; - for (int i = 0, size = formFields.size(); i < size; i++) { - Field field = formFields.get(i); - - if (field.getError() != null - && !field.isHidden() - && !field.isDisabled()) { - - String focusJavaScript = - StringUtils.replace(FOCUS_JAVASCRIPT, - "$id", - field.getId()); - buffer.append(focusJavaScript); - errorFieldFound = true; - break; - } - } - - if (!errorFieldFound) { - for (int i = 0, size = formFields.size(); i < size; i++) { - Field field = formFields.get(i); - - if (field.getFocus() - && !field.isHidden() - && !field.isDisabled()) { - - String focusJavaScript = - StringUtils.replace(FOCUS_JAVASCRIPT, - "$id", - field.getId()); - buffer.append(focusJavaScript); - break; - } - } - } - } - - /** - * Render the Form validation JavaScript to the string buffer. - * - * @param buffer the StringBuffer to render to - * @param formFields the list of form fields - */ - protected void renderValidationJavaScript(HtmlStringBuffer buffer, List formFields) { - - // Render JavaScript form validation code - if (isJavaScriptValidation()) { - List functionNames = new ArrayList(); - - buffer.append("\n"); - } - } - - /** - * Returns true if a POST error occurred, false otherwise. - * - * @return true if a POST error occurred, false otherwise - */ - protected boolean hasPostError() { - Exception e = (Exception) - getContext().getRequest().getAttribute(FileUploadService.UPLOAD_EXCEPTION); - - if (e instanceof FileSizeLimitExceededException - || e instanceof SizeLimitExceededException) { - return true; - } - - return false; - } - - /** - * Validate the request for any file upload (multipart) errors. - *

- * A form error message is displayed if a file upload error occurs. - * These messages are defined in the resource bundle: - *

- *
    - *
  • /click-control.properties - *
      - *
    • file-size-limit-exceeded-error
    • - *
    • post-size-limit-exceeded-error
    • - *
    - *
  • - *
- *
- */ - protected void validateFileUpload() { - setError(null); - - Exception exception = (Exception) getContext().getRequest() - .getAttribute(FileUploadService.UPLOAD_EXCEPTION); - - if (!(exception instanceof FileUploadException)) { - return; - } - - FileUploadException fue = (FileUploadException) exception; - - String key = null; - Object args[] = null; - - if (fue instanceof SizeLimitExceededException) { - SizeLimitExceededException se = - (SizeLimitExceededException) fue; - - key = "post-size-limit-exceeded-error"; - - args = new Object[2]; - args[0] = se.getPermittedSize(); - args[1] = se.getActualSize(); - setError(getMessage(key, args)); - - } else if (fue instanceof FileSizeLimitExceededException) { - FileSizeLimitExceededException fse = - (FileSizeLimitExceededException) fue; - - key = "file-size-limit-exceeded-error"; - - // Parse the FileField name from the message - String msg = fue.getMessage(); - int start = 10; - int end = msg.indexOf(' ', start); - String fieldName = fue.getMessage().substring(start, end); - - args = new Object[3]; - args[0] = ClickUtils.toLabel(fieldName); - args[1] = fse.getPermittedSize(); - args[2] = fse.getActualSize(); - setError(getMessage(key, args)); - } - } - - // Private Methods -------------------------------------------------------- - - /** - * Add fields for the given Container to the specified field list, - * recursively including any Fields contained in child containers. - * - * @param container the container to obtain the fields from - * @param fields the list of contained fields - */ - private void addStatefulFields(final Container container, final List fields) { - for (Control control : container.getControls()) { - if (control instanceof Label - || control instanceof Button - || control instanceof NonProcessedHiddenField - ) { - // Skip buttons and labels and NonProcessedHiddenFields - continue; - } - - if (control instanceof Field) { - fields.add((Field) control); - } else if (control instanceof org.apache.click.control.Container) { - Container childContainer = (Container) control; - addStatefulFields(childContainer, fields); - } - } - } - - /** - * Return true if the control is hidden, false otherwise. - * - * @param control control to check hidden status - * @return true if the control is hidden, false otherwise - */ - private boolean isHidden(Control control) { - if (!(control instanceof Field)) { - // Non-Field Controls can not be hidden - return false; - } else { - return ((Field) control).isHidden(); - } - } - - // Inner Classes ---------------------------------------------------------- - - /** - * Provides a HiddenField which does not get processed or bind to its - * incoming value. In addition the field name cannot be changed once set. - */ - private static class NonProcessedHiddenField extends HiddenField { - - private static final long serialVersionUID = 1L; - - /** - * Create a field with the given name and class. - * - * @param name the field name - * @param valueClass the Class of the value Object - */ - public NonProcessedHiddenField(String name, Class valueClass) { - super(name, valueClass); - } - - /** - * Create a field with the given name and value. - * - * @param name the field name - * @param value the value of the field - */ - public NonProcessedHiddenField(String name, Object value) { - super(name, value); - } - - /** - * This method is overridden to not change the field name once it is set. - * - * @param name the name of the field - */ - @Override - public void setName(String name) { - if (this.name != null) { - return; - } - super.setName(name); - } - - /** - * Overridden to not process the field or bind to its request value. - */ - @Override - public boolean onProcess() { - return true; - } - } - - /** - * Provides a HiddenField which name and value cannot be changed, once it - * is set. - */ - private static class ImmutableHiddenField extends NonProcessedHiddenField { - - private static final long serialVersionUID = 1L; - - /** - * Create a field with the given name and value. - * - * @param name the field name - * @param value the value of the field - */ - public ImmutableHiddenField(String name, Object value) { - super(name, value); - } - - /** - * This method is overridden to not change the field value once it is set. - * - * @param value the field value - */ - @Override - public void setValue(String value) { - if (this.value != null) { - return; - } - super.setValue(value); - } - - /** - * This method is overridden to not change the field value object once - * it is set. - * - * @param valueObject the field value object - */ - @Override - public void setValueObject(Object valueObject) { - if (this.valueObject != null) { - return; - } - super.setValueObject(valueObject); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/HiddenField.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/HiddenField.java deleted file mode 100644 index c910ec2e36..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/HiddenField.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -import java.io.IOException; -import java.io.Serializable; -import java.sql.Time; -import java.sql.Timestamp; -import java.util.Date; - -/** - * Provides a Hidden Field control:   <input type='hidden'>. - *

- * The HiddenField control is useful for storing state information in a Form, - * such as object ids, instead of using the Session object. This control is - * capable of supporting the following classes:

    - *
  • Boolean
  • - *
  • Date
  • - *
  • Double
  • - *
  • Float
  • - *
  • Integer
  • - *
  • Long
  • - *
  • Short
  • - *
  • String
  • - *
  • Serializable
  • - *
- *

- * Serializable non-primitive objects will be serialized, compressed and - * Base64 encoded, using {@link ClickUtils#encode(Object)} - * method, and decoded using the corresponding - * {@link ClickUtils#decode(String)} method. - * - *

HiddenField Example

- * - * An example is provided below which uses a hidden field to count the number of - * times a form is consecutively submitted. The count is displayed in the - * page template using the model "count" value. - * - *
- * public class CountPage extends Page {
- *
- *     public Form form = new Form();
- *     public Integer count;
- *
- *     private HiddenField counterField = new HiddenField("counterField", Integer.class);
- *
- *     public CountPage() {
- *         form.add(counterField);
- *         form.add(new Submit("ok", "  OK  "));
- *     }
- *
- *     public void onGet() {
- *         count = new Integer(0);
- *         counterField.setValueObject(count);
- *     }
- *
- *     public void onPost() {
- *         count = (Integer) counterField.getValueObject();
- *         count = new Integer(count.intValue() + 1);
- *         counterField.setValueObject(count);
- *     }
- * } 
- * - * See also W3C HTML reference - * INPUT - */ -public class HiddenField extends Field { - - private static final long serialVersionUID = 1L; - - // ----------------------------------------------------- Instance Variables - - /** The field value Object. */ - protected Object valueObject; - - /** The field value Class. */ - protected Class valueClass; - - // ----------------------------------------------------------- Constructors - - /** - * Construct a HiddenField with the given name and Class. - * - * @param name the name of the hidden field - * @param valueClass the Class of the value Object - */ - public HiddenField(String name, Class valueClass) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - if (valueClass == null) { - throw new IllegalArgumentException("Null valueClass parameter"); - } - this.name = name; - this.valueClass = valueClass; - } - - /** - * Construct a HiddenField with the given name and value object. - * - * @param name the name of the hidden field - * @param value the value object - */ - public HiddenField(String name, Object value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - if (value == null) { - throw new IllegalArgumentException("Null value parameter"); - } - this.name = name; - this.valueClass = value.getClass(); - setValueObject(value); - } - - /** - * Create an HiddenField with no name or Class defined. Please note - * the HiddenField's name and value Class must be defined before it is - * valid. - */ - public HiddenField() { - super(); - } - - // ------------------------------------------------------ Public Attributes - - /** - * Return the hiddenfield's html tag: input. - * - * @see AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "input"; - } - - /** - * Returns true. - * - * @see org.apache.click.control.Field#isHidden() - * - * @return true - */ - @Override - public boolean isHidden() { - return true; - } - - /** - * Return the input type: 'hidden'. - * - * @return the input type: 'hidden' - */ - public String getType() { - return "hidden"; - } - - /** - * @see org.apache.click.control.Field#getValue() - * - * @return the Field value - */ - @Override - public String getValue() { - return (getValueObject() != null) ? getValueObject().toString() : ""; - } - - /** - * @see org.apache.click.control.Field#setValue(String) - * - * @param value the Field value - */ - @Override - public void setValue(String value) { - setValueObject(value); - } - - /** - * Return the registered Class for the Hidden Field value Object. - * - * @return the registered Class for the Hidden Field value Object - */ - public Class getValueClass() { - return valueClass; - } - - /** - * Set the registered Class for the Hidden Field value Object. - * - * @param valueClass the registered Class for the Hidden Field value Object - */ - public void setValueClass(Class valueClass) { - this.valueClass = valueClass; - } - - /** - * Return the value Object of the hidden field. - * - * @see org.apache.click.control.Field#getValueObject() - * - * @return the object representation of the Field value - */ - @Override - public Object getValueObject() { - return valueObject; - } - - /** - * @see Field#setValueObject(Object) - * - * @param value the object value to set - */ - @Override - public void setValueObject(Object value) { - if ((value != null) && (value.getClass() != valueClass)) { - String msg = - "The value.getClass(): '" + value.getClass().getName() - + "' must be the same as the HiddenField valueClass: '" - + ((valueClass != null) ? valueClass.getName() : "null") + "'"; - - throw new IllegalArgumentException(msg); - } - - this.valueObject = value; - } - - /** - * Returns null to ensure no client side JavaScript validation is performed. - * - * @return null to ensure no client side JavaScript validation is performed - */ - @Override - public String getValidationJavaScript() { - return null; - } - - // --------------------------------------------------------- Public Methods - - /** - * This method binds the submitted request value to the Field's value. - */ - @Override - public void bindRequestValue() { - - String aValue = getRequestValue(); - Class valueClass = getValueClass(); - - if (valueClass == null) { - throw new IllegalStateException("The value class is not defined." - + " Please use setValueClass(Class valueClass) to specify" - + " the HiddenField value type."); - } - - if (valueClass == String.class) { - setValue(aValue); - - } else if (aValue != null && aValue.length() > 0) { - - if (valueClass == Integer.class) { - setValueObject(Integer.valueOf(aValue)); - - } else if (valueClass == Boolean.class) { - setValueObject(Boolean.valueOf(aValue)); - - } else if (valueClass == Double.class) { - setValueObject(Double.valueOf(aValue)); - - } else if (valueClass == Float.class) { - setValueObject(Float.valueOf(aValue)); - - } else if (valueClass == Long.class) { - setValueObject(Long.valueOf(aValue)); - - } else if (valueClass == Short.class) { - setValueObject(Short.valueOf(aValue)); - - } else if (valueClass == Timestamp.class) { - long time = Long.parseLong(aValue); - setValueObject(new Timestamp(time)); - - } else if (valueClass == java.sql.Date.class) { - long time = Long.parseLong(aValue); - setValueObject(new java.sql.Date(time)); - - } else if (valueClass == Time.class) { - long time = Long.parseLong(aValue); - setValueObject(new Time(time)); - - } else if (Date.class.isAssignableFrom(valueClass)) { - long time = Long.parseLong(aValue); - setValueObject(new Date(time)); - - } else if (Serializable.class.isAssignableFrom(valueClass)) { - try { - setValueObject(ClickUtils.decode(aValue)); - } catch (ClassNotFoundException cnfe) { - String msg = - "could not decode value for hidden field: " + aValue; - throw new RuntimeException(msg, cnfe); - } catch (IOException ioe) { - String msg = - "could not decode value for hidden field: " + aValue; - throw new RuntimeException(msg, ioe); - } - } else { - setValue(aValue); - } - } else { - setValue(null); - } - } - - /** - * Render the HTML representation of the HiddenField. - * - * @see org.openidentityplatform.openam.click.Control#render(HtmlStringBuffer) - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - - buffer.elementStart(getTag()); - buffer.appendAttribute("type", getType()); - buffer.appendAttribute("name", getName()); - buffer.appendAttribute("id", getId()); - - Class valueCls = getValueClass(); - - if (valueCls == String.class - || valueCls == Integer.class - || valueCls == Boolean.class - || valueCls == Double.class - || valueCls == Float.class - || valueCls == Long.class - || valueCls == Short.class) { - - buffer.appendAttributeEscaped("value", String.valueOf(getValue())); - - } else if (getValueObject() instanceof Date) { - String dateStr = String.valueOf(((Date) getValueObject()).getTime()); - buffer.appendAttributeEscaped("value", dateStr); - - } else if (getValueObject() instanceof Serializable) { - try { - buffer.appendAttribute("value", ClickUtils.encode(getValueObject())); - } catch (IOException ioe) { - String msg = - "could not encode value for hidden field: " - + getValueObject(); - throw new RuntimeException(msg, ioe); - } - } else { - buffer.appendAttributeEscaped("value", getValue()); - } - - buffer.elementEnd(); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Label.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Label.java deleted file mode 100644 index 695b9c2c87..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Label.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides a Label display control. The Label control performs no server side - * processing, and is used primarily to add descriptive labels or horizontal - * rules to auto rendered forms. - * - *

Label Example

- * - * A Label example: - * - *
- * Form form = new Form("form");
- * ..
- *
- * form.add(new Label("hr", "<hr/>")); 
- * - * HTML output: - *
- * <tr><td colspan='2' align='left'><hr/></td></tr> 
- */ -public class Label extends Field { - - private static final long serialVersionUID = 1L; - - // ----------------------------------------------------------- Constructors - - /** - * Create a Label display control. - *

- * Note the Label control will attempt to find a localized label message - * in the parent messages, and if not found then in the field messages - * using the key name of getName() + ".label". - *

- * If a value cannot be found in the parent or control messages then the - * Field name will be converted into a label using the - * {@link org.apache.click.util.ClickUtils#toLabel(String)} method. - * - * @param name the name of the Field - */ - public Label(String name) { - super(name); - } - - /** - * Create a Label display control with the given name and label. - * - * @param name the name of the Field - * @param label the display label caption - */ - public Label(String name, String label) { - super(name, label); - } - - /** - * Create a Label with no label/name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public Label() { - super(); - } - - // --------------------------------------------------------- Public Methods - - /** - * Returns true. - * - * @see Field#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - return true; - } - - /** - * Render a label. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - buffer.append(getLabel()); - } - - /** - * Returns the label. - * - * @see Object#toString() - * - * @return the label string value - */ - @Override - public String toString() { - return getLabel(); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Radio.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Radio.java deleted file mode 100644 index 11bb18d328..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Radio.java +++ /dev/null @@ -1,404 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides a Radio control:   <input type='radio'>. - * - * - * - * - * - *
Radio
- * - * The Radio control is generally used within a RadioGroup, for an code example - * please see the {@link RadioGroup} Javadoc example. - * When used with a RadioGroup the Radio control will derive its name from the - * parent RadioGroup, if the Radio's name is not defined. - * - *

- * See also W3C HTML reference - * INPUT - * - * @see RadioGroup - */ -public class Radio extends Field { - - private static final long serialVersionUID = 1L; - - // ----------------------------------------------------- Instance Variables - - /** The field checked value. */ - protected boolean checked; - - // ----------------------------------------------------------- Constructors - - /** - * Create a radio field with the given value. - * - * @param value the label of the field - */ - public Radio(String value) { - setValue(value); - } - - /** - * Create a radio field with the given value and label. - * - * @param value the label of the field - * @param label the name of the field - */ - public Radio(String value, String label) { - setValue(value); - setLabel(label); - } - - /** - * Create a radio field with the given value, label and name. - * - * @param value the label of the field - * @param label the label of the field - * @param name the name of the field - */ - public Radio(String value, String label, String name) { - setValue(value); - setLabel(label); - setName(name); - } - - /** - * Create an Radio field with no name defined. - */ - public Radio() { - } - - // ------------------------------------------------------ Public Attributes - - /** - * Return the radio's html tag: input. - * - * @see AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "input"; - } - - /** - * Return true if the radio is checked, or false otherwise. - * - * @return true if the radio is checked. - */ - public boolean isChecked() { - return checked; - } - - /** - * Set the selected value of the radio. - * - * @param value the selected value - */ - public void setChecked(boolean value) { - checked = value; - } - - - /** - * Return the Radio field id attribute. - * - * @return HTML element identifier attribute "id" value - */ - @Override - public String getId() { - if (hasAttributes() && getAttributes().containsKey("id")) { - return getAttribute("id"); - - } else { - String fieldName = getName(); - - if (fieldName == null) { - // If fieldName is null, exit early - return null; - } - - Form parentForm = getForm(); - String formId = (parentForm != null) ? parentForm.getId() + "_" : ""; - String id = formId + fieldName + "_" + ClickUtils.escapeHtml(getValue()); - - if (id.indexOf('/') != -1) { - id = id.replace('/', '_'); - } - if (id.indexOf(' ') != -1) { - id = id.replace(' ', '_'); - } - if (id.indexOf('<') != -1) { - id = id.replace('<', '_'); - } - if (id.indexOf('>') != -1) { - id = id.replace('>', '_'); - } - if (id.indexOf('.') != -1) { - id = id.replace('.', '_'); - } - - return id; - } - } - - /** - * Set the parent of the Field. - * - * @see org.apache.click.Control#setParent(Object) - * - * @param parent the parent of the Control - * @throws IllegalArgumentException if the given parent instance is - * referencing this object: if (parent == this) - */ - @Override - public void setParent(Object parent) { - if (parent == this) { - throw new IllegalArgumentException("Cannot set parent to itself"); - } - this.parent = parent; - } - - /** - * Return the field display label. - *

- * If the label value is null, this method will attempt to find a - * localized label message in the parent messages using the key: - *

- * If the Radio name attribute is not null: - *

- * super.getName() + ".label" - *
- * If the Radio name attribute is null and the parent of the Radio is the RadioGroup: - *
- * parent.getName() + "." + getValue() + ".label" - *
- * If not found then the message will be looked up in the - * /click-control.properties file using the same key. - * If a value still cannot be found then the Field name will be - * the radio value. - *

- * For example given a CustomerPage with the properties file - * CustomerPage.properties: - * - *

-     * gender.M.label=Male
-     * gender.F.label=Female 
- * - * The page Radio code: - *
-     * public class CustomerPage extends Page {
-     *
-     *     public Form form = new Form();
-     *
-     *     private RadioGroup radioGroup = new RadioGroup("gender");
-     *
-     *     public CustomerPage() {
-     *         radioGroup.add(new Radio("M"));
-     *         radioGroup.add(new Radio("F"));
-     *         form.add(radioGroup);
-     *
-     *         ..
-     *     }
-     * } 
- * - * Will render the Radio label properties as: - *
-     * <input type="radio" name="gender" value="M"><label>Male</label></label><br/>
-     * <input type="radio" name="gender" value="F"><label>Female</label></label> 
- * - * @return the display label of the Field - */ - @Override - public String getLabel() { - if (label == null) { - if (super.getName() != null) { - label = getMessage(super.getName() + ".label"); - } else { - Object parent = getParent(); - if (parent instanceof RadioGroup) { - RadioGroup radioGroup = (RadioGroup) parent; - label = getMessage( - radioGroup.getName() + "." + getValue() + ".label"); - } - } - } - if (label == null) { - label = getValue(); - } - return label; - } - - /** - * Return the name of the Radio field. If the Radio name attribute has not - * been explicitly set, this method will return its parent RadioGroup's - * name if defined. - * - * @return the name of the control - */ - @Override - public String getName() { - if (super.getName() != null) { - return super.getName(); - - } else { - Object parent = getParent(); - if (parent instanceof RadioGroup) { - RadioGroup radioGroup = (RadioGroup) parent; - return radioGroup.getName(); - - } else { - return null; - } - } - } - - /** - * Return the input type: 'radio'. - * - * @return the input type: 'radio' - */ - public String getType() { - return "radio"; - } - - /** - * Set the radio value, setting the checked status if given value is the - * same as the radio field value. - * - * @see Field#setValue(String) - * - * @param value the Field value - */ - @Override - public void setValue(String value) { - setChecked(getValue().equals(value)); - super.setValue(value); - } - - // --------------------------------------------------------- Public Methods - - /** - * Bind the request submission, setting the Field {@link #checked} property - * if defined in the request. - */ - @Override - public void bindRequestValue() { - String localValue = getRequestValue(); - - setChecked(getValue().equals(localValue)); - } - - /** - * Process the request Context setting the checked value if selected - * and invoking the controls listener if defined. - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - if (isDisabled()) { - Context context = getContext(); - - // Switch off disabled property if control has incoming request - // parameter. Normally this means the field was enabled via JS - if (context.hasRequestParameter(getName())) { - setDisabled(false); - } else { - // If field is disabled skip process event - return true; - } - } - - bindRequestValue(); - - if (isChecked()) { - dispatchActionEvent(); - } - return true; - } - - /** - * Render a HTML Radio string. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - String id = getId(); - - buffer.elementStart(getTag()); - - buffer.appendAttribute("type", getType()); - buffer.appendAttribute("name", getName()); - buffer.appendAttributeEscaped("value", getValue()); - buffer.appendAttribute("id", id); - buffer.appendAttribute("title", getTitle()); - if (isValid()) { - removeStyleClass("error"); - } else { - addStyleClass("error"); - } - if (getTabIndex() > 0) { - buffer.appendAttribute("tabindex", getTabIndex()); - } - if (isChecked()) { - buffer.appendAttribute("checked", "checked"); - } - - appendAttributes(buffer); - - if (isDisabled() || isReadonly()) { - buffer.appendAttributeDisabled(); - } - if (isReadonly()) { - buffer.appendAttributeReadonly(); - } - - buffer.elementEnd(); - - buffer.elementStart("label"); - buffer.appendAttribute("for", id); - buffer.closeTag(); - buffer.appendEscaped(getLabel()); - buffer.elementEnd("label"); - - // radio element does not support "readonly" element, so as a work around - // we make the field "disabled" and render a hidden field to submit its value - if (isReadonly() && isChecked()) { - buffer.elementStart("input"); - buffer.appendAttribute("type", "hidden"); - buffer.appendAttribute("name", getName()); - buffer.appendAttributeEscaped("value", getValue()); - buffer.elementEnd(); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/RadioGroup.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/RadioGroup.java deleted file mode 100644 index 45aac06899..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/RadioGroup.java +++ /dev/null @@ -1,563 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.click.util.PropertyUtils; - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Provides a RadioGroup control. - * - * - * - * - * - * - *
Radio Group - * Red - * Green - * Blue - *
- * - * The RadioGroup control provides a Field for containing grouped Radio buttons. - * Radio controls added to a RadioGroup will have their name set to that of - * the RadioGroup. This will ensure the buttons will toggle together so that - * only one button is selected at a time. - * - *

RadioGroup Example

- * - * The example below illustrates a RadioGroup being added to a form. - * - *
- * public class Purchase extends Page {
- *
- *     public Form form = new Form();
- *
- *     private RadioGroup radioGroup = new RadioGroup("packaging");
- *
- *     public Purchase() {
- *         radioGroup.add(new Radio("STD", "Standard "));
- *         radioGroup.add(new Radio("PRO", "Protective "));
- *         radioGroup.add(new Radio("GFT", "Gift Wrap "));
- *         radioGroup.setValue("STD");
- *         radioGroup.setVerticalLayout(true);
- *         form.add(radioGroup);
- *
- *         ..
- *     }
- * } 
- * - * This radio group field would be render as: - * - * - * - * - * - * - *
Packaging - * Standard
- * Protective
- * Gift Wrap - *
- * - * See also W3C HTML reference - * INPUT - * - * @see Radio - */ -public class RadioGroup extends Field { - - private static final long serialVersionUID = 1L; - - /** - * The field validation JavaScript function template. - * The function template arguments are:
    - *
  • 0 - is the field id
  • - *
  • 1 - is the name of the radio button
  • - *
  • 2 - is the id of the form
  • - *
  • 3 - is the Field required status
  • - *
  • 4 - is the localized error message
  • - *
  • 5 - is the first radio id to select
  • - *
- */ - protected final static String VALIDATE_RADIOGROUP_FUNCTION = - "function validate_{0}() '{'\n" - + " var msg = validateRadioGroup(''{1}'', ''{2}'', {3}, [''{4}'']);\n" - + " if (msg) '{'\n" - + " return msg + ''|{5}'';\n" - + " '}' else '{'\n" - + " return null;\n" - + " '}'\n" - + "'}'\n"; - - // Instance Variables ----------------------------------------------------- - - /** The list of Radio controls. */ - protected List radioList; - - /** - * The layout is vertical flag (default false). If the layout is vertical - * each Radio controls is rendered on a new line using the <br> - * tag. - */ - protected boolean isVerticalLayout = true; - - // Constructors ----------------------------------------------------------- - - /** - * Create a RadioGroup with the given name. - * - * @param name the name of the field - */ - public RadioGroup(String name) { - super(name); - } - - /** - * Create a RadioGroup with the given name and required status. - * - * @param name the name of the field - * @param required the field required status - */ - public RadioGroup(String name, boolean required) { - super(name); - setRequired(required); - } - - /** - * Create a RadioGroup with the given name and label. - * - * @param name the name of the field - * @param label the label of the field - */ - public RadioGroup(String name, String label) { - super(name, label); - } - - /** - * Create a RadioGroup with the given name, label and required status. - * - * @param name the name of the field - * @param label the label of the field - * @param required the field required status - */ - public RadioGroup(String name, String label, boolean required) { - super(name, label); - setRequired(required); - } - - /** - * Create a RadioGroup field with no name. - *

- * Please note the control's name must be defined before it is valid. - */ - public RadioGroup() { - super(); - } - - // Public Attributes ------------------------------------------------------ - - /** - * Add the given radio to the radio group. When the radio is added to the - * group it will use its parent RadioGroup's name when rendering if it - * has not already been set. - * - * @param radio the radio control to add to the radio group - * @throws IllegalArgumentException if the radio parameter is null - */ - public void add(Radio radio) { - if (radio == null) { - throw new IllegalArgumentException("Null radio parameter"); - } - - radio.setParent(this); - getRadioList().add(radio); - if (getForm() != null) { - radio.setForm(getForm()); - } - } - - /** - * Add the given collection Radio item options to the RadioGroup. - * - * @param options the collection of Radio items to add - * @throws IllegalArgumentException if options is null - */ - public void addAll(Collection options) { - if (options == null) { - String msg = "options parameter cannot be null"; - throw new IllegalArgumentException(msg); - } - for (Radio radio : options) { - add(radio); - } - } - - /** - * Add the given Map of radio values and labels to the RadioGroup. - * The Map entry key will be used as the radio value and the Map entry - * value will be used as the radio label. - *

- * It is recommended that LinkedHashMap is used as the Map - * parameter to maintain the order of the radio items. - * - * @param options the Map of radio option values and labels to add - * @throws IllegalArgumentException if options is null - */ - public void addAll(Map options) { - if (options == null) { - String msg = "options parameter cannot be null"; - throw new IllegalArgumentException(msg); - } - for (Map.Entry entry : options.entrySet()) { - Radio radio = new Radio(entry.getKey().toString(), - entry.getValue().toString()); - add(radio); - } - } - - /** - * Add the given collection of objects to the RadioGroup, creating new - * Radio instances based on the object properties specified by value and - * label. - * - *

-     * RadioGroup radioGroup = new RadioGroup("type", "Type:");
-     * radioGroup.addAll(getCustomerService().getCustomerTypes(), "id", "name");
-     * form.add(select); 
- * - * @param objects the collection of objects to render as radio options - * @param value the name of the object property to render as the Radio value - * @param label the name of the object property to render as the Radio label - * @throws IllegalArgumentException if options, value or label parameter is null - */ - public void addAll(Collection objects, String value, String label) { - if (objects == null) { - String msg = "objects parameter cannot be null"; - throw new IllegalArgumentException(msg); - } - if (value == null) { - String msg = "value parameter cannot be null"; - throw new IllegalArgumentException(msg); - } - if (label == null) { - String msg = "label parameter cannot be null"; - throw new IllegalArgumentException(msg); - } - - if (objects.isEmpty()) { - return; - } - - Map cache = new HashMap(); - - for (Object object : objects) { - try { - Object valueResult = PropertyUtils.getValue(object, value, cache); - Object labelResult = PropertyUtils.getValue(object, label, cache); - - Radio radio = null; - - if (labelResult != null) { - radio = new Radio(valueResult.toString(), labelResult.toString()); - - } else { - radio = new Radio(valueResult.toString()); - } - - add(radio); - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - /** - * Return the RadioGroup focus JavaScript. - * - * @return the RadioGroup focus JavaScript - */ - @Override - public String getFocusJavaScript() { - String id = ""; - - if (!getRadioList().isEmpty()) { - Radio radio = getRadioList().get(0); - id = radio.getId(); - } - - HtmlStringBuffer buffer = new HtmlStringBuffer(32); - buffer.append("setFocus('"); - buffer.append(id); - buffer.append("');"); - - return buffer.toString(); - } - - /** - * @see Field#setForm(Form) - * - * @param form Field's parent Form - */ - @Override - public void setForm(Form form) { - super.setForm(form); - if (hasRadios()) { - for (int i = 0, size = getRadioList().size(); i < size; i++) { - Radio radio = getRadioList().get(i); - radio.setForm(getForm()); - } - } - } - - /** - * Return true if the radio control layout is vertical. - * - * @return true if the radio control layout is vertical - */ - public boolean isVerticalLayout() { - return isVerticalLayout; - } - - /** - * Set the vertical radio control layout flag. - * - * @param vertical the vertical layout flag - */ - public void setVerticalLayout(boolean vertical) { - isVerticalLayout = vertical; - } - - /** - * Return the list of radio controls. - * - * @return the list of radio controls - */ - public List getRadioList() { - if (radioList == null) { - radioList = new ArrayList(); - } - return radioList; - } - - /** - * Return true if RadioGroup has Radio controls, or false otherwise. - * - * @return true if RadioGroup has Radio controls, or false otherwise - */ - public boolean hasRadios() { - return radioList != null && !radioList.isEmpty(); - } - - /** - * Return the RadioGroup JavaScript client side validation function. - * - * @return the field JavaScript client side validation function - */ - @Override - public String getValidationJavaScript() { - Object[] args = new Object[6]; - args[0] = getId(); - args[1] = getName(); - args[2] = getForm().getId(); - args[3] = String.valueOf(isRequired()); - args[4] = getMessage("select-error", getErrorLabel()); - - if (!getRadioList().isEmpty()) { - Radio radio = getRadioList().get(0); - args[5] = radio.getId(); - } else { - args[5] = ""; - } - - return MessageFormat.format(VALIDATE_RADIOGROUP_FUNCTION, args); - } - - // Public Methods --------------------------------------------------------- - - /** - * @see org.apache.click.Control#onInit() - */ - @Override - public void onInit() { - super.onInit(); - for (int i = 0, size = getRadioList().size(); i < size; i++) { - Radio radio = getRadioList().get(i); - radio.onInit(); - } - } - - /** - * Process the request Context setting the checked value and invoking - * the controls listener if defined. - * - * @see org.apache.click.Control#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - if (isDisabled()) { - Context context = getContext(); - - // Switch off disabled property if control has incoming request - // parameter. Normally this means the field was enabled via JS - if (context.hasRequestParameter(getName())) { - setDisabled(false); - } else { - // If field is disabled skip process event - return true; - } - } - - bindRequestValue(); - - boolean continueProcessing = true; - for (int i = 0, size = getRadioList().size(); i < size; i++) { - Radio radio = getRadioList().get(i); - if (!radio.onProcess()) { - continueProcessing = false; - } - } - - if (getValidate()) { - validate(); - } - - dispatchActionEvent(); - - return continueProcessing; - } - - /** - * @see org.apache.click.Control#onDestroy() - */ - @Override - public void onDestroy() { - for (int i = 0, size = getRadioList().size(); i < size; i++) { - Radio radio = getRadioList().get(i); - try { - radio.onDestroy(); - } catch (Throwable t) { - ClickUtils.getLogService().error("onDestroy error", t); - } - } - } - - /** - * @see AbstractControl#getControlSizeEst() - * - * @return the estimated rendered control size in characters - */ - @Override - public int getControlSizeEst() { - return getRadioList().size() * 30; - } - - /** - * Render the HTML representation of the RadioGroup. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - buffer.elementStart("span"); - buffer.appendAttribute("id", getId()); - appendAttributes(buffer); - buffer.closeTag(); - - String localValue = getValue(); - - final int size = getRadioList().size(); - - for (int i = 0; i < size; i++) { - Radio radio = getRadioList().get(i); - - if (isReadonly() && !radio.isReadonly()) { - radio.setReadonly(true); - } - if (isDisabled() && !radio.isDisabled()) { - radio.setDisabled(true); - } - - if (localValue != null && localValue.length() > 0) { - if (radio.getValue().equals(localValue)) { - radio.setChecked(true); - } else { - radio.setChecked(false); - } - } - - radio.render(buffer); - - if (isVerticalLayout() && (i < size - 1)) { - buffer.append("
"); - } - } - - buffer.elementEnd("span"); - } - - /** - * Return the HTML rendered RadioGroup string. - * - * @see Object#toString() - * - * @return the HTML rendered RadioGroup string - */ - @Override - public String toString() { - HtmlStringBuffer buffer = new HtmlStringBuffer(getControlSizeEst()); - render(buffer); - return buffer.toString(); - } - - /** - * Validate the RadioGroup request submission. - *

- * A field error message is displayed if a validation error occurs. - * These messages are defined in the resource bundle:

- *
org.apache.click.control.MessageProperties
- *

- * Error message bundle key names include:

    - *
  • select-error
  • - *
- */ - @Override - public void validate() { - setError(null); - - if (isRequired() && getValue().length() == 0) { - setErrorMessage("select-error"); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Renderable.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Renderable.java deleted file mode 100644 index fe12dca543..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Renderable.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -import java.io.Serializable; - -/** - * Provides an interface for rendering output to an efficient string buffer. - *

- * Implementations of this interface will normally render HTML markup. - */ -public interface Renderable extends Serializable { - - /** - * Render output, normally HTML markup, to the given buffer. - * - * @param buffer the string buffer to render output to - */ - public void render(HtmlStringBuffer buffer); - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Table.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Table.java deleted file mode 100644 index 61497b65b2..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/Table.java +++ /dev/null @@ -1,2214 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Control; -import org.apache.click.Stateful; -import org.apache.click.control.Decorator; -import org.apache.click.dataprovider.DataProvider; -import org.apache.click.dataprovider.PagingDataProvider; -import org.openidentityplatform.openam.click.element.CssImport; -import org.openidentityplatform.openam.click.element.CssStyle; -import org.openidentityplatform.openam.click.element.Element; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.math.NumberUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; - -/** - * Provides a HTML Table control: <table>. - * - * - * - * - * - *
- * - *
- * - * The Table control provides a HTML <table> control with - * DisplayTag - * like functionality. The design of the Table control has been informed by - * the excellent DisplayTag library. - * - *

Table Example

- * - * An example Table usage is provided below: - * - *
- * public class CustomersPage extends BorderPage {
- *
- *     public Table table = new Table();
- *     public ActionLink deleteLink = new ActionLink("Delete", this, "onDeleteClick");
- *
- *     public CustomersPage() {
- *         table.setClass(Table.CLASS_ITS);
- *         table.setPageSize(4);
- *         table.setShowBanner(true);
- *         table.setSortable(true);
- *
- *         table.addColumn(new Column("id"));
- *         table.addColumn(new Column("name"));
- *
- *         Column column = new Column("email");
- *         column.setAutolink(true);
- *         table.addColumn(column);
- *
- *         table.addColumn(new Column("investments"));
- *
- *         column = new Column("Action");
- *         column.setDecorator(new LinkDecorator(table, deleteLink, "id"));
- *         column.setSortable(false);
- *         table.addColumn(column);
- *
- *         // Set data provider to populate the table row list from
- *         table.setDataProvider(new DataProvider() {
- *             public List getData() {
- *                 return getCustomerService().getCustomersSortedByName();
- *             }
- *         });
- *     }
- *
- *     public boolean onDeleteClick() {
- *         Integer id = deleteLink.getValueInteger();
- *         getCustomerService().deleteCustomer(id);
- *         return true;
- *     }
- * } 
- * - *

DataProvider

- * In the example above a {@link DataProvider} - * is used to populate the Table {@link #setRowList(List) row list} - * from. DataProviders are used to provide data on demand to controls. For very - * large data sets use a {@link PagingDataProvider} - * instead. See the section large data sets for - * details. - * - *

CSS and JavaScript resources

- * - * The Table control makes use of the following resources (which Click automatically - * deploys to the application directory, /click): - * - *
    - *
  • click/table.css
  • - *
- * - * To import the Table CSS styles and any control JavaScript simply reference - * the variables $headElements and - * $jsElements in the page template. For example: - * - *
- * <html>
- * <head>
- * $headElements
- * </head>
- * <body>
- *
- * $table
- *
- * $jsElements
- * </body>
- * </html> 
- * - *

Table Styles

- * - * The table CSS style sheet is adapted from the DisplayTag screen.css - * style sheet and includes the styles: - * - *
    - *
  • blue1
  • - *
  • blue2
  • - *
  • complex
  • - *
  • isi
  • - *
  • its
  • - *
  • mars
  • - *
  • nocol
  • - *
  • orange1
  • - *
  • orange2
  • - *
  • report
  • - *
  • simple
  • - *
- * - * To use one of these CSS styles set the table "class" - * attribute. For example in a page constructor: - * - *
- * public LineItemsPage() {
- *     Table table = new Table("table");
- *     table.setClass(Table.CLASS_SIMPLE);
- *     ..
- * } 
- * - * An alternative method of specifying the table class to use globally for your - * application is to define a table-default-class message property - * in your applications click-pages.properties file. For example: - * - *
- * table-default-class=blue2 
- * - *

Paging

- * - * Table provides out-of-the-box paging. - *

- * To enable Paging set the table's page size: {@link #setPageSize(int)} and - * optionally make the Table Banner visible: {@link #setShowBanner(boolean)}. - *

- * Table supports rendering different paginators through the method - * {@link #setPaginator(Renderable) setPaginator}. - * The default Table Paginator is {@link TablePaginator}. - * - *

Sorting

- * Table also has built in column sorting. - *

- * To enable/disable sorting use {@link #setSortable(boolean)}. - * - *

Custom Parameters - preserve state when paging and sorting

- * - * Its often necessary to add extra parameters on the Table links in order to - * preserve state when navigating between pages or sorting columns. - *

- * One can easily add extra parameters to links generated by the Table through - * the Table's {@link #getControlLink() controlLink}. - *

- * For example: - * - *

- * public CompanyPage extends BorderPage {
- *
- *      // companyId is the criteria used to limit Table rows to clients from
- *      // the specified company. This variable could be selected from a drop
- *      // down list or similar means.
- *      public String companyId;
- *
- *      public Table table = new Table();
- *
- *      public onInit() {
- *          // Set the companyId on the table's controlLink. If you view the
- *          // output rendered by Table note that the companyId parameter
- *          // is rendered for each Paging and Sorting link.
- *          table.getControlLink().setParameter("companyId", companyId);
- *
- *          // Set data provider to populate the table row list from
- *          table.setDataProvider(new DataProvider() {
- *              public List getData() {
- *                  return getCompanyDao().getCompanyClients(companyId);
- *              }
- *          });
- *      }
- *
- *      ...
- * } 
- * - *

Row Attributes

- * - * Sometimes it is useful to add HTML attributes on individual rows. For these - * cases one can override the method {@link #addRowAttributes(Map, Object, int)} - * and add custom attributes to the row's attribute Map. - * - *

Large Datasets

- * - * For large data sets use a {@link PagingDataProvider}. - *

- * A PagingDataProvider has two responsibilities. First, it must load - * only those rows to be displayed on the current page e.g. rows 50 - 59. - * Second, it must return the total number of rows represented by the - * PagingDataProvider. - *

- * The Table methods {@link #getFirstRow()}, {@link #getLastRow()} - * and {@link #getPageSize()} provides the necessary information to limit - * the rows to the selected page. For sorting, the Table methods - * {@link #getSortedColumn()} and {@link #isSortedAscending()} provides the - * necessary information to sort the data. - *

- * Please note: when using a PagingDataProvider, you are responsible - * for sorting the data, as the Table does not have access to all the data. - *

- * Example usage: - * - *

- * public CustomerPage() {
- *     Table table = new Table("table");
- *     table.setPageSize(10);
- *     table.setShowBanner(true);
- *     table.setSortable(true);
- *
- *     table.addColumn(new Column("id"));
- *     table.addColumn(new Column("name"));
- *     table.addColumn(new Column("investments"));
- *
- *     table.setDataProvider(new PagingDataProvider() {
- *
- *         public List getData() {
- *             int start = table.getFirstRow();
- *             int count = table.getPageSize();
- *             String sortColumn = table.getSortedColumn();
- *             boolean ascending = table.isSortedAscending();
- *
- *             return getCustomerService().getCustomersForPage(start, count, sortColumn, ascending);
- *         }
- *
- *         public int size() {
- *             return getCustomerService().getNumberOfCustomers();
- *         }
- *     });
- * } 
- * - * For a live demonstration see the - * Large Dataset Demo. - *

- * - * See the W3C HTML reference - * Tables - * and the W3C CSS reference - * Tables. - * - * @see Column - * @see Decorator - */ -public class Table extends AbstractControl implements Stateful { - - // Constants -------------------------------------------------------------- - - private static final long serialVersionUID = 1L; - - private static final Set DARK_STYLES; - - static { - DARK_STYLES = new HashSet(); - DARK_STYLES.add("isi"); - DARK_STYLES.add("nocol"); - DARK_STYLES.add("report"); - } - - /** The attached style pagination banner position. */ - public static final int PAGINATOR_ATTACHED = 1; - - /** The detached style pagination banner position. */ - public static final int PAGINATOR_DETACHED = 2; - - /** The attached style pagination banner position. */ - public static final int PAGINATOR_INLINE = 3; - - /** The table top pagination banner position. */ - public static final int POSITION_TOP = 1; - - /** The table bottom pagination banner position. */ - public static final int POSITION_BOTTOM = 2; - - /** The table top and bottom pagination banner position. */ - public static final int POSITION_BOTH = 3; - - /** The control ActionLink page number parameter name: "ascending". */ - public static final String ASCENDING = "ascending"; - - /** The control ActionLink sorted column parameter name: "column". */ - public static final String COLUMN = "column"; - - /** The control ActionLink page number parameter name: "page". */ - public static final String PAGE = "page"; - - /** The control ActionLink sort number parameter name: "sort". */ - public static final String SORT = "sort"; - - /** The table CSS style: "blue1". */ - public static final String CLASS_BLUE1 = "blue1"; - - /** The table CSS style: "blue2". */ - public static final String CLASS_BLUE2 = "blue2"; - - /** The table CSS style: "complex". */ - public static final String CLASS_COMPLEX = "complex"; - - /** The table CSS style: "isi". */ - public static final String CLASS_ISI = "isi"; - - /** The table CSS style: "its". */ - public static final String CLASS_ITS = "its"; - - /** The table CSS style: "mars". */ - public static final String CLASS_MARS = "mars"; - - /** The table CSS style: "nocol". */ - public static final String CLASS_NOCOL = "nocol"; - - /** The table CSS style: "orange1". */ - public static final String CLASS_ORANGE1 = "orange1"; - - /** The table CSS style: "orange2". */ - public static final String CLASS_ORANGE2 = "orange2"; - - /** The table CSS style: "report". */ - public static final String CLASS_REPORT = "report"; - - /** The table CSS style: "simple". */ - public static final String CLASS_SIMPLE = "simple"; - - /** The array of pre-defined table CSS class styles. */ - public static final String[] CLASS_STYLES = { - Table.CLASS_BLUE1, Table.CLASS_BLUE2, Table.CLASS_COMPLEX, - Table.CLASS_ISI, Table.CLASS_ITS, Table.CLASS_MARS, Table.CLASS_NOCOL, - Table.CLASS_ORANGE1, Table.CLASS_ORANGE2, Table.CLASS_REPORT, - Table.CLASS_SIMPLE - }; - - // Instance Variables ----------------------------------------------------- - - /** - * The table pagination banner position: - * [ POSITION_TOP | POSITION_BOTTOM | POSITION_BOTH ]. - * The default position is POSITION_BOTTOM. - */ - protected int bannerPosition = POSITION_BOTTOM; - - /** The table HTML <caption> element. */ - protected String caption; - - /** The map of table columns keyed by column name. */ - protected Map columns = new HashMap(); - - /** The list of table Columns. */ - protected List columnList = new ArrayList(); - - /** The table paging and sorting control action link. */ - protected ActionLink controlLink; - - /** The list of table controls. */ - protected List controlList; - - /** The table HTML <td> height attribute. */ - protected String height; - - /** The table data provider. */ - @SuppressWarnings("unchecked") - protected DataProvider dataProvider; - - /** - * The total possible number of rows of the table. This value - * could be much larger than the number of entries in the {@link #rowList}, - * indicating that some rows have not been loaded yet. - */ - protected int rowCount; - - /** - * The table rows set 'hover' CSS class on mouseover events flag. By default - * hoverRows is false. - */ - protected boolean hoverRows; - - /** - * Flag indicating if rowList is nullified when - * onDestroy() is invoked, default is true. This flag only applies - * to stateful pages. - * - * @see #setNullifyRowListOnDestroy(boolean) - * - * @deprecated stateful pages are not supported anymore, use stateful - * Controls instead - */ - protected boolean nullifyRowListOnDestroy = true; - - /** - * The currently displayed page number. The page number is zero indexed, - * i.e. the page number of the first page is 0. - */ - protected int pageNumber; - - /** - * The maximum page size in rows. A value of 0 means there is no maximum - * page size. - */ - protected int pageSize; - - /** The paginator used to render the table pagination controls. */ - protected Renderable paginator; - - /** - * The paginator attachment style: - * [ PAGINATOR_ATTACHED | PAGINATOR_DETACHED | PAGINATOR_INLINE ]. - * The default paginator attachment type is PAGINATOR_ATTACHED. - */ - protected int paginatorAttachment = PAGINATOR_ATTACHED; - - /** - * The default column render id attribute status. The default value is - * false. - */ - protected boolean renderId; - - /** - * The list Table rows. Please note the rowList is cleared in table - * {@link #onDestroy()} method at the end of each request. - */ - @SuppressWarnings("unchecked") - protected List rowList; - - /** - * The show table banner flag detailing number of rows and current rows - * displayed. - */ - protected boolean showBanner; - - /** - * The default column are sortable status. By default columnsSortable is - * false. - */ - protected boolean sortable = false; - - /** The row list is sorted status. By default sorted is false. */ - protected boolean sorted = false; - - /** The rows list is sorted in ascending order. */ - protected boolean sortedAscending = true; - - /** The name of the sorted column. */ - protected String sortedColumn; - - /** The table HTML <td> width attribute. */ - protected String width; - - // Constructors ----------------------------------------------------------- - - /** - * Create an Table for the given name. - * - * @param name the table name - * @throws IllegalArgumentException if the name is null - */ - public Table(String name) { - setName(name); - } - - /** - * Create a Table with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public Table() { - super(); - } - - // Public Attributes ------------------------------------------------------ - - /** - * Return the table's html tag: table. - * - * @see org.apache.click.control.AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "table"; - } - - /** - * Set the parent of the Table. - * - * @see Control#setParent(Object) - * - * @param parent the parent of the Table - * @throws IllegalStateException if {@link #name} is not defined - * @throws IllegalArgumentException if the given parent instance is - * referencing this object: if (parent == this) - */ - @Override - public void setParent(Object parent) { - if (parent == this) { - throw new IllegalArgumentException("Cannot set parent to itself"); - } - if (getName() == null) { - throw new IllegalArgumentException("Table name is not defined"); - } - this.parent = parent; - } - - /** - * Return the Table pagination banner position. Banner position values: - * [ POSITION_TOP | POSITION_BOTTOM | POSITION_BOTH ]. - * The default banner position is POSITION_BOTTOM. - * - * @return the table pagination banner position - */ - public int getBannerPosition() { - return bannerPosition; - } - - /** - * Set Table pagination banner position. Banner position values: - * [ POSITION_TOP | POSITION_BOTTOM | POSITION_BOTH ]. - * - * @param value the table pagination banner position - */ - public void setBannerPosition(int value) { - bannerPosition = value; - } - - /** - * Return the content of the table <caption> element, or null - * if not defined. - * - * @return the content of the table caption element, or null if not - * defined. - */ - public String getCaption() { - return caption; - } - - /** - * Set the content of the table <caption> element. - * - * @param caption the content of the caption element. - */ - public void setCaption(String caption) { - this.caption = caption; - } - - /** - * Set the HTML class attribute. - *

- * Note: this method will replace the existing "class" - * attribute value. - *

- * Predefined table CSS classes include: - *

    - *
  • complex
  • - *
  • isi
  • - *
  • its
  • - *
  • mars
  • - *
  • nocol
  • - *
  • report
  • - *
  • simple
  • - *
- * - * @param value the HTML class attribute - */ - public void setClass(String value) { - setAttribute("class", value); - } - - /** - * Add the column to the table. The column will be added to the - * {@link #columns} Map using its name. - * - * @param column the column to add to the table - * @return the added column - * @throws IllegalArgumentException if the table already contains a column - * with the same name, or the column name is not defined - */ - public Column addColumn(Column column) { - if (column == null) { - String msg = "column parameter cannot be null"; - throw new IllegalArgumentException(msg); - } - if (StringUtils.isBlank(column.getName())) { - throw new IllegalArgumentException("Column name is not defined"); - } - if (getColumns().containsKey(column.getName())) { - String msg = - "Table already contains column named: " + column.getName(); - throw new IllegalArgumentException(msg); - } - - getColumns().put(column.getName(), column); - getColumnList().add(column); - column.setTable(this); - - return column; - } - - /** - * Remove the given Column from the table. - * - * @param column the column to remove from the table - */ - public void removeColumn(Column column) { - if (column != null && getColumns().containsKey(column.getName())) { - getColumns().remove(column.getName()); - getColumnList().remove(column); - } - } - - /** - * Remove the named column from the Table. - * - * @param name the name of the column to remove from the table - */ - public void removeColumn(String name) { - Column column = getColumns().get(name); - removeColumn(column); - } - - /** - * Remove the list of named columns from the table. - * - * @param columnNames the list of column names to remove from the table - */ - public void removeColumns(List columnNames) { - if (columnNames != null) { - for (String columnName : columnNames) { - removeColumn(columnName); - } - } - } - - /** - * Return true if the Table will nullify the rowList when the - * onDestroy() method is invoked. - * - * @return true if the rowList is nullified when onDestroy is invoked - */ - public boolean getNullifyRowListOnDestroy() { - return nullifyRowListOnDestroy; - } - - /** - * Set the flag to nullify the rowList when the onDestroy() - * method is invoked. - *

- * This option only applies to stateful pages. - *

- * If this option is false, the rowList will be persisted between requests. - * If this option is true (the default), the rowList must be set - * each request. - * - * @param value the flag value to nullify the table rowList when onDestroy - * is called - */ - public void setNullifyRowListOnDestroy(boolean value) { - nullifyRowListOnDestroy = value; - } - - /** - * Return the Column for the given name. - * - * @param name the name of the column to return - * @return the Column for the given name - */ - public Column getColumn(String name) { - return columns.get(name); - } - - /** - * Return the list of table columns. - * - * @return the list of table columns - */ - public List getColumnList() { - return columnList; - } - - /** - * Return the Map of table Columns, keyed on column name. - * - * @return the Map of table Columns, keyed on column name - */ - public Map getColumns() { - return columns; - } - - /** - * Add the given Control to the table. The control will be processed when - * the Table is processed. - * - * @deprecated use {@link #add(Control)} instead - * - * @param control the Control to add to the table - * @return the added control - */ - public Control addControl(Control control) { - return add(control); - } - - /** - * Add the given Control to the table. The control will be processed when - * the Table is processed. - * - * @param control the Control to add to the table - * @return the added control - */ - public Control add(Control control) { - if (control == null) { - throw new IllegalArgumentException("Null control parameter"); - } - // Note: set parent first since setParent might veto further processing - control.setParent(this); - - getControls().add(control); - - return control; - } - - /** - * Return the list of Controls added to the table. Note table paging control - * will not be returned in this list. - * - * @return the list of table controls - */ - public List getControls() { - if (controlList == null) { - controlList = new ArrayList(); - } - return controlList; - } - - /** - * Return true if the table has any controls defined. - * - * @return true if the table has any controls defined - */ - public boolean hasControls() { - return (controlList != null) && !controlList.isEmpty(); - } - - /** - * Return the table paging and sorting control action link. - *

- * Note you can set parameters on the returned ActionLink in order - * to preserve state when paging or sorting columns. A common use case is - * to filter out Table rows on specified criteria. See - * here for an example. - * - * @return the table paging and sorting control action link - */ - public ActionLink getControlLink() { - if (controlLink == null) { - controlLink = new ActionLink(); - } - return controlLink; - } - - /** - * Return the table row list DataProvider. - * - * @return the table row list DataProvider - */ - @SuppressWarnings("unchecked") - public DataProvider getDataProvider() { - return dataProvider; - } - - /** - * Set the table row list {@link DataProvider}. - * The Table will retrieve its data from the DataProvider on demand. - *

- * Please note: setting the dataProvider will nullify the table - * {@link #setRowList(List) rowList}. - *

- * Example usage: - * - *

-     * public CustomerPage() {
-     *     Table table = new Table("table");
-     *     table.addColumn(new Column("id"));
-     *     table.addColumn(new Column("name"));
-     *     table.addColumn(new Column("investments"));
-     *
-     *     table.setDataProvider(new DataProvider() {
-     *         public List getData() {
-     *              return getCustomerService().getCustomers();
-     *         }
-     *     });
-     * } 
- * - * For large data sets use a {@link PagingDataProvider} - * instead. - *

- * The Table methods {@link #getFirstRow()}, {@link #getLastRow()} - * and {@link #getPageSize()} provides the necessary information to limit - * the rows to the selected page. For sorting, the Table methods - * {@link #getSortedColumn()} and {@link #isSortedAscending()} provides the - * necessary information to sort the data. - *

- * Please note: when using a PagingDataProvider, you are responsible - * for sorting the data, as the Table does not have access to all the data. - *

- * Example usage: - * - *

-     * public CustomerPage() {
-     *     Table table = new Table("table");
-     *     table.setPageSize(10);
-     *     table.setShowBanner(true);
-     *     table.setSortable(true);
-     *
-     *     table.addColumn(new Column("id"));
-     *     table.addColumn(new Column("name"));
-     *     table.addColumn(new Column("investments"));
-     *
-     *     table.setDataProvider(new PagingDataProvider() {
-     *
-     *         public List getData() {
-     *             int start = table.getFirstRow();
-     *             int count = table.getPageSize();
-     *             String sortColumn = table.getSortedColumn();
-     *             boolean ascending = table.isSortedAscending();
-     *
-     *             return getCustomerService().getCustomersForPage(start, count, sortColumn, ascending);
-     *         }
-     *
-     *         public int size() {
-     *             return getCustomerService().getNumberOfCustomers();
-     *         }
-     *     });
-     * } 
- * - * @see #setRowList(List) - * - * @param dataProvider the table row list DataProvider - */ - @SuppressWarnings("unchecked") - public void setDataProvider(DataProvider dataProvider) { - this.dataProvider = dataProvider; - if (dataProvider != null) { - setRowList(null); - } - } - - /** - * Return the table HTML <td> height attribute. - * - * @return the table HTML <td> height attribute - */ - public String getHeight() { - return height; - } - /** - * Set the table HTML <td> height attribute. - * - * @param value the table HTML <td> height attribute - */ - public void setHeight(String value) { - height = value; - } - - /** - * Return true if the table row (<tr>) elements should have the - * class="hover" attribute set on JavaScript mouseover events. This class - * can be used to define mouse over :hover CSS pseudo classes to create - * table row highlight effects. - * - * @return true if table rows elements will have the class 'hover' attribute - * set on JavaScript mouseover events - */ - public boolean getHoverRows() { - return hoverRows; - } - - /** - * Set whether the table row (<tr>) elements should have the - * class="hover" attribute set on JavaScript mouseover events. This class - * can be used to define mouse over :hover CSS pseudo classes to create - * table row highlight effects. For example: - * - *
-     * hover:hover { color: navy } 
- * - * @param hoverRows specify whether class 'hover' rows attribute is rendered - * (default false). - */ - public void setHoverRows(boolean hoverRows) { - this.hoverRows = hoverRows; - } - - /** - * Return the Table HTML HEAD elements for the following resource: - * - *
    - *
  • click/table.css
  • - *
- * - * Additionally, the HEAD elements of the {@link #getControlLink()} will - * also be returned. - * - * @return the list of HEAD elements for the Table - */ - @Override - public List getHeadElements() { - Context context = getContext(); - String versionIndicator = ClickUtils.getResourceVersionIndicator(context); - - if (headElements == null) { - headElements = super.getHeadElements(); - - headElements.add(new CssImport("/click/table.css", versionIndicator)); - - headElements.addAll(getControlLink().getHeadElements()); - } - - String tableId = getId(); - CssStyle cssStyle = new CssStyle(); - cssStyle.setId(tableId + "_css_setup"); - - if (!headElements.contains(cssStyle)) { - boolean isDarkStyle = isDarkStyle(); - - String contextPath = context.getRequest().getContextPath(); - HtmlStringBuffer buffer = new HtmlStringBuffer(300); - buffer.append("th.sortable a {\n"); - buffer.append("background: url("); - buffer.append(contextPath); - buffer.append("/click/column-sortable-"); - if (isDarkStyle) { - buffer.append("dark"); - } else { - buffer.append("light"); - } - buffer.append(versionIndicator); - buffer.append(".gif)"); - buffer.append(" center right no-repeat;}\n"); - buffer.append("th.ascending a {\n"); - buffer.append("background: url("); - buffer.append(contextPath); - buffer.append("/click/column-ascending-"); - if (isDarkStyle) { - buffer.append("dark"); - } else { - buffer.append("light"); - } - buffer.append(versionIndicator); - buffer.append(".gif)"); - buffer.append(" center right no-repeat;}\n"); - buffer.append("th.descending a {\n"); - buffer.append("background: url("); - buffer.append(contextPath); - buffer.append("/click/column-descending-"); - if (isDarkStyle) { - buffer.append("dark"); - } else { - buffer.append("light"); - } - buffer.append(versionIndicator); - buffer.append(".gif)"); - buffer.append(" center right no-repeat;}"); - cssStyle.setContent(buffer.toString()); - headElements.add(cssStyle); - } - return headElements; - } - - /** - * @see Control#setName(String) - * - * @param name of the control - * @throws IllegalArgumentException if the name is null - */ - @Override - public void setName(String name) { - super.setName(name); - ActionLink localControlLink = getControlLink(); - localControlLink.setName(getName() + "-controlLink"); - localControlLink.setParent(this); - } - - /** - * Return the number of pages to display. - * - * @return the number of pages to display - */ - public int getNumberPages() { - if (getPageSize() == 0) { - return 1; - } - - int rowCount = getRowCount(); - if (rowCount == 0) { - return 1; - } - - double value = (double) rowCount / (double) getPageSize(); - - return (int) Math.ceil(value); - } - - /** - * Return the currently displayed page number. The page number is zero - * indexed, i.e. the page number of the first page is 0. - * - * @return the currently displayed page number - */ - public int getPageNumber() { - return pageNumber; - } - - /** - * Set the currently displayed page number. The page number is zero - * indexed, i.e. the page number of the first page is 0. - * - * @param pageNumber set the currently displayed page number - */ - public void setPageNumber(int pageNumber) { - this.pageNumber = pageNumber; - } - - /** - * Return the maximum page size in rows. A page size of 0 - * means there is no maximum page size. - * - * @return the maximum page size in rows - */ - public int getPageSize() { - return pageSize; - } - - /** - * Return the paginator for rendering the table pagination. - * - * @return the table paginator - */ - public Renderable getPaginator() { - if (paginator == null) { - paginator = new TablePaginator(this); - } - return paginator; - } - - /** - * Set the paginator for rendering the table pagination controls. - * - * @param value the table paginator to set - */ - public void setPaginator(Renderable value) { - paginator = value; - } - - /** - * Return the paginator attachment style. Renderable attachment style values: - * [ PAGINATOR_ATTACHED | PAGINATOR_DETACHED | PAGINATOR_INLINE ]. - * The default paginator attachment type is PAGINATOR_ATTACHED. - * - * @return the paginator attachment style - */ - public int getPaginatorAttachment() { - return paginatorAttachment; - } - - /** - * Set Table pagination attachment style. Renderable attachment style values: - * [ PAGINATOR_ATTACHED | PAGINATOR_DETACHED | PAGINATOR_INLINE ]. - * - * @param value the table pagination attachment style - */ - public void setPaginatorAttachment(int value) { - paginatorAttachment = value; - } - - /** - * Set the maximum page size in rows. A page size of 0 - * means there is no maximum page size. - * - * @param pageSize the maximum page size in rows - */ - public void setPageSize(int pageSize) { - this.pageSize = pageSize; - } - - /** - * Returns the column render id attribute status. The default value - * is false. - * - * @return the column render id attribute status, default is false - */ - public boolean getRenderId() { - return renderId; - } - - /** - * Set the column render id attribute status. - * - * @param renderId set the column render id attribute status - */ - public void setRenderId(boolean renderId) { - this.renderId = renderId; - } - - /** - * Return the list of table rows. Please note the rowList is cleared in - * table {@link #onDestroy()} method at the end of each request. - *

- * If the rowList is null, this method delegates to {@link #createRowList()} - * to create a new row list. - * - * @return the list of table rows - */ - @SuppressWarnings("unchecked") - public List getRowList() { - if (rowList == null) { - rowList = createRowList(); - } - - return rowList; - } - - /** - * Set the list of table rows. Each row can either be a value object - * (JavaBean) or an instance of a Map. - *

- * Although possible to set the table rows directly with this method, best - * practice is to use a {@link #setDataProvider(DataProvider)} - * instead. - *

- * Please note the rowList is cleared in table {@link #onDestroy()} method - * at the end of each request. - * - * @param rowList the list of table rows to set - */ - @SuppressWarnings("unchecked") - public void setRowList(List rowList) { - this.rowList = rowList; - if (this.rowList == null) { - this.rowCount = 0; - } else { - this.rowCount = this.rowList.size(); - } - } - - /** - * Return the show Table banner flag detailing number of rows and current rows - * displayed. - * - * @return the show Table banner flag - */ - public boolean getShowBanner() { - return showBanner; - } - - /** - * Set the show Table banner flag detailing number of rows and current rows - * displayed. - * - * @param showBanner the show Table banner flag - */ - public void setShowBanner(boolean showBanner) { - this.showBanner = showBanner; - } - - /** - * Return the table default column are sortable status. By default table - * columns are not sortable. - * - * @return the table default column are sortable status - */ - public boolean getSortable() { - return sortable; - } - - /** - * Set the table default column are sortable status. - * - * @param sortable the table default column are sortable status - */ - public void setSortable(boolean sortable) { - this.sortable = sortable; - } - - /** - * Return the sorted status of the table row list. - * - * @return the sorted table row list status - */ - public boolean isSorted() { - return sorted; - } - - /** - * Set the sorted status of the table row list. - * - * @param value the sorted status to set - */ - public void setSorted(boolean value) { - sorted = value; - } - - /** - * Return true if the sort order is ascending. - * - * @return true if the sort order is ascending - */ - public boolean isSortedAscending() { - return sortedAscending; - } - - /** - * Set the ascending sort order status. - * - * @param ascending the ascending sort order status - */ - public void setSortedAscending(boolean ascending) { - sortedAscending = ascending; - } - - /** - * Return the name of the sorted column, or null if not defined. - * - * @return the name of the sorted column, or null if not defined - */ - public String getSortedColumn() { - return sortedColumn; - } - - /** - * Set the name of the sorted column, or null if not defined. - * - * @param columnName the name of the sorted column - */ - public void setSortedColumn(String columnName) { - sortedColumn = columnName; - } - - /** - * Return the Table state. The following state is returned: - *

    - *
  • {@link #getPageNumber()}
  • - *
  • {@link #isSortedAscending()}
  • - *
  • {@link #getSortedColumn()}
  • - *
  • {@link #getControlLink() controlLink parameters}
  • - *
- * - * @return the Table state - */ - public Object getState() { - Object[] tableState = new Object[4]; - boolean hasState = false; - - int currentPageNumber = getPageNumber(); - if (currentPageNumber != 0) { - hasState = true; - tableState[0] = Integer.valueOf(currentPageNumber); - } - - String currentSortedColumn = getSortedColumn(); - if (currentSortedColumn != null) { - hasState = true; - tableState[1] = currentSortedColumn; - } - - boolean currentSortedAscending = isSortedAscending(); - if (!currentSortedAscending) { - hasState = true; - tableState[2] = Boolean.valueOf(currentSortedAscending); - } - - Object controlLinkState = getControlLink().getState(); - if (controlLinkState != null) { - hasState = true; - tableState[3] = controlLinkState; - } - - if (hasState) { - return tableState; - } else { - return null; - } - } - - /** - * Set the Table state. - * - * @param state the Table state to set - */ - public void setState(Object state) { - if (state == null) { - return; - } - - Object[] tableState = (Object[]) state; - - if (tableState[0] != null) { - int storedPageNumber = ((Integer) tableState[0]).intValue(); - setPageNumber(storedPageNumber); - } - - if (tableState[1] != null) { - String storedSortedColumn = (String) tableState[1]; - setSortedColumn(storedSortedColumn); - } - - if (tableState[2] != null) { - boolean storedAscending = ((Boolean) tableState[2]).booleanValue(); - setSortedAscending(storedAscending); - } - - if (tableState[3] != null) { - Object controlLinkState = tableState[3]; - getControlLink().setState(controlLinkState); - } - } - - /** - * Return the table HTML <td> width attribute. - * - * @return the table HTML <td> width attribute - */ - public String getWidth() { - return width; - } - /** - * Set the table HTML <td> width attribute. - * - * @param value the table HTML <td> width attribute - */ - public void setWidth(String value) { - width = value; - } - - // Public Methods --------------------------------------------------------- - - /** - * The total possible number of rows of the table. This value - * could be much larger than the number of entries in the {@link #rowList}, - * indicating that some rows have not been loaded yet. - *

- * This property is automatically set by the table to the appropriate value. - * - * @return the total possible number of rows of the table - */ - public final int getRowCount() { - return rowCount; - } - - /** - * Return the index of the first row to display. Index starts from 0. - *

- * Note: {@link #setPageSize(int) page size} must be set for this - * method to correctly calculate the first row, otherwise this method will - * return 0. - * - * @return the index of the first row to display - */ - public int getFirstRow() { - int firstRow = 0; - - if (getPageSize() > 0) { - if (getPageNumber() > 0) { - firstRow = getPageSize() * getPageNumber(); - } - } - - return firstRow; - } - - /** - * Return the index of the last row to display. Index starts from 0. - *

- * Note: the Table {@link #setRowList(List) row list} and - * {@link #setPageSize(int) page size} must be set for this method to - * correctly calculate the last row, otherwise this method will return 0. - * - * @return the index of the last row to display - */ - public int getLastRow() { - int numbRows = getRowCount(); - int lastRow = numbRows; - - if (getPageSize() > 0) { - lastRow = getFirstRow() + getPageSize(); - lastRow = Math.min(lastRow, numbRows); - } - return lastRow; - } - - /** - * Initialize the controls contained in the Table. - * - * @see Control#onInit() - */ - @Override - public void onInit() { - super.onInit(); - getControlLink().onInit(); - for (int i = 0, size = getControls().size(); i < size; i++) { - Control control = getControls().get(i); - control.onInit(); - } - } - - /** - * This method invokes {@link #getRowList()} to ensure exceptions thrown - * while retrieving table rows will be handled by the error page. - * - * @see Control#onRender() - */ - @Override - public void onRender() { - getControlLink().onRender(); - for (int i = 0, size = getControls().size(); i < size; i++) { - Control control = getControls().get(i); - control.onRender(); - } - getRowList(); - } - - /** - * Process any Table paging control requests, and process any added Table - * Controls. - * - * @see Control#onProcess() - * - * @return true to continue Page event processing or false otherwise - */ - @Override - public boolean onProcess() { - ActionLink localControlLink = getControlLink(); - - // Ensure parameters are defined to cater for Ajax requests that uses - // strict parameter binding - localControlLink.defineParameter(PAGE); - localControlLink.defineParameter(COLUMN); - localControlLink.defineParameter(ASCENDING); - localControlLink.defineParameter(SORT); - - localControlLink.onProcess(); - - if (localControlLink.isClicked()) { - String page = localControlLink.getParameter(PAGE); - if (NumberUtils.isNumber(page)) { - setPageNumber(Integer.parseInt(page)); - } else { - setPageNumber(0); - } - - String column = localControlLink.getParameter(COLUMN); - if (column != null) { - setSortedColumn(column); - } - - String ascending = localControlLink.getParameter(ASCENDING); - if (ascending != null) { - setSortedAscending("true".equals(ascending)); - } - - // Flip sorting order - if ("true".equals(localControlLink.getParameter(SORT))) { - setSortedAscending(!isSortedAscending()); - } - } - - boolean continueProcessing = true; - for (int i = 0, size = getControls().size(); i < size; i++) { - Control control = getControls().get(i); - continueProcessing = control.onProcess(); - if (!continueProcessing) { - continueProcessing = false; - } - } - - dispatchActionEvent(); - return continueProcessing; - } - - /** - * This method will clear the rowList, if the property - * nullifyRowListOnDestroy is true, set the sorted flag to false and - * will invoke the onDestroy() method of any child controls. - * - * @see Control#onDestroy() - */ - @Override - public void onDestroy() { - sorted = false; - - getControlLink().onDestroy(); - for (int i = 0, size = getControls().size(); i < size; i++) { - Control control = getControls().get(i); - try { - control.onDestroy(); - } catch (Throwable t) { - ClickUtils.getLogService().error("onDestroy error", t); - } - } - if (getNullifyRowListOnDestroy()) { - setRowList(null); - } - } - - /** - * @see AbstractControl#getControlSizeEst() - * - * @return the estimated rendered control size in characters - */ - @Override - public int getControlSizeEst() { - int bufferSize = 0; - if (getPageSize() > 0) { - bufferSize = (getColumnList().size() * 60) * (getPageSize() + 1) + 1792; - } else { - bufferSize = (getColumnList().size() * 60) * (getRowList().size() + 1); - } - return bufferSize; - } - - /** - * Render the HTML representation of the Table. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - - // Retrieve data to ensure rowCount has correct value - getRowList(); - - // Range sanity check. - int pageNumber = Math.min(getPageNumber(), getRowCount() - 1); - pageNumber = Math.max(pageNumber, 0); - setPageNumber(pageNumber); - - // If attached table top paginator configured render it. - if (getPaginatorAttachment() == PAGINATOR_ATTACHED) { - if (getBannerPosition() == POSITION_TOP || getBannerPosition() == POSITION_BOTH) { - renderPaginator(buffer); - if (buffer.length() > 0) { - buffer.append("\n"); - } - } - } - - // Render table start. - buffer.elementStart(getTag()); - buffer.appendAttribute("id", getId()); - - // If table class not specified, look for message property - // 'table-default-class' in the global click-page.properties messages bundle. - if (!hasAttribute("class") && getPage() != null) { - Map messages = getPage().getMessages(); - if (messages.containsKey("table-default-class")) { - String htmlClass = messages.get("table-default-class"); - setAttribute("class", htmlClass); - } - } - - appendAttributes(buffer); - - buffer.appendAttribute("height", getHeight()); - buffer.appendAttribute("width", getWidth()); - - buffer.closeTag(); - buffer.append("\n"); - - String caption = getCaption(); - if (caption != null) { - buffer.elementStart("caption"); - buffer.closeTag(); - buffer.append(caption); - buffer.elementEnd("caption"); - buffer.append("\n"); - } - // Render table header - renderHeaderRow(buffer); - - sortRowList(); - - // Render table body - renderBodyRows(buffer); - - // Render table footer - renderFooterRow(buffer); - - // Render table end. - buffer.elementEnd(getTag()); - buffer.append("\n"); - - // If attached table bottom paginator configured render it. - if (getPaginatorAttachment() == PAGINATOR_ATTACHED) { - if (getBannerPosition() == POSITION_BOTTOM || getBannerPosition() == POSITION_BOTH) { - renderPaginator(buffer); - } - } - } - - /** - * Remove the Table state from the session for the given request context. - * - * @param context the request context - * - * @see #saveState(Context) - * @see #restoreState(Context) - */ - public void removeState(Context context) { - ClickUtils.removeState(this, getName(), context); - } - - /** - * Restore the Table state from the session for the given request context. - *

- * This method delegates to {@link #setState(Object)} to set the - * table restored state. - * - * @param context the request context - * - * @see #saveState(Context) - * @see #removeState(Context) - */ - public void restoreState(Context context) { - ClickUtils.restoreState(this, getName(), context); - } - - /** - * Save the Table state to the session for the given request context. - *

- * * This method delegates to {@link #getState()} to retrieve the table state - * to save. - * - * @see #restoreState(Context) - * @see #removeState(Context) - * - * @param context the request context - */ - public void saveState(Context context) { - ClickUtils.saveState(this, getName(), context); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Create a new table row list. If a {@link #getDataProvider() dataProvider} - * is specified the new row list will be populated from the data provider. - * - * @return a new table row list - */ - @SuppressWarnings("unchecked") - protected List createRowList() { - DataProvider dp = getDataProvider(); - - List rowList = null; - - if (dp != null) { - - boolean isPaginating = false; - - if (dp instanceof PagingDataProvider) { - isPaginating = true; - } - - if (isPaginating) { - // rowCount is provided by the paging data provider. Its - // important to set the rowCount *before* invoking dp.getData - // since the getData implementation could have a dependency - // on the methods getFirstRow, getLastRow etc all of which - // depends on the rowCount value - this.rowCount = ((PagingDataProvider) dp).size(); - - // paginated datasets cannot be sorted by the table since it - // has access to only a limited number of rows. The dataset has - // to be sorted by a database or other means. Set the sorted - // property to true indicating that the data is already in a - // sorted state - setSorted(true); - } - - Iterable iterableData = dp.getData(); - - // If dataProvider returns a list, use that as the rowList - if (iterableData instanceof List) { - rowList = (List) iterableData; - - } else { - - // Create and populate the rowList from the Iterable data - rowList = new ArrayList(); - if (iterableData != null) { - for (Object row : iterableData) { - rowList.add(row); - } - } - } - - if (!isPaginating) { - // for non paginating data provider the row count equals - // the number of rows in the rowList - this.rowCount = rowList.size(); - } - } else { - // Create empty list - rowList = new ArrayList(); - this.rowCount = 0; - } - - return rowList; - } - - /** - * Render the table header row of column names. - * - * @param buffer the StringBuffer to render the header row in - */ - protected void renderHeaderRow(HtmlStringBuffer buffer) { - buffer.append("\n\n"); - - List tableColumns = getColumnList(); - for (int j = 0; j < tableColumns.size(); j++) { - Column column = tableColumns.get(j); - column.renderTableHeader(buffer, getContext()); - if (j < tableColumns.size() - 1) { - buffer.append("\n"); - } - } - - buffer.append("\n"); - } - - /** - * Render the table body rows for each of the rows in getRowList. - * - * @param buffer the StringBuffer to render the table body rows in - */ - @SuppressWarnings("unchecked") - protected void renderBodyRows(HtmlStringBuffer buffer) { - buffer.append("\n"); - - // If inline table top paginator configured render it. - if (getPaginatorAttachment() == PAGINATOR_INLINE) { - if (getBannerPosition() == POSITION_TOP || getBannerPosition() == POSITION_BOTH) { - - buffer.append("\n"); - buffer.append(""); - - renderPaginator(buffer); - - buffer.append("\n"); - } - } - - int firstRow = 0; - int lastRow = 0; - - if (getDataProvider() instanceof PagingDataProvider) { - lastRow = getRowList().size(); - } else { - firstRow = getFirstRow(); - lastRow = getLastRow(); - } - - if (lastRow == 0) { - renderBodyNoRows(buffer); - } else { - List tableRows = getRowList(); - - Map rowAttributes = new HashMap(3); - - for (int i = firstRow; i < lastRow; i++) { - Object row = getRowList().get(i); - - buffer.append("\n"); - - renderBodyRowColumns(buffer, i); - - buffer.append(""); - if (i < tableRows.size() - 1) { - buffer.append("\n"); - } - } - } - - // If inline table bottom paginator configured render it. - if (getPaginatorAttachment() == PAGINATOR_INLINE) { - if (getBannerPosition() == POSITION_BOTTOM || getBannerPosition() == POSITION_BOTH) { - - buffer.append("\n\n"); - buffer.append(""); - - renderPaginator(buffer); - - buffer.append("\n\n"); - } - } - - buffer.append(""); - } - - /** - * Override this method to set HTML attributes for each Table row. - *

- * For example: - * - *

-     * public CompanyPage extends BorderPage {
-     *
-     *     public void onInit() {
-     *         table = new Table() {
-     *             public void addRowAttributes(Map attributes, Object row, int rowIndex) {
-     *                 Customer customer = (Customer) row;
-     *                 if (customer.isDisabled()) {
-     *                     // Set the row class to disabled. CSS can then be used
-     *                     // to set disabled rows background to a different color.
-     *                     attributes.put("class", "disabled");
-     *                 }
-     *                 attributes.put("onclick", "alert('you clicked on row "
-     *                     + rowIndex + "')");
-     *             }
-     *         };
-     *     }
-     * } 
- * - * Please note that in order to enable alternate background colors - * for rows, Click will automatically add a CSS class attribute - * to each row with a value of either odd or even. You are - * free to add other CSS class attributes as illustrated in the example - * above. - * - * @param attributes the row attributes - * @param row the domain object currently being rendered - * @param rowIndex the rows index - */ - protected void addRowAttributes(Map attributes, Object row, int rowIndex) { - } - - /** - * Render the table header footer row. This method is designed to be - * overridden by Table subclasses which include a custom footer row. - *

- * By default this method does not render a table footer. - *

- * An example: - *

-     * private Table table;
-     *
-     * public void onInit() {
-     *     table = new Table("table") {
-     *         public void renderFooterRow(HtmlStringBuffer buffer) {
-     *             double totalHoldings = getCustomerService().getTotalHoldings(customers);
-     *             renderTotalHoldingsFooter(buffer);
-     *         };
-     *     }
-     *     addControl(table);
-     *     ...
-     * }
-     *
-     * ...
-     *
-     * public void renderTotalHoldingsFooter(HtmlStringBuffer buffer,) {
-     *     double total = 0;
-     *     for (int i = 0; i < table.getRowList().size(); i++) {
-     *         Customer customer = (Customer) table.getRowList().get(i);
-     *         if (customer.getHoldings() != null) {
-     *             total += customer.getHoldings().doubleValue();
-     *         }
-     *     }
-     *
-     *     String format = "<b>Total Holdings</b>:   ${0,number,#,##0.00}";
-     *     String totalDisplay = MessageFormat.format(format, new Object[] { new Double(total) });
-     *
-     *     buffer.append("<foot><tr><td colspan='4' style='text-align:right'>");
-     *     buffer.append(totalDisplay);
-     *     buffer.append("</td></tr></tfoot>");
-     * }
-     * 
- * - * @param buffer the StringBuffer to render the footer row in - */ - protected void renderFooterRow(HtmlStringBuffer buffer) { - } - - /** - * Render the current table body row cells. - * - * @param buffer the StringBuffer to render the table row cells in - * @param rowIndex the 0-based index in tableRows to render - */ - protected void renderBodyRowColumns(HtmlStringBuffer buffer, int rowIndex) { - Object row = getRowList().get(rowIndex); - - List tableColumns = getColumnList(); - - for (int j = 0; j < tableColumns.size(); j++) { - Column column = tableColumns.get(j); - column.renderTableData(row, buffer, getContext(), rowIndex); - if (j < tableColumns.size() - 1) { - buffer.append("\n"); - } - } - } - - /** - * Render the table body content if no rows are in the row list. - * - * @param buffer the StringBuffer to render the no row message to - */ - protected void renderBodyNoRows(HtmlStringBuffer buffer) { - buffer.append(""); - buffer.append(getMessage("table-no-rows-found")); - buffer.append("\n"); - } - - /** - * Render the table pagination display. - * - * @param buffer the StringBuffer to render the pagination display to - */ - protected void renderPaginator(HtmlStringBuffer buffer) { - getPaginator().render(buffer); - } - - /** - * Render the table banner detailing number of rows and number displayed. - *

- * See the /click-controls.properties for the HTML templates: - * table-page-banner and table-page-banner-nolinks - * - * @deprecated use {@link #renderPaginator(HtmlStringBuffer)} instead, this - * method is provided to support backward compatibility older Click 1.4 - * customized tables. In these scenarios please override {@link #renderPaginator(HtmlStringBuffer)} - * method to invoke {@link #renderTableBanner(HtmlStringBuffer)} and {@link #renderPagingControls(HtmlStringBuffer)}. - * - * @param buffer the StringBuffer to render the paging controls to - */ - protected void renderTableBanner(HtmlStringBuffer buffer) { - if (getShowBanner()) { - int rowCount = getRowCount(); - String rowCountStr = String.valueOf(rowCount); - - String firstRow = null; - if (getRowList().isEmpty()) { - firstRow = String.valueOf(0); - } else { - firstRow = String.valueOf(getFirstRow() + 1); - } - - String lastRow = null; - if (getRowList().isEmpty()) { - lastRow = String.valueOf(0); - } else { - lastRow = String.valueOf(getLastRow()); - } - - Object[] args = { rowCountStr, firstRow, lastRow}; - - if (getPageSize() > 0) { - buffer.append(getMessage("table-page-banner", args)); - } else { - buffer.append(getMessage("table-page-banner-nolinks", args)); - } - } - } - - /** - * Render the table paging action link controls. - *

- * See the /click-controls.properties for the HTML templates: - * table-page-links and table-page-links-nobanner - * - * @deprecated use {@link #renderPaginator(HtmlStringBuffer)} instead, this - * method is provided to support backward compatibility older Click 1.4 - * customized tables. In these scenarios please override {@link #renderPaginator(HtmlStringBuffer)} - * method to invoke {@link #renderTableBanner(HtmlStringBuffer)} and {@link #renderPagingControls(HtmlStringBuffer)}. - * - * @param buffer the StringBuffer to render the paging controls to - */ - protected void renderPagingControls(HtmlStringBuffer buffer) { - if (getPageSize() > 0) { - String firstLabel = getMessage("table-first-label"); - String firstTitle = getMessage("table-first-title"); - String previousLabel = getMessage("table-previous-label"); - String previousTitle = getMessage("table-previous-title"); - String nextLabel = getMessage("table-next-label"); - String nextTitle = getMessage("table-next-title"); - String lastLabel = getMessage("table-last-label"); - String lastTitle = getMessage("table-last-title"); - String gotoTitle = getMessage("table-goto-title"); - - AbstractLink link = getControlLink(); - if (getSortedColumn() != null) { - link.setParameter(SORT, null); - link.setParameter(COLUMN, getSortedColumn()); - link.setParameter(ASCENDING, String.valueOf(isSortedAscending())); - } else { - link.setParameter(SORT, null); - link.setParameter(COLUMN, null); - link.setParameter(ASCENDING, null); - } - - if (getPageNumber() > 0) { - link.setLabel(firstLabel); - link.setParameter(PAGE, String.valueOf(0)); - link.setAttribute("title", firstTitle); - firstLabel = link.toString(); - - link.setLabel(previousLabel); - link.setParameter(PAGE, String.valueOf(getPageNumber() - 1)); - link.setAttribute("title", previousTitle); - previousLabel = link.toString(); - } - - HtmlStringBuffer pagesBuffer = - new HtmlStringBuffer(getNumberPages() * 70); - - // Create sliding window of paging links - int lowerBound = Math.max(0, getPageNumber() - 5); - int upperBound = Math.min(lowerBound + 10, getNumberPages()); - if (upperBound - lowerBound < 10) { - lowerBound = Math.max(upperBound - 10, 0); - } - - for (int i = lowerBound; i < upperBound; i++) { - String pageNumber = String.valueOf(i + 1); - if (i == getPageNumber()) { - pagesBuffer.append("" + pageNumber + ""); - - } else { - link.setLabel(pageNumber); - link.setParameter(PAGE, String.valueOf(i)); - link.setAttribute("title", gotoTitle + " " + pageNumber); - pagesBuffer.append(link.toString()); - } - - if (i < upperBound - 1) { - pagesBuffer.append(", "); - } - } - String pageLinks = pagesBuffer.toString(); - - if (getPageNumber() < getNumberPages() - 1) { - link.setLabel(nextLabel); - link.setParameter(PAGE, String.valueOf(getPageNumber() + 1)); - link.setAttribute("title", nextTitle); - nextLabel = link.toString(); - - link.setLabel(lastLabel); - link.setParameter(PAGE, String.valueOf(getNumberPages() - 1)); - link.setAttribute("title", lastTitle); - lastLabel = link.toString(); - } - - Object[] args = - { firstLabel, previousLabel, pageLinks, nextLabel, lastLabel }; - - if (getShowBanner()) { - buffer.append(getMessage("table-page-links", args)); - } else { - buffer.append(getMessage("table-page-links-nobanner", args)); - } - } - } - - /** - * The default row list sorting method, which will sort the row list based - * on the selected column if the row list is not already sorted. - */ - @SuppressWarnings("unchecked") - protected void sortRowList() { - if (!isSorted() && StringUtils.isNotBlank(getSortedColumn())) { - - Column column = getColumns().get(getSortedColumn()); - - Collections.sort(getRowList(), column.getComparator()); - - setSorted(true); - } - } - - // Private Methods -------------------------------------------------------- - - /** - * Return true if a dark table style is selected, false otherwise. - * - * @return true if a dark table style is selected, false otherwise - */ - private boolean isDarkStyle() { - - // Flag indicating which import style to return - boolean isDarkStyle = false; - if (hasAttribute("class")) { - - String styleClasses = getAttribute("class"); - - StringTokenizer tokens = new StringTokenizer(styleClasses, " "); - while (tokens.hasMoreTokens()) { - String token = tokens.nextToken(); - if (DARK_STYLES.contains(token)) { - isDarkStyle = true; - break; - } - } - } - return isDarkStyle; - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/TablePaginator.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/TablePaginator.java deleted file mode 100644 index 96cb60105c..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/TablePaginator.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -/** - * Provides the default Table Paginator. - * - * - * - * - * - *
- * - *
- */ -public class TablePaginator implements Renderable { - - private static final long serialVersionUID = 1L; - - /** The parent table to provide paginator for. */ - protected Table table; - - // Constructors ----------------------------------------------------------- - - /** - * Create a Paginator for the given Table. - * - * @param table the paginator's table - */ - public TablePaginator(Table table) { - setTable(table); - } - - // Public Methods --------------------------------------------------------- - - /** - * Return the parent Table for this Paginator. - * - * @return the paginator's parent table - */ - public Table getTable() { - return table; - } - - /** - * Set the parent Table for this Paginator. - * - * @param table the paginator's parent table - */ - public void setTable(Table table) { - this.table = table; - } - - /** - * @see Renderable#render(HtmlStringBuffer) - * - * @param buffer the string buffer to render the paginator to - */ - public void render(HtmlStringBuffer buffer) { - final Table table = getTable(); - - if (table == null) { - throw new IllegalStateException("No parent table defined." - + " Ensure a parent Table is set using #setTable(Table)."); - } - - if (table.getShowBanner()) { - int rowCount = table.getRowCount(); - String rowCountStr = String.valueOf(rowCount); - - String firstRow = null; - if (table.getRowList().isEmpty()) { - firstRow = String.valueOf(0); - } else { - firstRow = String.valueOf(table.getFirstRow() + 1); - } - - String lastRow = null; - if (table.getRowList().isEmpty()) { - lastRow = String.valueOf(0); - } else { - lastRow = String.valueOf(table.getLastRow()); - } - - Object[] args = { rowCountStr, firstRow, lastRow}; - - if (table.getPageSize() > 0) { - buffer.append(table.getMessage("table-page-banner", args)); - } else { - buffer.append(table.getMessage("table-page-banner-nolinks", args)); - } - } - - if (table.getPageSize() > 0) { - String firstLabel = table.getMessage("table-first-label"); - String firstTitle = table.getMessage("table-first-title"); - String previousLabel = table.getMessage("table-previous-label"); - String previousTitle = table.getMessage("table-previous-title"); - String nextLabel = table.getMessage("table-next-label"); - String nextTitle = table.getMessage("table-next-title"); - String lastLabel = table.getMessage("table-last-label"); - String lastTitle = table.getMessage("table-last-title"); - String gotoTitle = table.getMessage("table-goto-title"); - - final ActionLink controlLink = table.getControlLink(); - - if (table.getSortedColumn() != null) { - controlLink.setParameter(org.apache.click.control.Table.SORT, null); - controlLink.setParameter(org.apache.click.control.Table.COLUMN, table.getSortedColumn()); - controlLink.setParameter(org.apache.click.control.Table.ASCENDING, String.valueOf(table.isSortedAscending())); - } else { - controlLink.setParameter(org.apache.click.control.Table.SORT, null); - controlLink.setParameter(org.apache.click.control.Table.COLUMN, null); - controlLink.setParameter(org.apache.click.control.Table.ASCENDING, null); - } - - if (table.getPageNumber() > 0) { - controlLink.setLabel(firstLabel); - controlLink.setParameter(org.apache.click.control.Table.PAGE, String.valueOf(0)); - controlLink.setTitle(firstTitle); - firstLabel = controlLink.toString(); - - controlLink.setLabel(previousLabel); - controlLink.setParameter(org.apache.click.control.Table.PAGE, String.valueOf(table.getPageNumber() - 1)); - controlLink.setTitle(previousTitle); - previousLabel = controlLink.toString(); - } - - HtmlStringBuffer pagesBuffer = - new HtmlStringBuffer(table.getNumberPages() * 70); - - // Create sliding window of paging links - int lowerBound = Math.max(0, table.getPageNumber() - 5); - int upperBound = Math.min(lowerBound + 10, table.getNumberPages()); - if (upperBound - lowerBound < 10) { - lowerBound = Math.max(upperBound - 10, 0); - } - - for (int i = lowerBound; i < upperBound; i++) { - String pageNumber = String.valueOf(i + 1); - if (i == table.getPageNumber()) { - pagesBuffer.append("" + pageNumber + ""); - - } else { - controlLink.setLabel(pageNumber); - controlLink.setParameter(org.apache.click.control.Table.PAGE, String.valueOf(i)); - controlLink.setTitle(gotoTitle + " " + pageNumber); - controlLink.render(pagesBuffer); - } - - if (i < upperBound - 1) { - pagesBuffer.append(", "); - } - } - String pageLinks = pagesBuffer.toString(); - - if (table.getPageNumber() < table.getNumberPages() - 1) { - controlLink.setLabel(nextLabel); - controlLink.setParameter(org.apache.click.control.Table.PAGE, String.valueOf(table.getPageNumber() + 1)); - controlLink.setTitle(nextTitle); - nextLabel = controlLink.toString(); - - controlLink.setLabel(lastLabel); - controlLink.setParameter(Table.PAGE, String.valueOf(table.getNumberPages() - 1)); - controlLink.setTitle(lastTitle); - lastLabel = controlLink.toString(); - } - - Object[] args = - { firstLabel, previousLabel, pageLinks, nextLabel, lastLabel }; - - if (table.getShowBanner()) { - buffer.append(table.getMessage("table-page-links", args)); - } else { - buffer.append(table.getMessage("table-page-links-nobanner", args)); - } - controlLink.setTitle(null); - } - } - - /** - * Returns the HTML representation of this paginator. - *

- * This method delegates the rendering to the method - * {@link #render(HtmlStringBuffer)}. - * - * @see Object#toString() - * - * @return the HTML representation of this paginator - */ - @Override - public String toString() { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - - render(buffer); - - return buffer.toString(); - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/TextArea.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/TextArea.java deleted file mode 100644 index d6719f7b58..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/TextArea.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -import java.text.MessageFormat; - -/** - * Provides a TextArea control:   <textarea></textarea>. - * - * - * - * - * - * - *
Text Area
- * - *

TextArea Example

- * - * The example below shows how to a TextArea to a Form: - * - *
- * TextArea commentsField = new TextArea("comments");
- * commentsField.setCols(40);
- * commentsField.setRows(6);
- * form.add(commentsField); 
- * - * The TextArea control will rendered HTML output: - *
- * <textarea name="comments" rows="6" cols="40"/></textarea> 
- * - * See also the W3C HTML reference: - * TEXTAREA - */ -public class TextArea extends Field { - - private static final long serialVersionUID = 1L; - - // -------------------------------------------------------------- Constants - - /** - * The field validation JavaScript function template. - * The function template arguments are:
    - *
  • 0 - is the field id
  • - *
  • 1 - is the Field required status
  • - *
  • 2 - is the minimum length
  • - *
  • 3 - is the maximum length
  • - *
  • 4 - is the localized error message for required validation
  • - *
  • 5 - is the localized error message for minimum length validation
  • - *
  • 6 - is the localized error message for maximum length validation
  • - *
- */ - protected final static String VALIDATE_TEXTAREA_FUNCTION = - "function validate_{0}() '{'\n" - + " var msg = validateTextField(\n" - + " ''{0}'',{1}, {2}, {3}, [''{4}'',''{5}'',''{6}'']);\n" - + " if (msg) '{'\n" - + " return msg + ''|{0}'';\n" - + " '}' else '{'\n" - + " return null;\n" - + " '}'\n" - + "'}'\n"; - - // ----------------------------------------------------- Instance Variables - - /** - * The number of text area columns. The default number of columns is twenty. - */ - protected int cols = 20; - - /** - * The maximum field length validation constraint. If the value is zero this - * validation constraint is not applied. The default value is zero. - */ - protected int maxLength = 0; - - /** - * The minimum field length validation constraint. If the valid is zero this - * validation constraint is not applied. The default value is zero. - */ - protected int minLength = 0; - - /** The number of text area rows. The default number of rows is three. */ - protected int rows = 3; - - // ----------------------------------------------------------- Constructors - - /** - * Construct the TextArea with the given name. The area will have a - * default size of 20 cols and 3 rows. - * - * @param name the name of the field - */ - public TextArea(String name) { - super(name); - } - - - /** - * Construct the TextArea with the given name and label. The area will have - * a default size of 20 cols and 3 rows. - * - * @param name the name of the field - * @param label the label of the field - */ - public TextArea(String name, String label) { - super(name, label); - } - - /** - * Construct the TextArea with the given name and required status. The - * area will have a default size of 20 cols and 3 rows. - * - * @param name the name of the field - * @param required the field required status - */ - public TextArea(String name, boolean required) { - super(name); - setRequired(required); - } - - /** - * Construct the TextArea with the given name, label and required status. - * The area will have a default size of 20 cols and 3 rows. - * - * @param name the name of the field - * @param label the label of the field - * @param required the field required status - */ - public TextArea(String name, String label, boolean required) { - super(name, label); - setRequired(required); - } - - /** - * Construct the TextArea with the given name, number of columns and - * number of rows. - * - * @param name the name of the field - * @param cols the number of text area cols - * @param rows the number of text area rows - */ - public TextArea(String name, int cols, int rows) { - super(name); - setCols(cols); - setRows(rows); - } - - /** - * Construct the TextArea with the given name, label, number of columns and - * number of rows. - * - * @param name the name of the field - * @param label the label of the field - * @param cols the number of text area cols - * @param rows the number of text area rows - */ - public TextArea(String name, String label, int cols, int rows) { - super(name, label); - setCols(cols); - setRows(rows); - } - - /** - * Construct the TextArea with the given name, label, number of columns and - * number of rows. - * - * @param name the name of the field - * @param label the label of the field - * @param cols the number of text area cols - * @param rows the number of text area rows - * @param required the field required status - */ - public TextArea(String name, String label, int cols, int rows, - boolean required) { - super(name, label); - setCols(cols); - setRows(rows); - setRequired(required); - } - - /** - * Create a TextArea with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public TextArea() { - super(); - } - - // ------------------------------------------------------- Public Attributes - - /** - * Return the textarea's html tag: textarea. - * - * @see org.apache.click.control.AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "textarea"; - } - - /** - * Return the number of text area columns. - * - * @return the number of text area columns - */ - public int getCols() { - return cols; - } - - /** - * Set the number of text area columns. The default number of columns is 20. - * - * @param cols set the number of text area columns. - */ - public void setCols(int cols) { - this.cols = cols; - } - - /** - * Returns the maximum field length validation constraint. If the - * {@link #maxLength} property is greater than zero, the Field values length - * will be validated against this constraint when processed. - * - * @return the maximum field length validation constraint - */ - public int getMaxLength() { - return maxLength; - } - - /** - * Sets the maximum field length. If the {@link #maxLength} property is - * greater than zero, the Field values length will be validated against - * this constraint when processed. - * - * @param maxLength the maximum field length validation constraint - */ - public void setMaxLength(int maxLength) { - this.maxLength = maxLength; - } - - /** - * Returns the minimum field length validation constraint. If the - * {@link #minLength} property is greater than zero, the Field values length - * will be validated against this constraint when processed. - * - * @return the minimum field length validation constraint - */ - public int getMinLength() { - return minLength; - } - - /** - * Sets the minimum field length validation constraint. If the - * {@link #minLength} property is greater than zero, the Field values length - * will be validated against this constraint when processed. - * - * @param minLength the minimum field length validation constraint - */ - public void setMinLength(int minLength) { - this.minLength = minLength; - } - - /** - * Return the number of text area rows. - * - * @return the number of text area rows - */ - public int getRows() { - return rows; - } - - /** - * Set the number of text area rows. The default number of rows is 3. - * - * @param rows set the number of text area rows - */ - public void setRows(int rows) { - this.rows = rows; - } - - // --------------------------------------------------------- Public Methods - - /** - * @see AbstractControl#getControlSizeEst() - * - * @return the estimated rendered control size in characters - */ - @Override - public int getControlSizeEst() { - return 96; - } - - /** - * Render the HTML representation of the TextArea. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - - buffer.elementStart(getTag()); - - buffer.appendAttribute("name", getName()); - buffer.appendAttribute("id", getId()); - buffer.appendAttribute("rows", getRows()); - buffer.appendAttribute("cols", getCols()); - buffer.appendAttribute("title", getTitle()); - if (isValid()) { - removeStyleClass("error"); - if (isDisabled()) { - addStyleClass("disabled"); - } else { - removeStyleClass("disabled"); - } - } else { - addStyleClass("error"); - } - if (getTabIndex() > 0) { - buffer.appendAttribute("tabindex", getTabIndex()); - } - - appendAttributes(buffer); - - if (isDisabled()) { - buffer.appendAttributeDisabled(); - } - if (isReadonly()) { - buffer.appendAttributeReadonly(); - } - - buffer.closeTag(); - - buffer.appendEscaped(getValue()); - - buffer.elementEnd(getTag()); - - if (getHelp() != null) { - buffer.append(getHelp()); - } - } - - /** - * Validate the TextArea request submission. - *

- * A field error message is displayed if a validation error occurs. - * These messages are defined in the resource bundle:

- *
org.apache.click.control.MessageProperties
- *

- * Error message bundle key names include:

    - *
  • field-maxlength-error
  • - *
  • field-minlength-error
  • - *
  • field-required-error
  • - *
- */ - @Override - public void validate() { - setError(null); - - String value = getValue(); - - int length = value.length(); - if (length > 0) { - if (getMinLength() > 0 && length < getMinLength()) { - setErrorMessage("field-minlength-error", getMinLength()); - return; - } - - if (getMaxLength() > 0 && length > getMaxLength()) { - setErrorMessage("field-maxlength-error", getMaxLength()); - return; - } - - } else { - if (isRequired()) { - setErrorMessage("field-required-error"); - } - } - } - - - /** - * Return the field JavaScript client side validation function. - *

- * The function name must follow the format validate_[id], where - * the id is the DOM element id of the fields focusable HTML element, to - * ensure the function has a unique name. - * - * @return the field JavaScript client side validation function - */ - @Override - public String getValidationJavaScript() { - Object[] args = new Object[7]; - args[0] = getId(); - args[1] = String.valueOf(isRequired()); - args[2] = String.valueOf(getMinLength()); - args[3] = String.valueOf(getMaxLength()); - args[4] = getMessage("field-required-error", getErrorLabel()); - args[5] = getMessage("field-minlength-error", - getErrorLabel(), String.valueOf(getMinLength())); - args[6] = getMessage("field-maxlength-error", - getErrorLabel(), String.valueOf(getMaxLength())); - return MessageFormat.format(VALIDATE_TEXTAREA_FUNCTION, args); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/TextField.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/control/TextField.java deleted file mode 100644 index 31cacfb082..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/control/TextField.java +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.control; - -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -import java.text.MessageFormat; - -/** - * Provides a Text Field control:   <input type='text'>. - * - * - * - * - * - * - *
Text Field
- * - *

TextField Example

- * - * The example below shows how to a TextField to a Form, and how it will be - * rendered as HTML. - * - *
- * TextField usernameField = new TextField("username");
- * usernameField.setRequired(true);
- * usernameField.setSize(12);
- * usernameField.setMaxLength(12);
- * usernameField.setMinLength(6);
- * form.add(usernameField); 
- * - * The TextField control will rendered HTML output: - * - *
- * <input type='text' name='username' value='' size='12' maxlength='12'> 
- * - * For another example using TextField see the {@link org.apache.click.control.Form} - * Javadoc example. - *

- * See also the W3C HTML reference: - * INPUT - */ -public class TextField extends Field { - - // -------------------------------------------------------------- Constants - - private static final long serialVersionUID = 1L; - - /** - * The field validation JavaScript function template. - * The function template arguments are:

    - *
  • 0 - is the field id
  • - *
  • 1 - is the Field required status
  • - *
  • 2 - is the minimum length
  • - *
  • 3 - is the maximum length
  • - *
  • 4 - is the localized error message for required validation
  • - *
  • 5 - is the localized error message for minimum length validation
  • - *
  • 6 - is the localized error message for maximum length validation
  • - *
- */ - protected final static String VALIDATE_TEXTFIELD_FUNCTION = - "function validate_{0}() '{'\n" - + " var msg = validateTextField(\n" - + " ''{0}'',{1}, {2}, {3}, [''{4}'',''{5}'',''{6}'']);\n" - + " if (msg) '{'\n" - + " return msg + ''|{0}'';\n" - + " '}' else '{'\n" - + " return null;\n" - + " '}'\n" - + "'}'\n"; - - // ----------------------------------------------------- Instance Variables - - /** - * The maximum field length validation constraint. If the value is zero this - * validation constraint is not applied. The default value is zero. - *

- * If maxLength is greater than zero, then maxLength is rendered as the - * HTML attribute 'maxlength'. - */ - protected int maxLength = 0; - - /** - * The minimum field length validation constraint. If the valid is zero this - * validation constraint is not applied. The default value is zero. - */ - protected int minLength = 0; - - /** The text field size attribute. The default size is 20. */ - protected int size = 20; - - // ----------------------------------------------------------- Constructors - - /** - * Construct the TextField with the given name. The default text field size - * is 20 characters. - * - * @param name the name of the field - */ - public TextField(String name) { - super(name); - } - - /** - * Construct the TextField with the given name and required status. - * The default text field size is 20 characters. - * - * @param name the name of the field - * @param required the field required status - */ - public TextField(String name, boolean required) { - super(name); - setRequired(required); - } - - /** - * Construct the TextField with the given name and label. The default text - * field size is 20 characters. - * - * @param name the name of the field - * @param label the label of the field - */ - public TextField(String name, String label) { - super(name, label); - } - - /** - * Construct the TextField with the given name, label and required status. - * The default text field size is 20 characters. - * - * @param name the name of the field - * @param label the label of the field - * @param required the field required status - */ - public TextField(String name, String label, boolean required) { - super(name, label); - setRequired(required); - } - - /** - * Construct the TextField with the given name, label and size. - * - * @param name the name of the field - * @param label the label of the field - * @param size the size of the field - */ - public TextField(String name, String label, int size) { - super(name, label); - setSize(size); - } - - /** - * Construct the TextField with the given name, label, size and required - * status. - * - * @param name the name of the field - * @param label the label of the field - * @param size the size of the field - * @param required the field required status - */ - public TextField(String name, String label, int size, boolean required) { - super(name, label); - setSize(size); - setRequired(required); - } - - /** - * Create a TextField with no name defined. - *

- * Please note the control's name must be defined before it is valid. - */ - public TextField() { - } - - // ------------------------------------------------------ Public Attributes - - /** - * Return the textfield's html tag: input. - * - * @see org.apache.click.control.AbstractControl#getTag() - * - * @return this controls html tag - */ - @Override - public String getTag() { - return "input"; - } - - /** - * Returns the maximum field length validation constraint. If the - * {@link #maxLength} property is greater than zero, the Field values length - * will be validated against this constraint when processed. - *

- * If maxLength is greater than zero, it is rendered as the field - * attribute 'maxlength' - * - * @return the maximum field length validation constraint - */ - public int getMaxLength() { - return maxLength; - } - - /** - * Sets the maximum field length. If the {@link #maxLength} property is - * greater than zero, the Field values length will be validated against - * this constraint when processed. - *

- * If maxLength is greater than zero, it is rendered as the field - * attribute 'maxlength' - * - * @param maxLength the maximum field length validation constraint - */ - public void setMaxLength(int maxLength) { - this.maxLength = maxLength; - } - - /** - * Returns the minimum field length validation constraint. If the - * {@link #minLength} property is greater than zero, the Field values length - * will be validated against this constraint when processed. - * - * @return the minimum field length validation constraint - */ - public int getMinLength() { - return minLength; - } - - /** - * Sets the minimum field length validation constraint. If the - * {@link #minLength} property is greater than zero, the Field values length - * will be validated against this constraint when processed. - * - * @param minLength the minimum field length validation constraint - */ - public void setMinLength(int minLength) { - this.minLength = minLength; - } - - /** - * Return the field size. - * - * @return the field size - */ - public int getSize() { - return size; - } - - /** - * Set the field size. - * - * @param size the field size - */ - public void setSize(int size) { - this.size = size; - } - - /** - * Return the input type: 'text'. - * - * @return the input type: 'text' - */ - public String getType() { - return "text"; - } - - // --------------------------------------------------------- Public Methods - - /** - * @see AbstractControl#getControlSizeEst() - * - * @return the estimated rendered control size in characters - */ - @Override - public int getControlSizeEst() { - return 96; - } - - /** - * Render the HTML representation of the TextField. - * - * @see #toString() - * - * @param buffer the specified buffer to render the control's output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - - buffer.elementStart(getTag()); - - buffer.appendAttribute("type", getType()); - buffer.appendAttribute("name", getName()); - buffer.appendAttribute("id", getId()); - buffer.appendAttributeEscaped("value", getValue()); - buffer.appendAttribute("size", getSize()); - buffer.appendAttribute("title", getTitle()); - if (getTabIndex() > 0) { - buffer.appendAttribute("tabindex", getTabIndex()); - } - if (getMaxLength() > 0) { - buffer.appendAttribute("maxlength", getMaxLength()); - } - - if (isValid()) { - removeStyleClass("error"); - if (isDisabled()) { - addStyleClass("disabled"); - } else { - removeStyleClass("disabled"); - } - } else { - addStyleClass("error"); - } - - appendAttributes(buffer); - - if (isDisabled()) { - buffer.appendAttributeDisabled(); - } - if (isReadonly()) { - buffer.appendAttributeReadonly(); - } - - buffer.elementEnd(); - - if (getHelp() != null) { - buffer.append(getHelp()); - } - } - - /** - * Validate the TextField request submission. - *

- * A field error message is displayed if a validation error occurs. - * These messages are defined in the resource bundle:

- *
org.apache.click.control.MessageProperties
- *

- * Error message bundle key names include:

    - *
  • field-maxlength-error
  • - *
  • field-minlength-error
  • - *
  • field-required-error
  • - *
- */ - @Override - public void validate() { - setError(null); - - String value = getValue(); - - int length = value.length(); - if (length > 0) { - if (getMinLength() > 0 && length < getMinLength()) { - setErrorMessage("field-minlength-error", getMinLength()); - return; - } - - if (getMaxLength() > 0 && length > getMaxLength()) { - setErrorMessage("field-maxlength-error", getMaxLength()); - return; - } - - } else { - if (isRequired()) { - setErrorMessage("field-required-error"); - } - } - } - - /** - * Return the field JavaScript client side validation function. - *

- * The function name must follow the format validate_[id], where - * the id is the DOM element id of the fields focusable HTML element, to - * ensure the function has a unique name. - * - * @return the field JavaScript client side validation function - */ - @Override - public String getValidationJavaScript() { - Object[] args = new Object[7]; - args[0] = getId(); - args[1] = String.valueOf(isRequired()); - args[2] = String.valueOf(getMinLength()); - args[3] = String.valueOf(getMaxLength()); - args[4] = getMessage("field-required-error", getErrorLabel()); - args[5] = getMessage("field-minlength-error", - getErrorLabel(), String.valueOf(getMinLength())); - args[6] = getMessage("field-maxlength-error", - getErrorLabel(), String.valueOf(getMaxLength())); - return MessageFormat.format(VALIDATE_TEXTFIELD_FUNCTION, args); - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/CssImport.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/element/CssImport.java deleted file mode 100644 index 46f75caefd..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/CssImport.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.element; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.lang.builder.HashCodeBuilder; - -import java.util.Map; - -/** - * Provides a Css HEAD element for importing external Cascading - * Stylesheet files using the <link> tag. - *

- * Example usage: - *

- * public class MyPage extends Page {
- *
- *     public List getHeadElements() {
- *         // We use lazy loading to ensure the CSS import is only added the
- *         // first time this method is called.
- *         if (headElements == null) {
- *             // Get the head elements from the super implementation
- *             headElements = super.getHeadElements();
- *
- *             CssImport cssImport = new CssImport("/css/style.css");
- *             headElements.add(cssImport);
- *         }
- *         return headElements;
- *     }
- * } 
- * - * The cssImport instance will be rendered as follows (assuming the - * context path is myApp): - *
- * <link type="text/css" rel="stylesheet" href="/myApp/css/style.css"/> 
- */ -public class CssImport extends ResourceElement { - - private static final long serialVersionUID = 1L; - - // Constructors ----------------------------------------------------------- - - /** - * Constructs a new Css import element. - *

- * The CssImport {@link #setVersionIndicator(String) version indicator} - * will automatically be set to the - * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}. - */ - public CssImport() { - this(null); - } - - /** - * Construct a new Css import element with the specified href - * attribute. - *

- * The CssImport {@link #setVersionIndicator(String) version indicator} - * will automatically be set to the - * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}. - *

- * Please note if the given href begins with a - * "/" character the href will be prefixed with the web - * application context path. - * - * @param href the Css import href attribute - */ - public CssImport(String href) { - this(href, true); - } - - /** - * Construct a new Css import element with the specified href - * attribute. - *

- * If useApplicationVersionIndicator is true the - * CssImport {@link #setVersionIndicator(String) version indicator} - * will automatically be set to the - * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}. - *

- * Please note if the given href begins with a - * "/" character the href will be prefixed with the web - * application context path. - * - * @param href the Css import href attribute - * @param useApplicationVersionIndicator indicates whether the version - * indicator will automatically be set to the application version indicator - */ - public CssImport(String href, boolean useApplicationVersionIndicator) { - this(href, null); - if (useApplicationVersionIndicator) { - setVersionIndicator(ClickUtils.getApplicationResourceVersionIndicator()); - } - } - - /** - * Construct a new Css import element with the specified href - * attribute and version indicator. - *

- * Please note if the given href begins with a - * "/" character the href will be prefixed with the web - * application context path. - * - * @param href the Css import href attribute - * @param versionIndicator the version indicator to add to the href path - */ - public CssImport(String href, String versionIndicator) { - setHref(href); - setAttribute("type", "text/css"); - setAttribute("rel", "stylesheet"); - setVersionIndicator(versionIndicator); - } - - // Public Properties ------------------------------------------------------ - - /** - * Returns the Css import HTML tag: <link>. - * - * @return the Css import HTML tag: <link> - */ - @Override - public String getTag() { - return "link"; - } - - /** - * This method always return true because Css import must be unique based on - * its href attribute. In other words the Page HEAD should only - * contain a single CSS import for the specific href. - * - * @see ResourceElement#isUnique() - * - * @return true because Css import must unique based on its href - * attribute - */ - @Override - public boolean isUnique() { - return true; - } - - /** - * Sets the href attribute. If the given href argument is - * null, the href attribute will be removed. - *

- * If the given href begins with a "/" character - * the href will be prefixed with the web applications context path. - * Note if the given href is already prefixed with the context path, - * Click won't add it a second time. - * - * @param href the new href attribute - */ - public void setHref(String href) { - if (href != null) { - if (href.charAt(0) == '/') { - Context context = getContext(); - String contextPath = context.getRequest().getContextPath(); - - // Guard against adding duplicate context path - if (!href.startsWith(contextPath + '/')) { - HtmlStringBuffer buffer = - new HtmlStringBuffer(contextPath.length() + href.length()); - - // Append the context path - buffer.append(contextPath); - buffer.append(href); - href = buffer.toString(); - } - } - } - setAttribute("href", href); - } - - /** - * Return the href attribute. - * - * @return the href attribute - */ - public String getHref() { - return getAttribute("href"); - } - - // Public Methods --------------------------------------------------------- - - /** - * Render the HTML representation of the CssImport element to the specified - * buffer. - * - * @param buffer the buffer to render output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - renderConditionalCommentPrefix(buffer); - - buffer.elementStart(getTag()); - - if (isRenderId()) { - buffer.appendAttribute("id", getId()); - } - - String href = getHref(); - renderResourcePath(buffer, "href", href); - - Map localAttributes = getAttributes(); - for (Map.Entry entry : localAttributes.entrySet()) { - String name = entry.getKey(); - if (!name.equals("id") && !name.equals("href")) { - buffer.appendAttributeEscaped(name, entry.getValue()); - } - } - - buffer.elementEnd(); - - renderConditionalCommentSuffix(buffer); - } - - /** - * @see Object#equals(Object) - * - * @param o the object with which to compare this instance with - * @return true if the specified object is the same as this object - */ - @Override - public boolean equals(Object o) { - if (getHref() == null) { - throw new IllegalStateException("'href' attribute is not defined."); - } - - //1. Use the == operator to check if the argument is a reference to this object. - if (o == this) { - return true; - } - - //2. Use the instanceof operator to check if the argument is of the correct type. - if (!(o instanceof CssImport)) { - return false; - } - - //3. Cast the argument to the correct type. - CssImport that = (CssImport) o; - - return getHref() == null ? that.getHref() == null - : getHref().equals(that.getHref()); - } - - /** - * @see Object#hashCode() - * - * @return a hash code value for this object - */ - @Override - public int hashCode() { - return new HashCodeBuilder(17, 37).append(getHref()).toHashCode(); - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/CssStyle.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/element/CssStyle.java deleted file mode 100644 index 6446e0d406..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/CssStyle.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.element; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.lang.builder.HashCodeBuilder; - -import java.util.HashMap; -import java.util.Map; - -/** - * Provides a Css HEAD element for including inline Cascading - * Stylesheets using the <style> tag. - *

- * Example usage: - * - *

- * public class MyPage extends Page {
- *
- *     public List getHeadElements() {
- *         // We use lazy loading to ensure the CSS import is only added the
- *         // first time this method is called.
- *         if (headElements == null) {
- *             // Get the header entries from the super implementation
- *             headElements = super.getHeadElements();
- *
- *             CssStyle cssStyle = new CssStyle("body { font: 12px arial; }");
- *             headElements.add(cssStyle);
- *         }
- *         return headElements;
- *     }
- * } 
- * - * The cssStyle instance will render as follows: - * - *
- * <style type="text/css">
- * body { font: 12px arial; }
- * </style> 
- * - * Below is an example showing how to render inline CSS from a Velocity - * template. - *

- * First we create a Velocity template (/css/style-template.css) which - * contains the variable $context that must be replaced at runtime with - * the application context path: - * - *

- * .blue {
- *     background: #00ff00 url('$context/css/blue.png') no-repeat fixed center;
- * } 
- * - * Next is the Page implementation: - * - *
- * public class MyPage extends Page {
- *
- *     public List getHeadElements() {
- *         // We use lazy loading to ensure the CSS is only added the first time
- *         // this method is called.
- *         if (headElements == null) {
- *             // Get the head elements from the super implementation
- *             headElements = super.getHeadElements();
- *
- *             Context context = getContext();
- *
- *             // Create a default template model to pass to the template
- *             Map model = ClickUtils.createTemplateModel(this, context);
- *
- *             // Specify the path to CSS template
- *             String templatePath = "/css/style-template.css";
- *
- *             // Create the inline Css for the given template path and model
- *             CssStyle cssStyle = new CssStyle(templatePath, model);
- *             headElements.add(cssStyle);
- *         }
- *         return headElements;
- *     }
- * } 
- * - * The Css above will render as follows (assuming the context path is - * myApp): - * - *
- * <style type="text/css">
- * .blue {
- *     background: #00ff00 url('/myApp/css/blue.png') no-repeat fixed center;
- * }
- * </style> 
- * - *

Character data (CDATA) support

- * - * Sometimes it is necessary to wrap inline {@link CssStyle Css} in - * CDATA tags. Two use cases are common for doing this: - *
    - *
  • For XML parsing: When using Ajax one often send back partial - * XML snippets to the browser, which is parsed as valid XML. However the XML - * parser will throw an error if the content contains reserved XML characters - * such as '&', '<' and '>'. For these situations it is recommended - * to wrap the style content inside CDATA tags. - *
  • - *
  • XHTML validation: if you want to validate your site using an XHTML - * validator e.g: http://validator.w3.org/.
  • - *
- * - * To wrap the CSS Style content in CDATA tags, set - * {@link #setCharacterData(boolean)} to true. Below is shown how the Css - * content would be rendered: - * - *
- * <style type="text/css">
- *  /∗<![CDATA[∗/
- *
- *  div > p {
- *    border: 1px solid black;
- *  }
- *
- *  /∗]]>∗/
- * </style> 
- * - * Notice the CDATA tags are commented out which ensures older browsers that - * don't understand the CDATA tag, will ignore it and only process the actual - * content. - *

- * For an overview of XHTML validation and CDATA tags please see - * http://javascript.about.com/library/blxhtml.htm. - */ -public class CssStyle extends ResourceElement { - - private static final long serialVersionUID = 1L; - - // Variables ------------------------------------------------------------- - - /** The inline Css content. */ - private String content; - - /** - * Indicates if the HeadElement's content should be wrapped in a CDATA tag. - */ - private boolean characterData; - - /** The path of the template to render. */ - private String template; - - /** The model of the template to render. */ - private Map model; - - // Constructor ------------------------------------------------------------ - - /** - * Construct a new Css style element. - */ - public CssStyle() { - this(null); - } - - /** - * Construct a new Css style element with the given content. - * - * @param content the Css content - */ - public CssStyle(String content) { - if (content != null) { - this.content = content; - } - setAttribute("type", "text/css"); - } - - /** - * Construct a new Css style element for the given template path - * and template model. - *

- * When the CssStyle is rendered the template and model will be merged and - * the result will be rendered together with any CssStyle - * {@link #setContent(String) content}. - *

- * - * For example: - *

-     * public class MyPage extends Page {
-     *     public void onInit() {
-     *         Context context = getContext();
-     *
-     *         // Create a default template model
-     *         Map model = ClickUtils.createTemplateModel(this, context);
-     *
-     *         // Create CssStyle for the given template path and model
-     *         CssStyle style = new CssStyle("/mypage-template.css", model);
-     *
-     *         // Add style to the Page Head elements
-     *         getHeadElements().add(style);
-     *     }
-     * } 
- * - * @param template the path of the template to render - * @param model the template model - */ - public CssStyle(String template, Map model) { - this(null); - setTemplate(template); - setModel(model); - } - - // Public Properties ------------------------------------------------------ - - /** - * Returns the Css HTML tag: <style>. - * - * @return the Css HTML tag: <style> - */ - @Override - public String getTag() { - return "style"; - } - - /** - * Return the CssStyle content. - * - * @return the CssStyle content - */ - public String getContent() { - return content; - } - - /** - * Set the CssStyle content. - * - * @param content the CssStyle content - */ - public void setContent(String content) { - this.content = content; - } - - /** - * Return true if the CssStyle's content should be wrapped in CDATA tags, - * false otherwise. - * - * @return true if the CssStyle's content should be wrapped in CDATA tags, - * false otherwise - */ - public boolean isCharacterData() { - return characterData; - } - - /** - * Sets whether the CssStyle's content should be wrapped in CDATA tags or - * not. - * - * @param characterData true indicates that the CssStyle's content should be - * wrapped in CDATA tags, false otherwise - */ - public void setCharacterData(boolean characterData) { - this.characterData = characterData; - } - - /** - * Return the path of the template to render. - * - * @see #setTemplate(String) - * - * @return the path of the template to render - */ - public String getTemplate() { - return template; - } - - /** - * Set the path of the template to render. - *

- * If the {@link #template} property is set, the template and {@link #model} - * will be merged and the result will be rendered together with any CssStyle - * {@link #setContent(String) content}. - * - * @param template the path of the template to render - */ - public void setTemplate(String template) { - this.template = template; - } - - /** - * Return the model of the {@link #setTemplate(String) template} - * to render. - * - * @see #setModel(Map) - * - * @return the model of the template to render - */ - public Map getModel() { - return model; - } - - /** - * Set the model of the template to render. - *

- * If the {@link #template} property is set, the template and {@link #model} - * will be merged and the result will be rendered together with any CssStyle - * {@link #setContent(String) content}. - * - * @param model the model of the template to render - */ - public void setModel(Map model) { - this.model = model; - } - - // Public Methods --------------------------------------------------------- - - /** - * Render the HTML representation of the CssStyle element to the specified - * buffer. - * - * @param buffer the buffer to render output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - - // Render IE conditional comment if conditional comment was set - renderConditionalCommentPrefix(buffer); - - buffer.elementStart(getTag()); - - if (isRenderId()) { - buffer.appendAttribute("id", getId()); - } - - appendAttributes(buffer); - - buffer.closeTag(); - - buffer.append("\n"); - - // Render CDATA tag if necessary - renderCharacterDataPrefix(buffer); - - renderContent(buffer); - - renderCharacterDataSuffix(buffer); - - buffer.append("\n"); - - buffer.elementEnd(getTag()); - - renderConditionalCommentSuffix(buffer); - } - - /** - * @see Object#equals(Object) - * - * @param o the object with which to compare this instance with - * - * @return true if the specified object is the same as this object - */ - @Override - public boolean equals(Object o) { - if (!isUnique()) { - return super.equals(o); - } - - //1. Use the == operator to check if the argument is a reference to this object. - if (o == this) { - return true; - } - - //2. Use the instanceof operator to check if the argument is of the correct type. - if (!(o instanceof CssStyle)) { - return false; - } - - //3. Cast the argument to the correct type. - CssStyle that = (CssStyle) o; - - String id = getId(); - String thatId = that.getId(); - return id == null ? thatId == null : id.equals(thatId); - } - - /** - * @see Object#hashCode() - * - * @return a hash code value for this object - */ - @Override - public int hashCode() { - if (!isUnique()) { - return super.hashCode(); - } - return new HashCodeBuilder(17, 37).append(getId()).toHashCode(); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Render the CssStyle {@link #setContent(String) content} - * to the specified buffer. - *

- * Please note: if the {@link #setTemplate(String) template} - * property is set, this method will merge the {@link #setTemplate(String) template} - * and {@link #setModel(Map) model} and the result will be - * rendered, together with the CssStyle - * {@link #setContent(String) content}, - * to the specified buffer. - * - * @param buffer the buffer to append the output to - */ - protected void renderContent(HtmlStringBuffer buffer) { - if (getTemplate() != null) { - Context context = getContext(); - - Map templateModel = getModel(); - if (templateModel == null) { - templateModel = new HashMap(); - } - buffer.append(context.renderTemplate(getTemplate(), templateModel)); - - } - - if (getContent() != null) { - buffer.append(getContent()); - } - } - - // Package Private Methods ------------------------------------------------ - - /** - * Render the CDATA tag prefix to the specified buffer if - * {@link #isCharacterData()} returns true. The default value is - * /∗<![CDATA[∗/. - * - * @param buffer buffer to append the conditional comment prefix - */ - void renderCharacterDataPrefix(HtmlStringBuffer buffer) { - // Wrap character data in CDATA block - if (isCharacterData()) { - buffer.append("/*/∗]]>∗/. - * - * @param buffer buffer to append the conditional comment prefix - */ - void renderCharacterDataSuffix(HtmlStringBuffer buffer) { - if (isCharacterData()) { - buffer.append(" /*]]>*/"); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/Element.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/element/Element.java deleted file mode 100644 index d4f3cdc236..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/Element.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.element; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -/** - * Provides a base class for rendering HTML elements, for example - * JavaScript (<script>) and Cascading Stylesheets - * (<link> / <style>). - *

- * Subclasses should override {@link #getTag()} to return a specific HTML tag. - */ -public class Element implements Serializable { - - private static final long serialVersionUID = 1L; - - // Variables -------------------------------------------------------------- - - /** The Element attributes Map. */ - private Map attributes; - - // Public Properties ------------------------------------------------------ - - /** - * Returns the Element HTML tag, the default value is null. - *

- * Subclasses should override this method and return the correct tag. - * - * @return this Element HTML tag - */ - public String getTag() { - return null; - } - - /** - * Return the HTML attribute with the given name, or null if the - * attribute does not exist. - * - * @param name the name of link HTML attribute - * @return the link HTML attribute - */ - public String getAttribute(String name) { - if (hasAttributes()) { - return getAttributes().get(name); - } - return null; - } - - /** - * Set the Element attribute with the given attribute name and value. - * - * @param name the attribute name - * @param value the attribute value - * @throws IllegalArgumentException if name parameter is null - */ - public void setAttribute(String name, String value) { - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - if (value != null) { - getAttributes().put(name, value); - } else { - getAttributes().remove(name); - } - } - - /** - * Return the Element attributes Map. - * - * @return the Element attributes Map. - */ - public Map getAttributes() { - if (attributes == null) { - attributes = new HashMap(); - } - return attributes; - } - - /** - * Return true if the Element has attributes or false otherwise. - * - * @return true if the Element has attributes on false otherwise - */ - public boolean hasAttributes() { - return attributes != null && !attributes.isEmpty(); - } - - /** - * Returns true if specified attribute is defined, false otherwise. - * - * @param name the specified attribute to check - * @return true if name is a defined attribute - */ - public boolean hasAttribute(String name) { - return hasAttributes() && getAttributes().containsKey(name); - } - - /** - * Return the "id" attribute value or null if no id is defined. - * - * @return HTML element identifier attribute "id" value or null if no id - * is defined - */ - public String getId() { - return getAttribute("id"); - } - - /** - * Set the HTML id attribute for the with the given value. - * - * @param id the element HTML id attribute value to set - */ - public void setId(String id) { - if (id != null) { - setAttribute("id", id); - } else { - getAttributes().remove("id"); - } - } - - // Public Methods --------------------------------------------------------- - - /** - * Return the thread local Context. - * - * @return the thread local Context - */ - public Context getContext() { - return Context.getThreadLocalContext(); - } - - /** - * Render the HTML representation of the Element to the specified buffer. - *

- * If {@link #getTag()} returns null, this method will return an empty - * string. - * - * @param buffer the specified buffer to render the Element output to - */ - public void render(HtmlStringBuffer buffer) { - if (getTag() == null) { - return; - } - renderTagBegin(getTag(), buffer); - renderTagEnd(getTag(), buffer); - - } - - /** - * Return the HTML string representation of the Element. - * - * @return the HTML string representation of the Element - */ - @Override - public String toString() { - if (getTag() == null) { - return ""; - } - HtmlStringBuffer buffer = new HtmlStringBuffer(getElementSizeEst()); - render(buffer); - return buffer.toString(); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Append all the Element attributes to the specified buffer. - * - * @param buffer the specified buffer to append all the attributes - */ - protected void appendAttributes(HtmlStringBuffer buffer) { - if (hasAttributes()) { - buffer.appendAttributes(attributes); - } - } - - // Package Private Methods ------------------------------------------------ - - /** - * Render the specified {@link #getTag() tag} and {@link #getAttributes()}. - *

- * Please note: the tag will not be closed by this method. This - * enables callers of this method to append extra attributes as needed. - *

- * For example the result of calling: - *

-     * Field field = new TextField("mytext");
-     * HtmlStringBuffer buffer = new HtmlStringBuffer();
-     * field.renderTagBegin("div", buffer);
-     * 
- * will be: - *
-     * <div name="mytext" id="mytext"
-     * 
- * Note that the tag is not closed. - * - * @param tagName the name of the tag to render - * @param buffer the buffer to append the output to - */ - void renderTagBegin(String tagName, HtmlStringBuffer buffer) { - if (tagName == null) { - throw new IllegalStateException("Tag cannot be null"); - } - - buffer.elementStart(tagName); - - buffer.appendAttribute("id", getId()); - appendAttributes(buffer); - } - - /** - * Closes the specified {@link #getTag() tag}. - * - * @param tagName the name of the tag to close - * @param buffer the buffer to append the output to - */ - void renderTagEnd(String tagName, HtmlStringBuffer buffer) { - buffer.elementEnd(); - } - - /** - * Return the estimated rendered element size in characters. - * - * @return the estimated rendered element size in characters - */ - int getElementSizeEst() { - int size = 0; - if (getTag() != null && hasAttributes()) { - //length of the markup -> == 3 - //1 * tag.length() - size += 3 + getTag().length(); - //using 20 as an estimate - size += 20 * getAttributes().size(); - } - return size; - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/JsImport.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/element/JsImport.java deleted file mode 100644 index 8c2b84fb30..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/JsImport.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.element; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.lang.builder.HashCodeBuilder; - -import java.util.Map; - -/** - * Provides a JavaScript HEAD element for importing external JavaScript - * files using the <script> tag. - *

- * Example usage: - *

- * public class MyPage extends Page {
- *
- *     public List getHeadElements() {
- *         // We use lazy loading to ensure the JS import is only added the
- *         // first time this method is called.
- *         if (headElements == null) {
- *             // Get the head elements from the super implementation
- *             headElements = super.getHeadElements();
- *
- *             JsImport jsImport = new JsImport("/js/js-library.js");
- *             headElements.add(jsImport);
- *         }
- *         return headElements;
- *     }
- * } 
- * - * The jsImport instance will be rendered as follows (assuming the context - * path is myApp): - *
- * <script type="text/javascript" href="/myApp/js/js-library.js"></script> 
- */ -public class JsImport extends ResourceElement { - - private static final long serialVersionUID = 1L; - - // Constructors ----------------------------------------------------------- - - /** - * Constructs a new JavaScript import element. - *

- * The JsImport {@link #setVersionIndicator(String) version indicator} - * will automatically be set to the - * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}. - */ - public JsImport() { - this(null); - } - - /** - * Construct a new JavaScript import element with the specified - * src attribute. - *

- * The JsImport {@link #setVersionIndicator(String) version indicator} - * will automatically be set to the - * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}. - *

- * Please note if the given src begins with a - * "/" character the src will be prefixed with the web - * application context path. - * - * @param src the JavaScript import src attribute - */ - public JsImport(String src) { - this(src, true); - } - - /** - * Construct a new JavaScript import element with the specified src - * attribute. - *

- * If useApplicationVersionIndicator is true the - * {@link #setVersionIndicator(String) version indicator} will - * automatically be set to the - * {@link ClickUtils#getApplicationResourceVersionIndicator() application version indicator}. - *

- * Please note if the given src begins with a - * "/" character the src will be prefixed with the web - * application context path. - * - * @param src the JavaScript import src attribute - * @param useApplicationVersionIndicator indicates whether the version - * indicator will automatically be set to the application version indicator - */ - public JsImport(String src, boolean useApplicationVersionIndicator) { - this(src, null); - if (useApplicationVersionIndicator) { - setVersionIndicator(ClickUtils.getApplicationResourceVersionIndicator()); - } - } - - /** - * Construct a new JavaScript import element with the specified src - * attribute and version indicator. - *

- * Please note if the given src begins with a - * "/" character the src will be prefixed with the web - * application context path. - * - * @param src the JsImport src attribute - * @param versionIndicator the version indicator to add to the src path - */ - public JsImport(String src, String versionIndicator) { - setSrc(src); - setAttribute("type", "text/javascript"); - setVersionIndicator(versionIndicator); - } - - // Public Properties ------------------------------------------------------ - - /** - * Returns the JavaScript import HTML tag: <script>. - * - * @return the JavaScript import HTML tag: <script> - */ - @Override - public String getTag() { - return "script"; - } - - /** - * This method always return true because a JavaScript import must be unique - * based on its src attribute. In other words the Page HEAD should - * only contain a single JavaScript import for the specific src. - * - * @see ResourceElement#isUnique() - * - * @return true because JavaScript import must unique based on its - * src attribute - */ - @Override - public boolean isUnique() { - return true; - } - - /** - * Sets the src attribute. If the given src argument is - * null, the src attribute will be removed. - *

- * If the given src begins with a "/" character - * the src will be prefixed with the web application context path. - * Note if the given src is already prefixed with the context path, - * Click won't add it a second time. - * - * @param src the new src attribute - */ - public void setSrc(String src) { - if (src != null) { - if (src.charAt(0) == '/') { - Context context = getContext(); - String contextPath = context.getRequest().getContextPath(); - - // Guard against adding duplicate context path - if (!src.startsWith(contextPath + '/')) { - HtmlStringBuffer buffer = - new HtmlStringBuffer(contextPath.length() + src.length()); - - // Append the context path - buffer.append(contextPath); - buffer.append(src); - src = buffer.toString(); - } - } - } - setAttribute("src", src); - } - - /** - * Return the src attribute. - * - * @return the src attribute - */ - public String getSrc() { - return getAttribute("src"); - } - - // Package Private Methods ------------------------------------------------ - - /** - * Render the HTML representation of the JsImport element to the specified - * buffer. - * - * @param buffer the buffer to render output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - renderConditionalCommentPrefix(buffer); - - buffer.elementStart(getTag()); - - if (isRenderId()) { - buffer.appendAttribute("id", getId()); - } - - String src = getSrc(); - renderResourcePath(buffer, "src", src); - - Map localAttributes = getAttributes(); - for (Map.Entry entry : localAttributes.entrySet()) { - String name = entry.getKey(); - if (!name.equals("id") && !name.equals("src")) { - buffer.appendAttributeEscaped(name, entry.getValue()); - } - } - - buffer.closeTag(); - - buffer.elementEnd(getTag()); - - renderConditionalCommentSuffix(buffer); - } - - /** - * @see Object#equals(Object) - * - * @param o the object with which to compare this instance with - * @return true if the specified object is the same as this object - */ - @Override - public boolean equals(Object o) { - //1. Use the == operator to check if the argument is a reference to this object. - if (o == this) { - return true; - } - - //2. Use the instanceof operator to check if the argument is of the correct type. - if (!(o instanceof JsImport)) { - return false; - } - - //3. Cast the argument to the correct type. - JsImport that = (JsImport) o; - - return (getSrc() == null) ? (that.getSrc() == null) - : (getSrc().equals(that.getSrc())); - } - - /** - * @see Object#hashCode() - * - * @return a hash code value for this object - */ - @Override - public int hashCode() { - return new HashCodeBuilder(17, 37).append(getSrc()).toHashCode(); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/JsScript.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/element/JsScript.java deleted file mode 100644 index 92f8f98ec9..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/JsScript.java +++ /dev/null @@ -1,559 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.element; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.lang.builder.HashCodeBuilder; - -import java.util.HashMap; -import java.util.Map; - -/** - * Provides a HEAD element for including inline JavaScript using the - * <script> tag. - *

- * Example usage: - * - *

- * public class MyPage extends Page {
- *
- *     public List getHeadElements() {
- *         // We use lazy loading to ensure the JS is only added the
- *         // first time this method is called.
- *         if (headElements == null) {
- *             // Get the head elements from the super implementation
- *             headElements = super.getHeadElements();
- *
- *             JsScript jsScript = new JsScript("alert('Hello World!);");
- *             headElements.add(jsScript);
- *         }
- *         return headElements;
- *     }
- * } 
- * - * The jsScript instance will be rendered as follows: - * - *
- * <script type="text/javascript">
- * alert('Hello World');
- * </script> 
- * - * Below is an example showing how to render inline Javascript from a - * Velocity template. - *

- * First we create a Velocity template (/js/mycorp-template.js) which - * contains the variable $divId that must be replaced at runtime with - * the real Div ID attribute: - * - *

- * hide = function() {
- *     var div = document.getElementById('$divId');
- *     div.style.display = "none";
- * } 
- * - * Next is the Page implementation: - * - *
- * public class MyPage extends Page {
- *
- *     public List getHeadElements() {
- *         // We use lazy loading to ensure the JS is only added the
- *         // first time this method is called.
- *         if (headElements == null) {
- *             // Get the head elements from the super implementation
- *             headElements = super.getHeadElements();
- *
- *             // Create a default template model to pass to the template
- *             Map model = ClickUtils.createTemplateModel(this, getContext());
- *
- *             // Add the id of the div to hide
- *             model.put("divId", "myDiv");
- *
- *             // Specify the path to the JavaScript template
- *             String templatePath = "/js/mycorp-template.js";
- *
- *             // Create the inline JavaScript for the given template path and model
- *             JsScript jsScript = new JsScript(templatePath, model);
- *             headElements.add(jsScript);
- *         }
- *         return headElements;
- *     }
- * } 
- * - * The jsScript instance will render as follows (assuming the context - * path is myApp): - * - *
- * <script type="text/javascript">
- *     hide = function() {
- *         var div = document.getElementById('myDiv');
- *         div.style.display = "none";
- *     }
- * </style> 
- * - *

Character data (CDATA) support

- * - * Sometimes it is necessary to wrap inline {@link JsScript JavaScript} - * in CDATA tags. Two use cases are common for doing this: - *
    - *
  • For XML parsing: When using Ajax one often send back partial - * XML snippets to the browser, which is parsed as valid XML. However the XML - * parser will throw an error if the content contains reserved XML characters - * such as '&', '<' and '>'. For these situations it is recommended - * to wrap the script content inside CDATA tags. - *
  • - *
  • XHTML validation: if you want to validate your site using an XHTML - * validator e.g: http://validator.w3.org/.
  • - *
- * - * To wrap the JavaScript content in CDATA tags, set - * {@link #setCharacterData(boolean)} to true. Below is shown how the JavaScript - * content would be rendered: - * - *
- * <script type="text/javascript">
- *  /∗<![CDATA[∗/
- *
- *  if(x < y) alert('Hello');
- *
- *  /∗]]>∗/
- * </script> 
- * - * Notice the CDATA tags are commented out which ensures older browsers that - * don't understand the CDATA tag, will ignore it and only process the actual - * content. - *

- * For an overview of XHTML validation and CDATA tags please see - * http://javascript.about.com/library/blxhtml.htm. - */ -public class JsScript extends ResourceElement { - - private static final long serialVersionUID = 1L; - - // Variables -------------------------------------------------------------- - - /** The inline JavaScript content. */ - private String content; - - /** - * Indicates if the JsScript's content should be wrapped in a CDATA tag. - */ - private boolean characterData = false; - - /** - * Indicates the JsScript content must be executed as soon as the browser - * DOM is available, default value is false. - */ - private boolean executeOnDomReady = false; - - /** The path of the template to render. */ - private String template; - - /** The model of the template to render. */ - private Map model; - - // Constructors ----------------------------------------------------------- - - /** - * Construct a new inline JavaScript element. - */ - public JsScript() { - this(null); - } - - /** - * Construct a new inline JavaScript element with the given content. - * - * @param content the JavaScript content - */ - public JsScript(String content) { - if (content != null) { - this.content = content; - } - setAttribute("type", "text/javascript"); - } - - /** - * Construct a new inline JavaScript element for the given template path - * and template model. - *

- * When the JsScript is rendered the template and model will be merged and - * the result will be rendered together with any JsScript - * {@link #setContent(String) content}. - *

- * - * For example: - *

-     * public class MyPage extends Page {
-     *     public void onInit() {
-     *         Context context = getContext();
-     *
-     *         // Create a default template model
-     *         Map model = ClickUtils.createTemplateModel(this, context);
-     *
-     *         // Create JsScript for the given template path and model
-     *         JsScript script = new JsScript("/mypage-template.js", model);
-     *
-     *         // Add script to the Page Head elements
-     *         getHeadElements().add(script);
-     *     }
-     * } 
- * - * @param template the path of the template to render - * @param model the template model - */ - public JsScript(String template, Map model) { - this(null); - setTemplate(template); - setModel(model); - } - - // Public Properties ------------------------------------------------------ - - /** - * Returns the JavaScript HTML tag: <script>. - * - * @return the JavaScript HTML tag: <script> - */ - @Override - public String getTag() { - return "script"; - } - - /** - * Return the JsScript content. - * - * @return the JsScript content - */ - public String getContent() { - return content; - } - - /** - * Set the JsScript content. - * - * @param content the JsScript content - */ - public void setContent(String content) { - this.content = content; - } - - /** - * Return true if the JsScript's content should be wrapped in CDATA tags, - * false otherwise. - * - * @return true if the JsScript's content should be wrapped in CDATA tags, - * false otherwise - */ - public boolean isCharacterData() { - return characterData; - } - - /** - * Sets whether the JsScript's content should be wrapped in CDATA tags or not. - * - * @param characterData true indicates that the JsScript's content should be - * wrapped in CDATA tags, false otherwise - */ - public void setCharacterData(boolean characterData) { - this.characterData = characterData; - } - - /** - * Return true if the JsScript content must be executed as soon as the - * browser DOM is ready, false otherwise. Default value is false. - * - * @see #setExecuteOnDomReady(boolean) - * - * @return true if the JsScript content must be executed as soon as the - * browser DOM is ready. - */ - public boolean isExecuteOnDomReady() { - return executeOnDomReady; - } - - /** - * Sets whether the JsScript content must be executed as soon as the browser - * DOM is ready. - *

- * If this flag is true, the JsScript content will be registered with - * the "Click.addLoadEvent" function from the JavaScript file - * "/click/control.js". - *

- * Please note: when setting this flag to true, the JavaScript - * file "/click/control.js" must already be included in the Page or - * Control, it won't be included automatically. - *

- * Also note: for {@link Context#isAjaxRequest() Ajax} - * requests the JsScript content won't be registered with the - * "Click.addLoadEvent" function because Ajax requests does not trigger - * the browser's DOM loaded event. Instead the JsScript content will be - * evaluated immediately by the browser. - * - * @param executeOnDomReady indicates whether the JsScript content must be - * executed as soon as the browser DOM is ready. - */ - public void setExecuteOnDomReady(boolean executeOnDomReady) { - this.executeOnDomReady = executeOnDomReady; - } - - /** - * Return the path of the template to render. - * - * @see #setTemplate(String) - * - * @return the path of the template to render - */ - public String getTemplate() { - return template; - } - - /** - * Set the path of the template to render. - *

- * If the {@link #template} property is set, the template and {@link #model} - * will be merged and the result will be rendered together with any JsScript - * {@link #setContent(String) content}. - * - * @param template the path of the template to render - */ - public void setTemplate(String template) { - this.template = template; - } - - /** - * Return the model of the {@link #setTemplate(String) template} - * to render. - * - * @see #setModel(Map) - * - * @return the model of the template to render - */ - public Map getModel() { - return model; - } - - /** - * Set the model of the template to render. - *

- * If the {@link #template} property is set, the template and {@link #model} - * will be merged and the result will be rendered together with any JsScript - * {@link #setContent(String) content}. - * - * @param model the model of the template to render - */ - public void setModel(Map model) { - this.model = model; - } - - // Public Methods --------------------------------------------------------- - - /** - * Render the HTML representation of the JsScript element to the specified - * buffer. - * - * @param buffer the buffer to render output to - */ - @Override - public void render(HtmlStringBuffer buffer) { - - Context context = getContext(); - boolean isAjaxRequest = context.isAjaxRequest(); - - // Render IE conditional comment if conditional comment was set - renderConditionalCommentPrefix(buffer); - - buffer.elementStart(getTag()); - - if (isRenderId()) { - buffer.appendAttribute("id", getId()); - } - - appendAttributes(buffer); - - buffer.closeTag(); - - buffer.append("\n"); - - // Render CDATA tag if necessary - renderCharacterDataPrefix(buffer); - - // Render the DOM ready function prefix for non-ajax requests. Ajax - // requests does not trigger DOM ready event so the prefix should not be - // rendered - if (!isAjaxRequest) { - renderDomReadyPrefix(buffer); - } - - renderContent(buffer, context); - - // Render the DOM ready function suffix for non-ajax requests - if (!isAjaxRequest) { - renderDomReadySuffix(buffer); - } - - renderCharacterDataSuffix(buffer); - - buffer.append("\n"); - - buffer.elementEnd(getTag()); - - renderConditionalCommentSuffix(buffer); - } - - /** - * @see Object#equals(Object) - * - * @param o the object with which to compare this instance with - * @return true if the specified object is the same as this object - */ - @Override - public boolean equals(Object o) { - if (!isUnique()) { - return super.equals(o); - } - - //1. Use the == operator to check if the argument is a reference to this object. - if (o == this) { - return true; - } - - //2. Use the instanceof operator to check if the argument is of the correct type. - if (!(o instanceof JsScript)) { - return false; - } - - //3. Cast the argument to the correct type. - JsScript that = (JsScript) o; - - String id = getId(); - String thatId = that.getId(); - return id == null ? thatId == null : id.equals(thatId); - } - - /** - * @see Object#hashCode() - * - * @return a hash code value for this object - */ - @Override - public int hashCode() { - if (!isUnique()) { - return super.hashCode(); - } - return new HashCodeBuilder(17, 37).append(getId()).toHashCode(); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Render the JsScript {@link #setContent(String) content} - * to the specified buffer. - *

- * Please note: if the {@link #setTemplate(String) template} - * property is set, this method will merge the {@link #setTemplate(String) template} - * and {@link #setModel(Map) model} and the result will be - * rendered, together with the JsScript - * {@link #setContent(String) content}, - * to the specified buffer. - * - * @param buffer the buffer to append the output to - * @param context the request context - */ - protected void renderContent(HtmlStringBuffer buffer, Context context) { - if (getTemplate() != null) { - - Map templateModel = getModel(); - if (templateModel == null) { - templateModel = new HashMap(); - } - buffer.append(context.renderTemplate(getTemplate(), templateModel)); - - } - - if (getContent() != null) { - buffer.append(getContent()); - } - } - - /** - * Render the "Click.addLoadEvent" function prefix to ensure the script - * is executed as soon as the browser DOM is available. The prefix is - * "Click.addLoadEvent(function(){". - * - * @see #renderDomReadySuffix(HtmlStringBuffer) - * - * @param buffer the buffer to append the Click.addLoadEvent function to - */ - protected void renderDomReadyPrefix(HtmlStringBuffer buffer) { - // Wrap content in Click.addLoadEvent function - if (isExecuteOnDomReady()) { - buffer.append("Click.addLoadEvent(function(){\n"); - } - } - - /** - * Render the "Click.addLoadEvent" function suffix. The suffix is - * "});". - * - * @see #renderDomReadyPrefix(HtmlStringBuffer) - * - * @param buffer buffer to append the conditional comment prefix - */ - protected void renderDomReadySuffix(HtmlStringBuffer buffer) { - // Close Click.addLoadEvent function - if (isExecuteOnDomReady()) { - buffer.append("});"); - } - } - - // Package Private Methods ------------------------------------------------ - - /** - * Render the CDATA tag prefix to the specified buffer if - * {@link #isCharacterData()} returns true. The prefix is - * /∗<![CDATA[∗/. - * - * @see #renderCharacterDataSuffix(HtmlStringBuffer) - * - * @param buffer buffer to append the conditional comment prefix - */ - void renderCharacterDataPrefix(HtmlStringBuffer buffer) { - // Wrap character data in CDATA block - if (isCharacterData()) { - buffer.append("/*/∗]]>∗/. - * - * @see #renderCharacterDataPrefix(HtmlStringBuffer) - * - * @param buffer buffer to append the conditional comment prefix - */ - void renderCharacterDataSuffix(HtmlStringBuffer buffer) { - if (isCharacterData()) { - buffer.append(" /*]]>*/"); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/ResourceElement.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/element/ResourceElement.java deleted file mode 100644 index bb5468620e..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/element/ResourceElement.java +++ /dev/null @@ -1,411 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.element; - -import org.apache.click.element.CssStyle; -import org.apache.click.element.JsImport; -import org.apache.click.element.JsScript; -import org.openidentityplatform.openam.click.util.HtmlStringBuffer; -import org.apache.commons.lang.StringUtils; - -/** - * Provides a base class for rendering HEAD resources of an HTML page, for - * example JavaScript (<script>) and Cascading Stylesheets - * (<link>/<style>). - *

- * Subclasses should override {@link #getTag()} to return a specific HTML tag. - *

- * Below are some example Resource elements: - *

    - *
  • {@link JsImport}, for importing external JavaScript using the - * <script> element.
  • - *
  • {@link JsScript}, for including inline JavaScript using the - * <script> element.
  • - *
  • {@link org.apache.click.element.CssImport}, for importing external Cascading Stylesheets - * using the <link> element.
  • - *
  • {@link CssStyle}, for including inline Cascading Stylesheets - * using the <style> element.
  • - *
- * - * - *

Remove duplicates

- * Click will ensure that duplicate Resource elements are removed by checking - * the {@link #isUnique()} property. Thus if the same Resource is imported - * multiple times by the Page or different Controls, only one Resource will be - * rendered, if {@link #isUnique()} returns true. - *

- * The rules for defining a unique Resource is as follows: - *

    - *
  • {@link JsImport} and {@link org.apache.click.element.CssImport} is unique based on the - * attributes {@link JsImport#getSrc()} and {@link CssImport#getHref()} - * respectively.
  • - *
  • {@link JsScript} and {@link CssStyle} is unique if their HTML - * {@link #setId(String) ID} attribute is set. The HTML - * spec defines that an element's HTML ID must be unique per page.
  • - *
- * For example: - *
- * public class MyPage extends Page {
- *
- *     public List getHeadElements() {
- *         // We use lazy loading to ensure the JavaScript and Css is only added
- *         // the first time this method is called.
- *         if (headElements == null) {
- *             // Get the head elements from the super implementation
- *             headElements = super.getHeadElements();
- *
- *             JsImport jsImport = new JsImport("/js/mylib.js");
- *             // Click will ensure the library "/js/mylib.js" is only included
- *             // once in the Page
- *             headElements.add(jsImport);
- *
- *             JsScript jsScript = new JsScript("alert('Hello!');");
- *             // Click won't ensure the script is unique because its ID
- *             // attribute is not defined
- *             headElements.add(jsScript);
- *
- *             jsScript = new JsScript("alert('Hello!');");
- *             jsScript.setId("my-unique-script-id");
- *             // Click will ensure the script is unique because its ID attribute
- *             // is defined. Click will remove other scripts with the same ID
- *             headElements.add(jsScript);
- *
- *             CssImport cssImport = new CssImport("/css/style.css");
- *             // Click will ensure the library "/css/style.css" is only
- *             // included once in the Page
- *             headElements.add(cssImport);
- *
- *             CssScript cssScript = new CssScript("body { font-weight: bold; }");
- *             cssScript.setId("my-unique-style-id");
- *             // Click will ensure the css is unique because its ID attribute
- *             // is defined. Click will remove other css styles with the same ID
- *             headElements.add(cssScript);
- *         }
- *         return headElements;
- *     }
- * } 
- * - * - *

Automatic Resource versioning

- * - * ResourceElement provides the ability to automatically version elements - * according to Yahoo Performance Rule: Add an Expires or a Cache-Control Header. - * This rule recommends adding an expiry header to JavaScript, Css - * and image resources, which forces the browser to cache the resources. It also - * suggests versioning the resources so that each new release of the - * web application renders resources with different paths, forcing the browser - * to download the new resources. - *

- * For detailed information on versioning JavaScript, Css and image resources - * see the PerformanceFilter. - *

- * To enable versioning of JavaScript, Css and image resources the following - * conditions must be met: - *

    - *
  • the {@link org.apache.click.util.ClickUtils#ENABLE_RESOURCE_VERSION} - * request attribute must be set to true
  • - *
  • the application mode must be either "production" or "profile"
  • - *
  • the {@link org.apache.click.util.ClickUtils#setApplicationVersion(String) - * application version} must be set
  • - *
- * Please note: PerformanceFilter - * handles the above steps for you. - * - * - *

Conditional comment support for Internet Explorer

- * - * Sometimes it is necessary to provide additional JavaScript and Css for - * Internet Explorer because it deviates quite often from the standards. - *

- * Conditional comments allows you to wrap the resource in a special comment - * which only IE understands, meaning other browsers won't process the resource. - *

- * You can read more about conditional comments - * here - * and here - *

- * It has to be said that IE7 and up has much better support for Css, thus - * conditional comments are mostly used for IE6 and below. - *

- * public class MyPage extends Page {
- *
- *     public List getHeadElements() {
- *         // We use lazy loading to ensure the JavaScript and Css is only added
- *         // the first time this method is called.
- *         if (headElements == null) {
- *             // Get the head elements from the super implementation
- *             headElements = super.getHeadElements();
- *
- *             CssImport cssImport = new CssImport("/css/ie-style.css");
- *             // Use one of the predefined conditional comments to target IE6
- *             // and below
- *             cssImport.setConditionalComment(IE_LESS_THAN_IE7);
- *             headElements.add(cssImport);
- *
- *             cssImport = new CssImport("/css/ie-style2.css");
- *             // Use a custom predefined conditional comments to target only IE6
- *             cssImport.setConditionalComment("[if IE 6]");
- *             headElements.add(cssImport);
- *         }
- *         return headElements;
- *     }
- * } 
- * - * ResourceElement contains some predefined Conditional Comments namely - * {@link #IF_IE}, {@link #IF_LESS_THAN_IE7} and {@link #IF_IE7}. - */ -public class ResourceElement extends Element { - - private static final long serialVersionUID = 1L; - - // Constants -------------------------------------------------------------- - - /** - * A predefined conditional comment to test if browser is IE. Value: - * [if IE]. - */ - public static final String IF_IE = "[if IE]"; - - /** - * A predefined conditional comment to test if browser is less than IE7. - * Value: [if lt IE 7]. - */ - public static final String IF_LESS_THAN_IE7 = "[if lt IE 7]"; - - /** - * A predefined conditional comment to test if browser is IE7. Value: - * [if IE 7]. - */ - public static final String IF_IE7 = "[if IE 7]"; - - /** - * A predefined conditional comment to test if browser is less than - * or equal to IE7. Value: [if lte IE 7]. - */ - public static final String IF_LESS_THAN_OR_EQUAL_TO_IE7 = "[if lte IE 7]"; - - /** - * A predefined conditional comment to test if browser is less than IE9. - * Value: [if lt IE 9]. - */ - public static final String IF_LESS_THAN_IE9 = "[if lt IE 9]"; - - // Variables -------------------------------------------------------------- - - /** - * The Internet Explorer conditional comment to wrap the Resource with. - */ - private String conditionalComment; - - /** - * Indicates whether the {@link #getId() ID} attribute should be rendered - * or not, default value is true. - */ - private boolean renderId = true; - - /** - * The version indicator to append to the Resource element. - */ - private String versionIndicator; - - // ------------------------------------------------------ Public properties - - /** - * Return the version indicator to be appended to the resource - * path. - * - * @return the version indicator to be appended to the resource - * path. - */ - public String getVersionIndicator() { - return versionIndicator; - } - - /** - * Set the version indicator to be appended to the resource path. - * - * @param versionIndicator the version indicator to be appended to the - * resource path - */ - public void setVersionIndicator(String versionIndicator) { - this.versionIndicator = versionIndicator; - } - - /** - * Returns whether or not the Resource unique. This method returns - * true if the {@link #getId() ID} attribute is defined, - * false otherwise. - * - * @return true if the Resource should be unique, false otherwise. - */ - public boolean isUnique() { - String id = getId(); - - // If id is defined, import will be any duplicate import found will be - // filtered out - if (StringUtils.isNotBlank(id)) { - return true; - } - return false; - } - - /** - * Returns the element render {@link #getId() ID} attribute status, default - * value is true. - * - * @see #setRenderId(boolean) - * - * @return the element render id attribute status, default value is true - */ - public boolean isRenderId() { - return renderId; - } - - /** - * Set the element render {@link #getId() ID} attribute status. - *

- * If renderId is false the element {@link #getId() ID} attribute will not - * be rendered. - * - * @param renderId set the element render id attribute status - */ - public void setRenderId(boolean renderId) { - this.renderId = renderId; - } - - /** - * Return Internal Explorer's conditional comment to wrap the - * Resource with. - * - * @return Internal Explorer's conditional comment to wrap the Resource with. - */ - public String getConditionalComment() { - return conditionalComment; - } - - /** - * Set Internet Explorer's conditional comment to wrap the Resource with. - * - * @param conditionalComment Internet Explorer's conditional comment to wrap - * the Resource with - */ - public void setConditionalComment(String conditionalComment) { - this.conditionalComment = conditionalComment; - } - - // Public Methods --------------------------------------------------------- - - /** - * Render the HTML representation of the Resource element to the specified - * buffer. - *

- * If {@link #getTag()} returns null, this method will return an empty - * string. - * - * @param buffer the specified buffer to render the Resource element output - * to - */ - @Override - public void render(HtmlStringBuffer buffer) { - renderConditionalCommentPrefix(buffer); - - if (getTag() == null) { - return; - } - renderTagBegin(getTag(), buffer); - renderTagEnd(getTag(), buffer); - - renderConditionalCommentSuffix(buffer); - } - - // Package Private Methods ------------------------------------------------ - - /** - * Render the given attribute and resourcePath and append the - * {@link #getVersionIndicator()} to the resourcePath, if it was set. - * If the version indicator is not defined this method will only render the - * resourcePath. - * - * @param buffer the buffer to render to - * @param attribute the attribute name to render - * @param resourcePath the resource path to render - */ - void renderResourcePath(HtmlStringBuffer buffer, String attribute, - String resourcePath) { - String versionIndicator = getVersionIndicator(); - - // If resourcePath is null exit early - if (resourcePath == null) { - return; - } - - // If version indicator is not defined render resource path only - if (StringUtils.isBlank(versionIndicator)) { - buffer.appendAttribute(attribute, resourcePath); - return; - } - - // If the resourcePath has no extension render the resource path only - int start = resourcePath.lastIndexOf("."); - if (start < 0) { - buffer.appendAttribute(attribute, resourcePath); - return; - } - - buffer.append(" "); - buffer.append(attribute); - buffer.append("=\""); - buffer.append(resourcePath.substring(0, start)); - buffer.append(versionIndicator); - buffer.append(resourcePath.substring(start)); - buffer.append("\""); - } - - /** - * Render the {@link #getConditionalComment() conditional comment} prefix - * to the specified buffer. If the conditional comment is not defined this - * method won't append to the buffer. - * - * @param buffer buffer to append the conditional comment prefix - */ - void renderConditionalCommentPrefix(HtmlStringBuffer buffer) { - String conditional = getConditionalComment(); - - // Render IE conditional comment - if (StringUtils.isNotBlank(conditional)) { - buffer.append(""); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ClickResourceService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ClickResourceService.java deleted file mode 100644 index 27c63ad070..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ClickResourceService.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import jakarta.servlet.ServletContext; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.apache.click.util.HtmlStringBuffer; -import org.apache.commons.io.IOUtils; - -/** - * Provides a default Click static resource service class. This class will - * serve static resources contained in the web applications JARs, under the - * resource path META-INF/resources and which are contained under the WAR file - * web root. - *

- * This service is useful for application servers which do not allow Click to - * automatically deploy resources to the web root directory. - */ -public class ClickResourceService implements ResourceService { - - /** The click resources cache. */ - protected Map resourceCache = new ConcurrentHashMap(); - - /** The application log service. */ - protected LogService logService; - - /** The application configuration service. */ - protected ConfigService configService; - - /** - * @see ResourceService#onInit(ServletContext) - * - * @param servletContext the application servlet context - * @throws IOException if an IO error occurs initializing the service - */ - public void onInit(ServletContext servletContext) throws IOException { - - configService = ClickUtils.getConfigService(servletContext); - logService = configService.getLogService(); - } - - /** - * @see org.openidentityplatform.openam.click.service.ResourceService#onDestroy() - */ - public void onDestroy() { - resourceCache.clear(); - } - - /** - * @see org.openidentityplatform.openam.click.service.ResourceService#isResourceRequest(HttpServletRequest) - * - * @param request the servlet request - * @return true if the request is for a static click resource - */ - public boolean isResourceRequest(HttpServletRequest request) { - String resourcePath = ClickUtils.getResourcePath(request); - - // If not a click page and not JSP and not a directory - return !configService.isTemplate(resourcePath) - && !resourcePath.endsWith("/"); - } - - /** - * @see ResourceService#renderResource(HttpServletRequest, HttpServletResponse) - * - * @param request the servlet resource request - * @param response the servlet response - * @throws IOException if an IO error occurs rendering the resource - */ - public void renderResource(HttpServletRequest request, HttpServletResponse response) - throws IOException { - - String resourcePath = ClickUtils.getResourcePath(request); - - byte[] resourceData = resourceCache.get(resourcePath); - - if (resourceData == null) { - // Lazily load resource - resourceData = loadResourceData(resourcePath); - - if (resourceData == null) { - response.sendError(HttpServletResponse.SC_NOT_FOUND); - return; - } - } - - String mimeType = ClickUtils.getMimeType(resourcePath); - if (mimeType != null) { - response.setContentType(mimeType); - } - - if (logService.isDebugEnabled()) { - HtmlStringBuffer buffer = new HtmlStringBuffer(200); - buffer.append("handleRequest: "); - buffer.append(request.getMethod()); - buffer.append(" "); - buffer.append(request.getRequestURL()); - logService.debug(buffer); - } - renderResource(response, resourceData); - } - - // ------------------------------------------------------ Protected Methods - - /** - * Return the list of directories that contains cacheable resources. - *

- * By default only resource packaged under the "/click" directory - * will be processed. To serve resources from other directories you need to - * override this method and return a list of directories to process. - *

- * For example: - * - *

-     * public class MyResourceService extends ClickResourceService {
-     *
-     *     protected List getCacheableDirs() {
-     *         // Get default dirs which includes /click
-     *         List list = super.getCacheableDirs();
-     *
-     *         // Add resources packaged under the folder /clickclick
-     *         list.add("/clickclick");
-     *         // Add resources packaged under the folder /mycorp
-     *         list.add("/mycorp");
-     *     }
-     * } 
- * - * You also need to add a mapping in your web.xml to forward - * requests for these resources on to Click: - * - *
-     * <-- The default Click *.htm mapping -->
-     * <servlet-mapping>
-     *   <servlet-name>ClickServlet</servlet-name>
-     *   <url-pattern>*.htm</url-pattern>
-     * </servlet-mapping>
-     *
-     * <-- Add a mapping to serve all resources under /click directly from
-     * the JARs. -->
-     * <servlet-mapping>
-     *   <servlet-name>ClickServlet</servlet-name>
-     *   <url-pattern>/click/*</url-pattern>
-     * </servlet-mapping>
-     *
-     * <-- Add another mapping to serve all resources under /clickclick
-     * from the JARs. -->
-     * <servlet-mapping>
-     *   <servlet-name>ClickServlet</servlet-name>
-     *   <url-pattern>/clickclick/*</url-pattern>
-     * </servlet-mapping>
-     *
-     * <-- Add a mapping to serve all resources under /mycorp
-     * from the JARs. -->
-     * <servlet-mapping>
-     *   <servlet-name>ClickServlet</servlet-name>
-     *   <url-pattern>/mycorp/*</url-pattern>
-     * </servlet-mapping>
-     * 
- * - * @return list of directories that should be cached - */ - protected List getCacheableDirs() { - List list = new ArrayList(); - list.add("/click"); - return list; - } - - // Private Methods -------------------------------------------------------- - - /** - * Store the resource under the given resource path. - * - * @param resourcePath the path to store the resource under - * @param data the resource byte array - */ - private void storeResourceData(String resourcePath, byte[] data) { - // Only cache in production modes - if (configService.isProductionMode() || configService.isProfileMode()) { - resourceCache.put(resourcePath, data); - } - } - - /** - * Load the resource for the given resourcePath. This method will load the - * resource from the servlet context, and if not found, load it from the - * classpath under the folder 'META-INF/resources'. - * - * @param resourcePath the path to the resource to load - * @return the resource as a byte array - * @throws IOException if the resources cannot be loaded - */ - private byte[] loadResourceData(String resourcePath) throws IOException { - - byte[] resourceData = null; - - ServletContext servletContext = configService.getServletContext(); - - resourceData = getServletResourceData(servletContext, resourcePath); - if (resourceData != null) { - storeResourceData(resourcePath, resourceData); - } else { - resourceData = getClasspathResourceData("META-INF/resources" - + resourcePath); - - if (resourceData != null) { - storeResourceData(resourcePath, resourceData); - } - } - - return resourceData; - } - - /** - * Load the resource for the given resourcePath from the servlet context. - * - * @param servletContext the application servlet context - * @param resourcePath the path of the resource to load - * @return the byte array for the given resource path - * @throws IOException if the resource could not be loaded - */ - private byte[] getServletResourceData(ServletContext servletContext, - String resourcePath) throws IOException { - - InputStream inputStream = null; - try { - inputStream = servletContext.getResourceAsStream(resourcePath); - - if (inputStream != null) { - return IOUtils.toByteArray(inputStream); - } else { - return null; - } - - } finally { - ClickUtils.close(inputStream); - } - } - - /** - * Load the resource for the given resourcePath from the classpath. - * - * @param resourcePath the path of the resource to load - * @return the byte array for the given resource path - * @throws IOException if the resource could not be loaded - */ - private byte[] getClasspathResourceData(String resourcePath) throws IOException { - - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - - InputStream inputStream = classLoader.getResourceAsStream(resourcePath); - if (inputStream == null) { - inputStream = getClass().getResourceAsStream(resourcePath); - } - - try { - - if (inputStream != null) { - return IOUtils.toByteArray(inputStream); - } else { - return null; - } - - } finally { - ClickUtils.close(inputStream); - } - } - - /** - * Render the given resourceData byte array to the response. - * - * @param response the response object - * @param resourceData the resource byte array - * @throws IOException if the resource data could not be rendered - */ - private void renderResource(HttpServletResponse response, - byte[] resourceData) throws IOException { - - OutputStream outputStream = null; - try { - response.setContentLength(resourceData.length); - - outputStream = response.getOutputStream(); - outputStream.write(resourceData); - outputStream.flush(); - - } finally { - ClickUtils.close(outputStream); - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ConfigService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ConfigService.java deleted file mode 100644 index 52454c380c..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ConfigService.java +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import java.lang.reflect.Field; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import jakarta.servlet.ServletContext; - -import org.openidentityplatform.openam.click.Page; -import org.openidentityplatform.openam.click.PageInterceptor; -import org.apache.click.util.Format; - -/** - * Provides a Click application configuration service interface. - *

- * A single application ConfigService instance is created by the ClickServlet at - * startup. Once the ConfigService has been initialized it is stored in the - * ServletContext using the key {@value #CONTEXT_NAME}. - * - * - *

Configuration

- * The default ConfigService is {@link XmlConfigService}. - *

- * However it is possible to specify a different implementation. - *

- * For example you can subclass XmlConfigService and override methods such as - * {@link #onInit(jakarta.servlet.ServletContext)} to alter initialization - * behavior. - *

- * For Click to recognize your custom service class you must set the - * context initialization parameter, - * {@link org.openidentityplatform.openam.click.ClickServlet#CONFIG_SERVICE_CLASS config-service-class} - * in your web.xml file. - *

- * Below is an example of a custom service class - * com.mycorp.service.CustomConfigService: - * - *

- * package com.mycorp.service;
- *
- * public class CustomConfigService extends XmlConfigService {
- *
- *     public CustomConfigService() {
- *     }
- *
- *     public void onInit(ServletContext servletContext) throws Exception {
- *         // Add your logic here
- *         ...
- *
- *         // Call super to resume initialization
- *         super.onInit(servletContext);
- *     }
- * }
- * 
- * - * Please note that the custom ConfigService implementation must have a - * no-argument constructor so Click can instantiate the service. - *

- * Also define the new service in your web.xml as follows: - * - *

- * {@code
- * 
- *
- * ...
- *
- *     
- *         config-service-class
- *         com.mycorp.service.CustomConfigSerivce
- *     
- *
- * ...
- *
- * } 
- */ -public interface ConfigService { - - /** The trace application mode. */ - public static final String MODE_TRACE = "trace"; - - /** The debug application mode. */ - public static final String MODE_DEBUG = "debug"; - - /** The development application mode. */ - public static final String MODE_DEVELOPMENT = "development"; - - /** The profile application mode. */ - public static final String MODE_PROFILE = "profile"; - - /** The profile application mode. */ - public static final String MODE_PRODUCTION = "production"; - - /** The error page file path:   "/click/error.htm". */ - static final String ERROR_PATH = "/click/error.htm"; - - /** The page not found file path:   "/click/not-found.htm". */ - public static final String NOT_FOUND_PATH = "/click/not-found.htm"; - - /** The page auto binding mode. */ - public enum AutoBinding { DEFAULT, ANNOTATION, NONE }; - - /** - * The servlet context attribute name. The ClickServlet stores the - * application ConfigService instance in the ServletContext using this - * context attribute name. The value of this constant is {@value}. - */ - public static final String CONTEXT_NAME = "org.openidentityplatform.openam.click.service.ConfigService"; - - /** - * Initialize the ConfigurationService with the given application servlet context. - *

- * This method is invoked after the ConfigurationService has been constructed. - * - * @param servletContext the application servlet context - * @throws Exception if an error occurs initializing the ConfigurationService - */ - public void onInit(ServletContext servletContext) throws Exception; - - /** - * Destroy the ConfigurationService. This method will also invoke the - * onDestroy() methods on the FileUploadService, - * TemplateService, ResourceService and the - * LogService in that order. - */ - public void onDestroy(); - - /** - * Return the application file upload service, which is used to parse - * multi-part file upload post requests. - * - * @return the application file upload service - */ - public FileUploadService getFileUploadService(); - - /** - * Return the application log service. - * - * @return the application log service. - */ - public LogService getLogService(); - - /** - * Return the application resource service. - * - * @return the application resource service. - */ - public ResourceService getResourceService(); - - /** - * Return the application templating service. - * - * @return the application templating service - */ - public TemplateService getTemplateService(); - - /** - * Return the application messages map service. - * - * @return the application messages Map service - */ - public MessagesMapService getMessagesMapService(); - - /** - * Return the Click application mode value:   - * ["production", "profile", "development", "debug", "trace"]. - * - * @return the application mode value - */ - public String getApplicationMode(); - - /** - * Return the Click application charset or null if not defined. - * - * @return the application charset value - */ - public String getCharset(); - - /** - * Return the error handling page Page Class. - * - * @return the error handling page Page Class - */ - public Class getErrorPageClass(); - - /** - * Create and return a new format object instance. - * - * @return a new format object instance - */ - public Format createFormat(); - - /** - * Return true if JSP exists for the given ".htm" path. - * - * @param path the Page ".htm" path - * @return true if JSP exists for the given ".htm" path - */ - public boolean isJspPage(String path); - - /** - * Return true if the given resource is a Page class template, false - * otherwise. - *

- * Below is an example showing how to map .htm and .jsp - * files as Page class templates. - * - *

-     * public class XmlConfigService implements ConfigService {
-     *
-     *     ...
-     *
-     *     public boolean isTemplate(String path) {
-     *         if (path.endsWith(".htm") || path.endsWith(".jsp")) {
-     *             return true;
-     *         }
-     *         return false;
-     *     }
-     *
-     *     ...
-     * } 
- * - * @param path the path to check if it is a Page class template or not - * @return true if the resource is a Page class template, false otherwise - */ - public boolean isTemplate(String path); - - /** - * Return the page auto binding mode. If the mode is "PUBLIC" any public - * Page fields will be auto bound, if the mode is "ANNOTATION" any Page field - * with the "Bindable" annotation will be auto bound and if the mode is - * "NONE" no Page fields will be auto bound. - * - * @return the Page field auto binding mode { PUBLIC, ANNOTATION, NONE } - */ - public ConfigService.AutoBinding getAutoBindingMode(); - - /** - * Return true if the application is in "production" mode. - * - * @return true if the application is in "production" mode - */ - public boolean isProductionMode(); - - /** - * Return true if the application is in "profile" mode. - * - * @return true if the application is in "profile" mode - */ - public boolean isProfileMode(); - - /** - * Return the Click application locale or null if not defined. - * - * @return the application locale value - */ - public Locale getLocale(); - - /** - * Return the path for the given page Class. - * - * @param pageClass the class of the Page to lookup the path for - * @return the path for the given page Class - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - public String getPagePath(Class pageClass); - - /** - * Return the page Class for the given path. The path must start - * with a "/". - * - * @param path the page path - * @return the page class for the given path - * @throws IllegalArgumentException if the Page Class for the path is not - * found - */ - public Class getPageClass(String path); - - /** - * Return the list of configured page classes. - * - * @return the list of configured page classes - */ - public List> getPageClassList(); - - /** - * Return Map of bindable fields for the given page class. - * - * @param pageClass the page class - * @return a Map of bindable fields for the given page class - */ - public Map getPageFields(Class pageClass); - - /** - * Return the bindable field of the given name for the pageClass, - * or null if not defined. - * - * @param pageClass the page class - * @param fieldName the name of the field - * @return the bindable field of the pageClass with the given name or null - */ - public Field getPageField(Class pageClass, String fieldName); - - /** - * Return the headers of the page for the given path. - * - * @param path the path of the page - * @return a Map of headers for the given page path - */ - public Map getPageHeaders(String path); - - /** - * Return an array bindable for the given page class. - * - * @param pageClass the page class - * @return an array bindable fields for the given page class - */ - public Field[] getPageFieldArray(Class pageClass); - - /** - * Return the list of configured PageInterceptors instances. - * - * @return the list of configured PageInterceptors instances - */ - public List getPageInterceptors(); - - /** - * Return the page not found Page Class. - * - * @return the page not found Page Class - */ - public Class getNotFoundPageClass(); - - /** - * Return the application servlet context. - * - * @return the application servlet context - */ - public ServletContext getServletContext(); - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ConsoleLogService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ConsoleLogService.java deleted file mode 100644 index bd134dd20b..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ConsoleLogService.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import jakarta.servlet.ServletContext; -import org.apache.click.util.HtmlStringBuffer; - -/** - * Provides a Log Service class which will log messages to the console or - * System.out. - *

- * ConsoleLogService is the default {@link LogService} for Click. - *

- * However you can instruct Click to use a different log service implementation. - * Please see {@link LogService} for more details. - */ -public class ConsoleLogService implements LogService { - - // -------------------------------------------------------------- Constants - - /** The trace logging level. */ - public static final int TRACE_LEVEL = -1; - - /** The debug logging level. */ - public static final int DEBUG_LEVEL = 0; - - /** The info logging level. */ - public static final int INFO_LEVEL = 1; - - /** The warn logging level. */ - public static final int WARN_LEVEL = 2; - - /** The error logging level. */ - public static final int ERROR_LEVEL = 3; - - /** The level names. */ - protected static final String[] LEVELS = - { " [trace] ", " [debug] ", " [info ] ", " [warn ] ", " [error] " }; - - // ----------------------------------------------------- Instance Variables - - /** The logging level. */ - protected int logLevel = INFO_LEVEL; - - /** The log name. */ - protected String name = "Click"; - - // --------------------------------------------------------- Public Methods - - /** - * @see LogService#onInit(ServletContext) - * - * @param servletContext the application servlet context - * @throws Exception if an error occurs initializing the LogService - */ - public void onInit(ServletContext servletContext) throws Exception { - } - - /** - * @see LogService#onDestroy() - */ - public void onDestroy() { - } - - /** - * Set the logging level - * [ TRACE_LEVEL | DEBUG_LEVEL | INFO_LEVEL | WARN_LEVEL | ERROR_LEVEL ]. - * - * @param level the logging level - */ - public void setLevel(int level) { - logLevel = level; - } - - /** - * @see LogService#debug(Object) - * - * @param message the message to log - */ - public void debug(Object message) { - log(DEBUG_LEVEL, String.valueOf(message), null); - } - - /** - * @see LogService#debug(Object, Throwable) - * - * @param message the message to log - * @param error the error to log - */ - public void debug(Object message, Throwable error) { - log(DEBUG_LEVEL, String.valueOf(message), error); - } - - /** - * @see LogService#error(Object) - * - * @param message the message to log - */ - public void error(Object message) { - log(ERROR_LEVEL, String.valueOf(message), null); - } - - /** - * @see LogService#error(Object, Throwable) - * - * @param message the message to log - * @param error the error to log - */ - public void error(Object message, Throwable error) { - log(ERROR_LEVEL, String.valueOf(message), error); - } - - /** - * @see LogService#info(Object) - * - * @param message the message to log - */ - public void info(Object message) { - log(INFO_LEVEL, String.valueOf(message), null); - } - - /** - * @see LogService#info(Object, Throwable) - * - * @param message the message to log - * @param error the error to log - */ - public void info(Object message, Throwable error) { - log(INFO_LEVEL, String.valueOf(message), error); - } - - /** - * @see LogService#trace(Object) - * - * @param message the message to log - */ - public void trace(Object message) { - log(TRACE_LEVEL, String.valueOf(message), null); - } - - /** - * @see LogService#trace(Object, Throwable) - * - * @param message the message to log - * @param error the error to log - */ - public void trace(Object message, Throwable error) { - log(TRACE_LEVEL, String.valueOf(message), error); - } - - /** - * @see LogService#warn(Object) - * - * @param message the message to log - */ - public void warn(Object message) { - log(WARN_LEVEL, String.valueOf(message), null); - } - - /** - * @see LogService#warn(Object, Throwable) - * - * @param message the message to log - * @param error the error to log - */ - public void warn(Object message, Throwable error) { - log(WARN_LEVEL, String.valueOf(message), error); - } - - /** - * @see LogService#isDebugEnabled() - * - * @return true if [debug] level logging is enabled - */ - public boolean isDebugEnabled() { - return logLevel <= DEBUG_LEVEL; - } - - /** - * @see LogService#isInfoEnabled() - * - * @return true if [info] level logging is enabled - */ - public boolean isInfoEnabled() { - return logLevel <= INFO_LEVEL; - } - - /** - * @see LogService#isTraceEnabled() - * - * @return true if [trace] level logging is enabled - */ - public boolean isTraceEnabled() { - return logLevel <= TRACE_LEVEL; - } - - // ------------------------------------------------------ Protected Methods - - /** - * Log the given message and optional error at the specified logging level. - * - * @param level the logging level - * @param message the message to log - * @param error the optional error to log - */ - protected void log(int level, String message, Throwable error) { - if (level < logLevel) { - return; - } - - HtmlStringBuffer buffer = new HtmlStringBuffer(); - - buffer.append("["); - buffer.append(name); - buffer.append("]"); - - buffer.append(LEVELS[level + 1]); - buffer.append(message); - - if (error != null) { - System.out.print(buffer.toString()); - error.printStackTrace(System.out); - } else { - System.out.println(buffer.toString()); - } - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/DefaultMessagesMapService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/DefaultMessagesMapService.java deleted file mode 100644 index c590fb2d23..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/DefaultMessagesMapService.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import java.util.Locale; -import java.util.Map; - -import jakarta.servlet.ServletContext; - -import org.apache.click.util.MessagesMap; - -/** - * Provides a default MessagesMapService which returns MessagesMap implementations - * of the messages map. - */ -public class DefaultMessagesMapService implements MessagesMapService { - - /** - * @see MessagesMapService#onInit(ServletContext) - * - * @param servletContext the application servlet context - * @throws Exception if an error occurs initializing the LogService - */ - public void onInit(ServletContext servletContext) throws Exception { - } - - /** - * @see org.apache.click.service.MessagesMapService#onDestroy() - */ - public void onDestroy() { - } - - /** - * Return a MessagesMap instance for the target baseClass, global resource - * name and locale. - * - * @param baseClass the target class - * @param globalResource the global resource bundle name - * @param locale the users Locale - * - * @return a MessagesMap instance. - * - * @see MessagesMapService#createMessagesMap(java.lang.Class, java.lang.String, java.util.Locale) - * @see MessagesMap#MessagesMap(Class, String) - */ - public Map createMessagesMap(Class baseClass, - String globalResource, Locale locale) { - return new MessagesMap(baseClass, globalResource, locale); - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/DeployUtils.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/DeployUtils.java deleted file mode 100644 index 8a64231ed4..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/DeployUtils.java +++ /dev/null @@ -1,555 +0,0 @@ -/* Copyright 2005-2006 Tim Fennell - * - * 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 org.openidentityplatform.openam.click.service; - -import org.openidentityplatform.openam.click.util.ClickUtils; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; - -/** - *

ResolverUtil is used to locate classes that are available in the/a class path and meet - * arbitrary conditions. The two most common conditions are that a class implements/extends - * another class, or that is it annotated with a specific annotation. However, through the use - * of the {@link org.openidentityplatform.openam.click.service.DeployUtils.Test} class it is possible to search using arbitrary conditions.

- * - *

A ClassLoader is used to locate all locations (directories and jar files) in the class - * path that contain classes within certain packages, and then to load those classes and - * check them. By default the ClassLoader returned by - * {@code Thread.currentThread().getContextClassLoader()} is used, but this can be overridden - * by calling {@link #setClassLoader(ClassLoader)} prior to invoking any of the {@code find()} - * methods.

- * - *

General searches are initiated by calling the - * {@link #find(net.sourceforge.stripes.util.ResolverUtil.Test, String)} ()} method and supplying - * a package name and a Test instance. This will cause the named package and all sub-packages - * to be scanned for classes that meet the test. There are also utility methods for the common - * use cases of scanning multiple packages for extensions of particular classes, or classes - * annotated with a specific annotation.

- * - *

The standard usage pattern for the ResolverUtil class is as follows:

- * - *
- *ResolverUtil<ActionBean> resolver = new ResolverUtil<ActionBean>();
- *resolver.findImplementation(ActionBean.class, pkg1, pkg2);
- *resolver.find(new CustomTest(), pkg1);
- *resolver.find(new CustomTest(), pkg2);
- *Collection<ActionBean> beans = resolver.getClasses();
- *
- * - * This class was copied and adapted from the Stripes Framework - - * Stripes. - * - * @author Tim Fennell - */ -class DeployUtils { - - // -------------------------------------------------------------- Constants - - /** The magic header that indicates a JAR (ZIP) file. */ - private static final byte[] JAR_MAGIC = { 'P', 'K', 3, 4 }; - - // -------------------------------------------------------------- Variables - - /** The set of matches being accumulated. */ - private List matches = new ArrayList(); - - /** The log service to log output to. */ - private LogService logService; - - /** - * The ClassLoader to use when looking for classes. If null then the ClassLoader - * returned by Thread.currentThread().getContextClassLoader() will be used. - */ - private ClassLoader classloader; - - // ----------------------------------------------------------- Constructors - - /** - * Create a new DeployUtils instance. - * - * @param logService the logService to log output to - */ - public DeployUtils(LogService logService) { - this.logService = logService; - } - - // --------------------------------------------------------- Public Methods - - /** - * Provides access to the resources discovered so far. If no calls have been - * made to any of the {@code find()} methods, this list will be empty. - * - * @return the list of resources that have been discovered. - */ - public List getResources() { - return matches; - } - - /** - * Returns the classloader that will be used for scanning for classes. If no explicit - * ClassLoader has been set by the calling, the context class loader will be used. - * - * @return the ClassLoader that will be used to scan for classes - */ - public ClassLoader getClassLoader() { - return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader; - } - - /** - * Sets an explicit ClassLoader that should be used when scanning for classes. If none - * is set then the context classloader will be used. - * - * @param classloader a ClassLoader to use when scanning for classes - */ - public void setClassLoader(ClassLoader classloader) { - this.classloader = classloader; - } - - /** - * Attempt to discover resources inside the given directory. Accumulated - * resources can be accessed by calling {@link #getResources()}. - * - * @param dirs one or more directories to scan (including sub-directories) - * for resources - * @return instance of DeployUtils allows for chaining calls - */ - public org.openidentityplatform.openam.click.service.DeployUtils findResources(String... dirs) { - if (dirs == null) { - return this; - } - - org.openidentityplatform.openam.click.service.DeployUtils.Test test = new org.openidentityplatform.openam.click.service.DeployUtils.IsDeployable(); - for (String dir : dirs) { - find(test, dir); - } - - return this; - } - - /** - * Scans for classes starting at the package provided and descending into subpackages. - * Each class is offered up to the Test as it is discovered, and if the Test returns - * true the class is retained. Accumulated classes can be fetched by calling - * {@link #getClasses()}. - * - * @param test an instance of {@link org.openidentityplatform.openam.click.service.DeployUtils.Test} that will be used to filter classes - * @param packageName the name of the package from which to start scanning for - * classes, e.g. {@code net.sourceforge.stripes} - */ - public org.openidentityplatform.openam.click.service.DeployUtils find(org.openidentityplatform.openam.click.service.DeployUtils.Test test, String packageName) { - String path = getPackagePath(packageName); - - try { - List urls = Collections.list(getClassLoader().getResources(path)); - for (URL url : urls) { - List children = listClassResources(url, path); - for (String child : children) { - addIfMatching(test, child); - } - } - - } catch (IOException ioe) { - logService.error("could not read package: " + packageName + " -- " + ioe); - } - - return this; - } - - // ------------------------------------------------------ Protected Methods - - /** - * Recursively list all resources under the given URL that appear to define a Java class. - * Matching resources will have a name that ends in ".class" and have a relative path such that - * each segment of the path is a valid Java identifier. The resource paths returned will be - * relative to the URL and begin with the specified path. - * - * @param url The URL of the parent resource to search. - * @param path The path with which each matching resource path must begin, relative to the URL. - * @return A list of matching resources. The list may be empty. - * @throws IOException - */ - protected List listClassResources(URL url, String path) throws IOException { - if (logService.isDebugEnabled()) { - logService.debug("listing classes in " + url); - } - - InputStream is = null; - try { - List resources = new ArrayList(); - - // First, try to find the URL of a JAR file containing the requested - // resource. If a JAR file is found, then we'll list child resources - // by reading the JAR. - URL jarUrl = findJarForResource(url, path); - if (jarUrl != null) { - // example jarUrl : jar:c:/dev/mylib.jar - is = jarUrl.openStream(); - resources = listClassResources(new JarInputStream(is), path); - - } else { - List children = new ArrayList(); - try { - if (isJar(url)) { - // example url : jar:c:/dev/mylib.jar/META-INF/resources - - // Some versions of JBoss VFS might give a JAR stream even - // if the resource referenced by the URL isn't actually a JAR - is = url.openStream(); - JarInputStream jarInput = new JarInputStream(is); - for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;) { - if (logService.isTraceEnabled()) { - logService.trace("jar entry: " + entry.getName()); - } - - if (isRelevantResource(entry.getName())) { - children.add(entry.getName()); - } - } - jarInput.close(); - } else { - // Some servlet containers allow reading from "directory" - // resources like text file, listing the child resources - // one per line. - is = url.openStream(); - - // There is the possibility that a file doesn't have an - // extension and would be seen as a directory. Guard against - // that by checking that the url is a file and adding it - // to the list of resources - File file = new File(url.getFile()); - if (file.isFile()) { - if (isRelevantResource(file.getName())) { - resources.add(path); - } - } else { - BufferedReader reader = new BufferedReader(new InputStreamReader(is)); - for (String line; (line = reader.readLine()) != null;) { - if (logService.isTraceEnabled()) { - logService.trace("reader entry: " + line); - } - if (isRelevantResource(line)) { - children.add(line); - } - } - reader.close(); - } - } - - } catch (FileNotFoundException e) { - /* - * For file URLs the openStream() call might fail, depending on the servlet - * container, because directories can't be opened for reading. If that happens, - * then list the directory directly instead. - */ - if ("file".equals(url.getProtocol())) { - File file = new File(url.getFile()); - if (file.isDirectory()) { - children = Arrays.asList(file.list(new FilenameFilter() { - public boolean accept(File dir, String name) { - return isRelevantResource(name); - } - })); - } - } else { - // No idea where the exception came from so log it - logService.error("could not deploy the resources from" - + " the url '" + url + "'. You will need to" - + " manually included resources from this url in" - + " your application."); - } - } - - // The URL prefix to use when recursively listing child resources - String prefix = url.toExternalForm(); - if (!prefix.endsWith("/")) { - prefix = prefix + "/"; - } - - // Iterate over each immediate child, adding classes and recursing into directories - for (String child : children) { - String resourcePath = path + "/" + child; - if (child.indexOf(".") != -1) { - if (logService.isTraceEnabled()) { - logService.trace("found deployable resource: " + resourcePath); - } - resources.add(resourcePath); - - } else { - URL childUrl = new URL(prefix + child); - resources.addAll(listClassResources(childUrl, resourcePath)); - } - } - } - - return resources; - - } finally { - ClickUtils.close(is); - } - } - - /** - * List the names of the entries in the given {@link JarInputStream} that begin with the - * specified {@code path}. Entries will match with or without a leading slash. - * - * @param jar The JAR input stream - * @param path The leading path to match - * @return The names of all the matching entries - * @throws IOException - */ - protected List listClassResources(JarInputStream jar, String path) throws IOException { - // Include the leading and trailing slash when matching names - if (!path.startsWith("/")) { - path = "/" + path; - } - if (!path.endsWith("/")) { - path = path + "/"; - } - - // Iterate over the entries and collect those that begin with the requested path - List resources = new ArrayList(); - for (JarEntry entry; (entry = jar.getNextJarEntry()) != null;) { - if (!entry.isDirectory()) { - // Add leading slash if it's missing - String name = entry.getName(); - if (!name.startsWith("/")) { - name = "/" + name; - } - - // Check resource name - if (name.startsWith(path)) { - resources.add(name.substring(1)); // Trim leading slash - } - } - } - - return resources; - } - - /** - * Attempts to deconstruct the given URL to find a JAR file containing the resource referenced - * by the URL. That is, assuming the URL references a JAR entry, this method will return a URL - * that references the JAR file containing the entry. If the JAR cannot be located, then this - * method returns null. - * - * @param url The URL of the JAR entry. - * @param path The path by which the URL was requested from the class loader. - * @return The URL of the JAR file, if one is found. Null if not. - * @throws MalformedURLException - */ - protected URL findJarForResource(URL url, String path) throws MalformedURLException { - if (logService.isTraceEnabled()) { - logService.trace("find jar url: " + url); - } - - // If the file part of the URL is itself a URL, then that URL probably points to the JAR - try { - for (;;) { - url = new URL(url.getFile()); - if (logService.isTraceEnabled()) { - logService.trace("inner url: " + url); - } - } - - } catch (MalformedURLException e) { - // This will happen at some point and serves a break in the loop - } - - // Look for the .jar extension and chop off everything after that - StringBuilder jarUrl = new StringBuilder(url.toExternalForm()); - int index = jarUrl.lastIndexOf(".jar"); - if (index >= 0) { - jarUrl.setLength(index + 4); - if (logService.isTraceEnabled()) { - logService.trace("extracted jar url: " + jarUrl); - } - } else { - if (logService.isTraceEnabled()) { - logService.trace("not a jar: " + jarUrl); - } - return null; - } - - // Try to open and test it - try { - URL testUrl = new URL(jarUrl.toString()); - if (isJar(testUrl)) { - return testUrl; - - } else { - // WebLogic fix: check if the URL's file exists in the filesystem. - if (logService.isTraceEnabled()) { - logService.trace("not a jar: " + jarUrl); - } - - jarUrl.replace(0, jarUrl.length(), testUrl.getFile()); - - // File name might be URL-encoded - File file = new File(ClickUtils.decodeURL(jarUrl.toString())); - if (file.exists()) { - if (logService.isTraceEnabled()) { - logService.trace("trying real file: " + file.getAbsolutePath()); - } - testUrl = file.toURI().toURL(); - if (isJar(testUrl)) { - return testUrl; - } - } - } - - } catch (MalformedURLException e) { - logService.warn("invalid jar url: " + e.getMessage()); - } - - if (logService.isTraceEnabled()) { - logService.trace("not a jar: " + jarUrl); - } - return null; - } - - /** - * Converts a Java package name to a path that can be looked up with a call to - * {@link ClassLoader#getResources(String)}. - * - * @param packageName The Java package name to convert to a path - */ - protected String getPackagePath(String packageName) { - return packageName == null ? null : packageName.replace('.', '/'); - } - - /** - * Returns true if the name of a resource (file or directory) is one that matters in the search - * for classes. Relevant resources would be class files themselves (file names that end with - * ".class") and directories that might be a Java package name segment (java identifiers). - * - * @param resourceName The resource name, without path information - */ - protected boolean isRelevantResource(String resourceName) { - return resourceName != null && !resourceName.equals(""); - } - - /** - * Returns true if the resource located at the given URL is a JAR file. - * - * @param url The URL of the resource to test. - */ - protected boolean isJar(URL url) { - return isJar(url, new byte[JAR_MAGIC.length]); - } - - /** - * Returns true if the resource located at the given URL is a JAR file. - * - * @param url The URL of the resource to test. - * @param buffer A buffer into which the first few bytes of the resource are read. The buffer - * must be at least the size of {@link #JAR_MAGIC}. (The same buffer may be reused - * for multiple calls as an optimization.) - */ - protected boolean isJar(URL url, byte[] buffer) { - InputStream is = null; - try { - is = url.openStream(); - if (is.read(buffer, 0, JAR_MAGIC.length) != JAR_MAGIC.length) { - return false; - } - if (Arrays.equals(buffer, JAR_MAGIC)) { - if (logService.isInfoEnabled()) { - logService.info("found jar: " + url); - } - return true; - } - - } catch (Exception e) { - // Failure to read the stream means this is not a JAR - - } finally { - ClickUtils.close(is); - } - - return false; - } - - /** - * Add the class designated by the fully qualified class name provided to the set of - * resolved classes if and only if it is approved by the Test supplied. - * - * @param test the test used to determine if the class matches - * @param fqn the fully qualified name of a class - */ - protected void addIfMatching(org.openidentityplatform.openam.click.service.DeployUtils.Test test, String fqn) { - try { - if (test.matches(fqn)) { - matches.add(fqn); - } - } catch (Throwable t) { - logService.error("could not examine class '" + fqn + "'" + " due to a " - + t.getClass().getName() + " with message: " + t.getMessage()); - } - } - - // ---------------------------------------------------------- Inner classes - - /** - * A simple interface that specifies how to test classes to determine if they - * are to be included in the results produced by the ResolverUtil. - */ - static interface Test { - /** - * Will be called repeatedly with candidate classes. Must return True if a class - * is to be included in the results, false otherwise. - */ - boolean matches(String resource); - } - - /** - * This test matches deployable resources. - */ - static class IsDeployable implements org.openidentityplatform.openam.click.service.DeployUtils.Test { - - /** - * Default constructor. - */ - IsDeployable() { - } - - /** - * Return true if the given resource should be included in the results, - * false otherwise. - * - * @param resource the resource to be included in the results - * @return true if the resource should be included in the results, false - * otherwise - */ - public boolean matches(String resource) { - // If a resource is found, it must be deployed - return true; - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/FileUploadService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/FileUploadService.java deleted file mode 100644 index 5fc61f5c02..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/FileUploadService.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import java.util.List; - -import jakarta.servlet.ServletContext; -import jakarta.servlet.http.HttpServletRequest; - -import org.apache.click.service.ConfigService; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileUploadException; - -/** - * Provides a file upload service interface. - */ -public interface FileUploadService { - - /** The attribute key used for storing an upload exception. */ - public static final String UPLOAD_EXCEPTION = "org.apache.click.service.upload_exception"; - - /** - * Initialize the FileUploadService with the given application servlet context. - *

- * This method is invoked after the FileUploadService has been constructed. - *

- * Note you can access {@link ConfigService} by invoking - * {@link org.openidentityplatform.openam.click.util.ClickUtils#getConfigService(jakarta.servlet.ServletContext)} - * - * @param servletContext the application servlet context - * @throws Exception if an error occurs initializing the FileUploadService - */ - public void onInit(ServletContext servletContext) throws Exception; - - /** - * Destroy the FileUploadService. - */ - public void onDestroy(); - - /** - * Return a parsed list of FileItem from the request. - * - * @param request the servlet request - * @return the list of FileItem instances parsed from the request - * @throws FileUploadException if request cannot be parsed - */ - public List parseRequest(HttpServletRequest request) throws FileUploadException; - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/LogService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/LogService.java deleted file mode 100644 index 75495344af..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/LogService.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import jakarta.servlet.ServletContext; - -/** - * Provides a logging service for the Click runtime. - * - *

Configuration

- * The default {@link org.apache.click.service.LogService} implementation is {@link ConsoleLogService}. - *

- * You can instruct Click to use a different implementation by adding - * the following element to your click.xml configuration file. - * - *

- * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- * <click-app charset="UTF-8">
- *
- *     <pages package="org.apache.click.examples.page"/>
- *
- *     <log-service classname="com.mycorp.CustomLogService"/>
- *
- * </click-app> 
- * - * The class com.mycorp.CustomLogService might be defined as follows: - * - *
- * package com.mycorp;
- *
- * public class CustomLogService extends ConsoleLogService {
- *
- *     protected void log(int level, String message, Throwable error) {
- *         // Add custom logic
- *         ...
- *
- *         super.log(level, message, error);
- *     }
- * } 
- */ -public interface LogService { - - /** - * Initialize the LogService with the given application servlet context. - *

- * This method is invoked after the LogService has been constructed. - * - * @param servletContext the application servlet context - * @throws Exception if an error occurs initializing the LogService - */ - public void onInit(ServletContext servletContext) throws Exception; - - /** - * Destroy the LogService. - */ - public void onDestroy(); - - /** - * Log the given message at [debug] logging level. - * - * @param message the message to log - */ - public void debug(Object message); - - /** - * Log the given message and error at [debug] logging level. - * - * @param message the message to log - * @param error the error to log - */ - public void debug(Object message, Throwable error); - - /** - * Log the given message at [error] logging level. - * - * @param message the message to log - */ - public void error(Object message); - - /** - * Log the given message and error at [error] logging level. - * - * @param message the message to log - * @param error the error to log - */ - public void error(Object message, Throwable error); - - /** - * Log the given message at [info] logging level. - * - * @param message the message to log - */ - public void info(Object message); - - /** - * Log the given message and error at [info] logging level. - * - * @param message the message to log - * @param error the error to log - */ - public void info(Object message, Throwable error); - - /** - * Log the given message at [trace] logging level. - * - * @param message the message to log - */ - public void trace(Object message); - - /** - * Log the given message and error at [trace] logging level. - * - * @param message the message to log - * @param error the error to log - */ - public void trace(Object message, Throwable error); - - /** - * Log the given message at [warn] logging level. - * - * @param message the message to log - */ - public void warn(Object message); - - /** - * Log the given message and error at [warn] logging level. - * - * @param message the message to log - * @param error the error to log - */ - public void warn(Object message, Throwable error); - - /** - * Return true if [debug] level logging is enabled. - * - * @return true if [debug] level logging is enabled - */ - public boolean isDebugEnabled(); - - /** - * Return true if [info] level logging is enabled. - * - * @return true if [info] level logging is enabled - */ - public boolean isInfoEnabled(); - - /** - * Return true if [trace] level logging is enabled. - * - * @return true if [trace] level logging is enabled - */ - public boolean isTraceEnabled(); - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/MessagesMapService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/MessagesMapService.java deleted file mode 100644 index 8cd1ec13f2..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/MessagesMapService.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import org.apache.click.service.DefaultMessagesMapService; - -import java.util.Locale; -import java.util.Map; - -import jakarta.servlet.ServletContext; - -/** - * Provides a messages map factory service for the Click runtime. - * - *

Configuration

- * The default {@link org.apache.click.service.MessagesMapService} implementation is {@link DefaultMessagesMapService}. - *

- * You can instruct Click to use a different implementation by adding - * the following element to your click.xml configuration file. - * - *

- * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- * <click-app charset="UTF-8">
- *
- *     <pages package="org.apache.click.examples.page"/>
- *
- *     <messages-map-service classname="com.mycorp.CustomMessagesMapService"/>
- *
- * </click-app> 
- * - * The class com.mycorp.CustomMessagesMapService might be defined as follows: - * - *
- * package com.mycorp;
- *
- * public class CustomMessagesMapService implements MessagesMapService {
- *
- *     public Map createMessagesMap(Class<?> baseClass, String globalResource, Locale locale) {
- *         return new MyMessagesMap(baseClass, globalResource, locale);
- *     }
- * } 
- */ -public interface MessagesMapService { - - /** - * Initialize the MessagesMapService with the given application servlet context. - *

- * This method is invoked after the MessagesMapService has been constructed. - * - * @param servletContext the application servlet context - * @throws Exception if an error occurs initializing the LogService - */ - public void onInit(ServletContext servletContext) throws Exception; - - /** - * Destroy the MessagesMapService. - */ - public void onDestroy(); - - /** - * Return a new messages map for the given baseClass (a page or control) - * and the given global resource bundle name. - * - * @param baseClass the target class - * @param globalResource the global resource bundle name - * @param locale the users Locale - * @return a new messages map with the messages for the target. - */ - public Map createMessagesMap(Class baseClass, - String globalResource, Locale locale); -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ResourceService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ResourceService.java deleted file mode 100644 index 50c6cf5c2e..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/ResourceService.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import org.apache.click.service.ClickResourceService; - -import java.io.IOException; - -import jakarta.servlet.ServletContext; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -/** - * Provides a static resource service interface. - * - *

Configuration

- * The default ResourceService is {@link ClickResourceService}. - *

- * However you can instruct Click to use a different implementation by adding - * the following element to your click.xml configuration file. - * - *

- * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- * <click-app charset="UTF-8">
- *
- *     <pages package="com.mycorp.page"/>
- *
- *     <resource-service classname="com.mycorp.service.DynamicResourceService">
- *
- * </click-app> 
- */ -public interface ResourceService { - - /** - * Initialize the ResourceService with the given application configuration - * service instance. - *

- * This method is invoked after the ResourceService has been constructed. - * - * @param servletContext the application servlet context - * @throws IOException if an IO error occurs initializing the service - */ - public void onInit(ServletContext servletContext) throws IOException; - - /** - * Destroy the ResourceService. - */ - public void onDestroy(); - - /** - * Return true if the request is for a static resource. - * - * @param request the servlet request - * @return true if the request is for a static resource - */ - public boolean isResourceRequest(HttpServletRequest request); - - /** - * Render the resource request to the given servlet resource response. - * - * @param request the servlet resource request - * @param response the servlet response - * @throws IOException if an IO error occurs rendering the resource - */ - public void renderResource(HttpServletRequest request, HttpServletResponse response) - throws IOException; - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/TemplateService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/TemplateService.java deleted file mode 100644 index 0aee0e7c61..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/TemplateService.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import java.io.IOException; -import java.io.Writer; -import java.util.Map; - -import jakarta.servlet.ServletContext; - -import org.openidentityplatform.openam.click.Page; -import org.apache.click.service.ConfigService; -import org.apache.click.service.TemplateException; -import org.apache.click.service.VelocityTemplateService; - -/** - * Provides a templating service interface. - * - *

Configuration

- * The default TemplateService is {@link VelocityTemplateService}. - *

- * However you can instruct Click to use a different implementation by adding - * the following element to your click.xml configuration file. - * - *

- * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
- * <click-app charset="UTF-8">
- *
- *     <pages package="org.apache.click.examples.page"/>
- *
- *     <template-service classname="org.apache.click.extras.service.FreemarkerTemplateService">
- *
- * </click-app> 
- */ -public interface TemplateService { - - /** - * Initialize the TemplateService with the given application configuration - * service instance. - *

- * This method is invoked after the TemplateService has been constructed. - *

- * Note you can access {@link ConfigService} by invoking - * {@link org.openidentityplatform.openam.click.util.ClickUtils#getConfigService(jakarta.servlet.ServletContext)} - * - * @param servletContext the application servlet context - * @throws Exception if an error occurs initializing the Template Service - */ - public void onInit(ServletContext servletContext) throws Exception; - - /** - * Destroy the TemplateService. - */ - public void onDestroy(); - - /** - * Render the given page to the writer. - * - * @param page the page template to render - * @param model the model to merge with the template and render - * @param writer the writer to send the merged template and model data to - * @throws IOException if an IO error occurs - * @throws TemplateException if template error occurs - */ - public void renderTemplate(Page page, Map model, Writer writer) - throws IOException, TemplateException; - - /** - * Render the given template and model to the writer. - * - * @param templatePath the path of the template to render - * @param model the model to merge with the template and render - * @param writer the writer to send the merged template and model data to - * @throws IOException if an IO error occurs - * @throws TemplateException if template error occurs - */ - public void renderTemplate(String templatePath, Map model, Writer writer) - throws IOException, TemplateException; - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/VelocityTemplateService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/VelocityTemplateService.java deleted file mode 100644 index f6d95b62f4..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/VelocityTemplateService.java +++ /dev/null @@ -1,785 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Writer; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Iterator; -import java.util.Map; -import java.util.Properties; -import java.util.TreeMap; - -import jakarta.servlet.ServletContext; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Page; - -import org.apache.click.service.TemplateException; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.openidentityplatform.openam.click.util.ErrorReport; -import org.apache.commons.lang.Validate; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.VelocityEngine; -import org.apache.velocity.exception.ParseErrorException; -import org.apache.velocity.exception.ResourceNotFoundException; -import org.apache.velocity.exception.TemplateInitException; -import org.apache.velocity.io.VelocityWriter; -import org.apache.velocity.runtime.RuntimeConstants; -import org.apache.velocity.runtime.RuntimeServices; -import org.apache.velocity.runtime.log.LogChute; -import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; -import org.openidentityplatform.openam.velocity.tools.view.WebappResourceLoader; -import org.apache.velocity.util.SimplePool; - -/** - * Provides a Velocity TemplateService class. - *

- * Velocity provides a simple to use, but powerful and performant templating engine - * for the Click Framework. The Velocity templating engine is configured and accessed - * by this VelocityTemplateService class. - * Velocity is the default templating engine used by Click and the Velocity class - * dependencies are included in the standard Click JAR file. - *

- * You can also instruct Click to use a different template service implementation. - * Please see {@link org.apache.click.service.TemplateService} for more details. - *

- * To see how to use the Velocity templating language please see the - * Velocity Users Guide. - * - *

Velocity Configuration

- * The VelocityTemplateService is the default template service used by Click, - * so it does not require any specific configuration. - * However if you wanted to configure this service specifically in your - * click.xml configuration file you would add the following XML element. - * - *
- * <template-service classname="VelocityTemplateService"/> 
- * - *

Velocity Properties

- * - * The Velocity runtime engine is configured through a series of properties when the - * VelocityTemplateService is initialized. The default Velocity properties set are: - * - *
- * resource.loader=webapp, class
- *
- * webapp.resource.loader.class=org.apache.velocity.tools.view.WebappResourceLoader
- * webapp.resource.loader.cache=[true|false]   #depending on application mode
- * webapp.resource.loader.modificationCheckInterval=0 #depending on application mode
- *
- * class.resource.loader.class=org.apache.velocity.runtime.loader.ClasspathResourceLoader
- * class.resource.loader.cache=[true|false]   #depending on application mode
- * class.resource.loader.modificationCheckInterval=0 #depending on application mode
- *
- * velocimacro.library.autoreload=[true|false] #depending on application mode
- * velocimacro.library=click/VM_global_library.vm
- * 
- * - * This service uses the Velocity Tools WebappResourceLoader for loading templates. - * This avoids issues associate with using the Velocity FileResourceLoader on JEE - * application servers. - *

- * See the Velocity - * Developer Guide - * for details about these properties. Note when the application is in trace mode - * the Velocity properties used will be logged on startup. - *

- * If you want to add some of your own Velocity properties, or replace Click's - * properties, add a velocity.properties file in the WEB-INF - * directory. Click will automatically pick up this file and load these properties. - *

- * As a example say we have our own Velocity macro library called - * mycorp.vm we can override the default velocimacro.library - * property by adding a WEB-INF/velocity.properties file to our web - * application. In this file we would then define the property as: - * - *

- * velocimacro.library=mycorp.vm 
- * - * Note do not place Velocity macros under the WEB-INF directory as the Velocity - * ResourceManager will not be able to load them. - *

- * The simplest way to set your own macro file is to add a file named macro.vm - * under your web application's root directory. At startup Click will first check to see - * if this file exists, and if it does it will use it instead of click/VM_global_library.vm. - * - *

Application Modes and Caching

- * - *

Production and Profile Mode

- * - * When the Click application is in production or profile mode Velocity caching - * is enabled. With caching enables page templates and macro files are loaded and - * parsed once and then are cached for use with later requests. When in - * production or profile mode the following Velocity runtime - * properties are set: - * - *
- * webapp.resource.loader.cache=true
- * webapp.resource.loader.modificationCheckInterval=0
- *
- * class.resource.loader.cache=true
- * class.resource.loader.modificationCheckInterval=0
- *
- * velocimacro.library.autoreload=false 
- * - * When running in these modes the {@link org.apache.click.service.ConsoleLogService} will be configured - * to use - * - *

Development and Debug Modes

- * - * When the Click application is in development, debug or trace - * modes Velocity caching is disabled. When caching is disabled page templates - * and macro files are reloaded and parsed when ever they changed. With caching - * disabled the following Velocity - * runtime properties are set: - * - *
- * webapp.resource.loader.cache=false
- *
- * class.resource.loader.cache=false
- *
- * velocimacro.library.autoreload=true 
- * - * Disabling caching is useful for application development where you can edit page - * templates on a running application server and see the changes immediately. - *

- * Please Note Velocity caching should be used for production as Velocity - * template reloading is much much slower and the process of parsing and - * introspecting templates and macros can use a lot of memory. - * - *

Velocity Logging

- * Velocity logging is very verbose at the best of times, so this service - * keeps the logging level at ERROR in all modes except trace - * mode where the Velocity logging level is set to WARN. - *

- * If you are having issues with some Velocity page templates or macros please - * switch the application mode into trace so you can see the warning - * messages provided. - *

- * To support the use of Click LogService classes inside the Velocity - * runtime a {@link VelocityTemplateService.LogChuteAdapter} class is provided. This class wraps the - * Click LogService with a Velocity LogChute so the Velocity runtime can - * use it for logging messages to. - *

- * If you are using LogServices other than {@link ConsoleLogService} you will - * probably configure that service to filter out Velocity's verbose INFO - * level messages. - */ -public class VelocityTemplateService implements TemplateService { - - // -------------------------------------------------------------- Constants - - /** The logger instance Velocity application attribute key. */ - private static final String LOG_INSTANCE = - VelocityTemplateService.LogChuteAdapter.class.getName() + ".LOG_INSTANCE"; - - /** The velocity logger instance Velocity application attribute key. */ - private static final String LOG_LEVEL = - VelocityTemplateService.LogChuteAdapter.class.getName() + ".LOG_LEVEL"; - - /** - * The default velocity properties filename:   - * "/WEB-INF/velocity.properties". - */ - protected static final String DEFAULT_TEMPLATE_PROPS = "/WEB-INF/velocity.properties"; - - /** The click error page template path. */ - protected static final String ERROR_PAGE_PATH = "/click/error.htm"; - - /** - * The user supplied macro file name:   "macro.vm". - */ - protected static final String MACRO_VM_FILE_NAME = "macro.vm"; - - /** The click not found page template path. */ - protected static final String NOT_FOUND_PAGE_PATH = "/click/not-found.htm"; - - /** - * The global Velocity macro file path:   - * "/click/VM_global_library.vm". - */ - protected static final String VM_FILE_PATH = "/click/VM_global_library.vm"; - - /** The Velocity writer buffer size. */ - protected static final int WRITER_BUFFER_SIZE = 32 * 1024; - - // -------------------------------------------------------------- Variables - - /** The application configuration service. */ - protected ConfigService configService; - - /** The /click/error.htm page template has been deployed. */ - protected boolean deployedErrorTemplate; - - /** The /click/not-found.htm page template has been deployed. */ - protected boolean deployedNotFoundTemplate; - - /** The VelocityEngine instance. */ - protected VelocityEngine velocityEngine = new VelocityEngine(); - - /** Cache of velocity writers. */ - protected SimplePool writerPool = new SimplePool(40); - - // --------------------------------------------------------- Public Methods - - /** - * @see TemplateService#onInit(ServletContext) - * - * @param servletContext the application servlet velocityContext - * @throws Exception if an error occurs initializing the Template Service - */ - public void onInit(ServletContext servletContext) throws Exception { - - Validate.notNull(servletContext, "Null servletContext parameter"); - - this.configService = ClickUtils.getConfigService(servletContext); - - // Set the velocity logging level - Integer logLevel = getInitLogLevel(); - - velocityEngine.setApplicationAttribute(LOG_LEVEL, logLevel); - - // Set ConfigService instance for LogChuteAdapter - velocityEngine.setApplicationAttribute(org.openidentityplatform.openam.click.service.ConfigService.class.getName(), - configService); - - // Set ServletContext instance for WebappResourceLoader - velocityEngine.setApplicationAttribute(ServletContext.class.getName(), - configService.getServletContext()); - - // Load velocity properties - Properties properties = getInitProperties(); - - // Initialize VelocityEngine - velocityEngine.init(properties); - - // Turn down the Velocity logging level - if (configService.isProductionMode() || configService.isProfileMode()) { - VelocityTemplateService.LogChuteAdapter logChuteAdapter = (VelocityTemplateService.LogChuteAdapter) - velocityEngine.getApplicationAttribute(LOG_INSTANCE); - if (logChuteAdapter != null) { - logChuteAdapter.logLevel = LogChute.WARN_ID; - } - } - - // Attempt to load click error page and not found templates from the - // web click directory - try { - velocityEngine.getTemplate(ERROR_PAGE_PATH); - deployedErrorTemplate = true; - } catch (ResourceNotFoundException rnfe) { - } - - try { - velocityEngine.getTemplate(NOT_FOUND_PAGE_PATH); - deployedNotFoundTemplate = true; - } catch (ResourceNotFoundException rnfe) { - } - } - - /** - * @see org.apache.click.service.TemplateService#onDestroy() - */ - public void onDestroy() { - // Dereference any allocated objects - velocityEngine = null; - writerPool = null; - configService = null; - } - - /** - * @see org.openidentityplatform.openam.click.service.TemplateService#renderTemplate(Page, Map, Writer) - * - * @param page the page template to render - * @param model the model to merge with the template and render - * @param writer the writer to send the merged template and model data to - * @throws IOException if an IO error occurs - * @throws TemplateException if template error occurs - */ - public void renderTemplate(Page page, Map model, Writer writer) - throws IOException, TemplateException { - - String templatePath = page.getTemplate(); - - if (!deployedErrorTemplate && templatePath.equals(ERROR_PAGE_PATH)) { - templatePath = "META-INF/resources" + ERROR_PAGE_PATH; - } - if (!deployedErrorTemplate && templatePath.equals(NOT_FOUND_PAGE_PATH)) { - templatePath = "META-INF/resources" + NOT_FOUND_PAGE_PATH; - } - - internalRenderTemplate(templatePath, page, model, writer); - } - - /** - * @see TemplateService#renderTemplate(String, Map, Writer) - * - * @param templatePath the path of the template to render - * @param model the model to merge with the template and render - * @param writer the writer to send the merged template and model data to - * @throws IOException if an IO error occurs - * @throws TemplateException if an error occurs - */ - public void renderTemplate(String templatePath, Map model, Writer writer) - throws IOException, TemplateException { - - internalRenderTemplate(templatePath, null, model, writer); - } - - // Protected Methods ------------------------------------------------------ - - /** - * Return the Velocity Engine initialization log level. - * - * @return the Velocity Engine initialization log level - */ - protected Integer getInitLogLevel() { - - // Set Velocity log levels - Integer initLogLevel = LogChute.ERROR_ID; - String mode = configService.getApplicationMode(); - - if (mode.equals(org.apache.click.service.ConfigService.MODE_DEVELOPMENT)) { - initLogLevel = LogChute.WARN_ID; - - } else if (mode.equals(org.apache.click.service.ConfigService.MODE_DEBUG)) { - initLogLevel = LogChute.WARN_ID; - - } else if (mode.equals(org.apache.click.service.ConfigService.MODE_TRACE)) { - initLogLevel = LogChute.INFO_ID; - } - - return initLogLevel; - } - - /** - * Return the Velocity Engine initialization properties. - * - * @return the Velocity Engine initialization properties - * @throws MalformedURLException if a resource cannot be loaded - */ - @SuppressWarnings("unchecked") - protected Properties getInitProperties() throws MalformedURLException { - - final Properties velProps = new Properties(); - - // Set default velocity runtime properties. - - velProps.setProperty(RuntimeConstants.RESOURCE_LOADER, "webapp, class"); - velProps.setProperty("webapp.resource.loader.class", - WebappResourceLoader.class.getName()); - velProps.setProperty("class.resource.loader.class", - ClasspathResourceLoader.class.getName()); - - if (configService.isProductionMode() || configService.isProfileMode()) { - velProps.put("webapp.resource.loader.cache", "true"); - velProps.put("webapp.resource.loader.modificationCheckInterval", "0"); - velProps.put("class.resource.loader.cache", "true"); - velProps.put("class.resource.loader.modificationCheckInterval", "0"); - velProps.put("velocimacro.library.autoreload", "false"); - - } else { - velProps.put("webapp.resource.loader.cache", "false"); - velProps.put("class.resource.loader.cache", "false"); - velProps.put("velocimacro.library.autoreload", "true"); - } - - velProps.put(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, - VelocityTemplateService.LogChuteAdapter.class.getName()); - - velProps.put("directive.if.tostring.nullcheck", "false"); - - // Use 'macro.vm' exists set it as default VM library - ServletContext servletContext = configService.getServletContext(); - URL macroURL = servletContext.getResource("/" + MACRO_VM_FILE_NAME); - if (macroURL != null) { - velProps.put("velocimacro.library", "/" + MACRO_VM_FILE_NAME); - - } else { - // Else use '/click/VM_global_library.vm' if available. - URL globalMacroURL = servletContext.getResource(VM_FILE_PATH); - if (globalMacroURL != null) { - velProps.put("velocimacro.library", VM_FILE_PATH); - - } else { - // Else use '/WEB-INF/classes/macro.vm' if available. - String webInfMacroPath = "/WEB-INF/classes/macro.vm"; - URL webInfMacroURL = servletContext.getResource(webInfMacroPath); - if (webInfMacroURL != null) { - velProps.put("velocimacro.library", webInfMacroPath); - } - } - } - - // Set the character encoding - String charset = configService.getCharset(); - if (charset != null) { - velProps.put("input.encoding", charset); - } - - // Load user velocity properties. - Properties userProperties = new Properties(); - - String filename = DEFAULT_TEMPLATE_PROPS; - - InputStream inputStream = servletContext.getResourceAsStream(filename); - - if (inputStream != null) { - try { - userProperties.load(inputStream); - - } catch (IOException ioe) { - String message = "error loading velocity properties file: " - + filename; - configService.getLogService().error(message, ioe); - - } finally { - try { - inputStream.close(); - } catch (IOException ioe) { - // ignore - } - } - } - - // Add user properties. - Iterator iterator = userProperties.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = (Map.Entry) iterator.next(); - - Object pop = velProps.put(entry.getKey(), entry.getValue()); - - LogService logService = configService.getLogService(); - if (pop != null && logService.isDebugEnabled()) { - String message = "user defined property '" + entry.getKey() - + "=" + entry.getValue() + "' replaced default property '" - + entry.getKey() + "=" + pop + "'"; - logService.debug(message); - } - } - - ConfigService configService = ClickUtils.getConfigService(servletContext); - LogService logger = configService.getLogService(); - if (logger.isTraceEnabled()) { - TreeMap sortedPropMap = new TreeMap(); - - Iterator i = velProps.entrySet().iterator(); - while (i.hasNext()) { - Map.Entry entry = (Map.Entry) i.next(); - sortedPropMap.put(entry.getKey(), entry.getValue()); - } - - logger.trace("velocity properties: " + sortedPropMap); - } - - return velProps; - } - - /** - * Provides the underlying Velocity template rendering. - * - * @param templatePath the template path to render - * @param page the page template to render - * @param model the model to merge with the template and render - * @param writer the writer to send the merged template and model data to - * @throws IOException if an IO error occurs - * @throws TemplateException if template error occurs - */ - protected void internalRenderTemplate(String templatePath, - Page page, - Map model, - Writer writer) - throws IOException, TemplateException { - - final VelocityContext velocityContext = new VelocityContext(model); - - // May throw parsing error if template could not be obtained - Template template = null; - VelocityWriter velocityWriter = null; - try { - String charset = configService.getCharset(); - if (charset != null) { - template = velocityEngine.getTemplate(templatePath, charset); - - } else { - template = velocityEngine.getTemplate(templatePath); - } - - velocityWriter = (VelocityWriter) writerPool.get(); - - if (velocityWriter == null) { - velocityWriter = - new VelocityWriter(writer, WRITER_BUFFER_SIZE, true); - - } else { - velocityWriter.recycle(writer); - } - - template.merge(velocityContext, velocityWriter); - - } catch (ParseErrorException pee) { - TemplateException te = new TemplateException(pee, - pee.getTemplateName(), - pee.getLineNumber(), - pee.getColumnNumber()); - - // Exception occurred merging template and model. It is possible - // that some output has already been written, so we will append the - // error report to the previous output. - ErrorReport errorReport = - new ErrorReport(te, - ((page != null) ? page.getClass() : null), - configService.isProductionMode(), - Context.getThreadLocalContext().getRequest(), - configService.getServletContext()); - - - if (velocityWriter == null) { - velocityWriter = - new VelocityWriter(writer, WRITER_BUFFER_SIZE, true); - } - - velocityWriter.write(errorReport.toString()); - - throw te; - - } catch (TemplateInitException tie) { - TemplateException te = new TemplateException(tie, - tie.getTemplateName(), - tie.getLineNumber(), - tie.getColumnNumber()); - - // Exception occurred merging template and model. It is possible - // that some output has already been written, so we will append the - // error report to the previous output. - ErrorReport errorReport = - new ErrorReport(te, - ((page != null) ? page.getClass() : null), - configService.isProductionMode(), - Context.getThreadLocalContext().getRequest(), - configService.getServletContext()); - - - if (velocityWriter == null) { - velocityWriter = - new VelocityWriter(writer, WRITER_BUFFER_SIZE, true); - } - - velocityWriter.write(errorReport.toString()); - - throw te; - - } catch (Exception error) { - TemplateException te = new TemplateException(error); - - // Exception occurred merging template and model. It is possible - // that some output has already been written, so we will append the - // error report to the previous output. - ErrorReport errorReport = - new ErrorReport(te, - ((page != null) ? page.getClass() : null), - configService.isProductionMode(), - Context.getThreadLocalContext().getRequest() , - configService.getServletContext()); - - if (velocityWriter == null) { - velocityWriter = - new VelocityWriter(writer, WRITER_BUFFER_SIZE, true); - } - - velocityWriter.write(errorReport.toString()); - - throw te; - - } finally { - if (velocityWriter != null) { - // flush and put back into the pool don't close to allow - // us to play nicely with others. - velocityWriter.flush(); - - // Clear the VelocityWriter's reference to its - // internal Writer to allow the latter - // to be GC'd while vw is pooled. - velocityWriter.recycle(null); - - writerPool.put(velocityWriter); - } - - writer.flush(); - writer.close(); - } - } - - // Inner Classes ---------------------------------------------------------- - - /** - * Provides a Velocity LogChute adapter class around the application - * log service to enable the Velocity Runtime to log to the application - * LogService. - *

- * Please see the {@link VelocityTemplateService} class for more details on - * Velocity logging. - *

- * PLEASE NOTE this class is not for public use. - */ - public static class LogChuteAdapter implements LogChute { - - private static final String MSG_PREFIX = "Velocity: "; - - /** The application configuration service. */ - protected org.apache.click.service.ConfigService configService; - - /** The application log service. */ - protected LogService logger; - - /** The log level. */ - protected int logLevel; - - /** - * Initialize the logger instance for the Velocity runtime. This method - * is invoked by the Velocity runtime. - * - * @see LogChute#init(RuntimeServices) - * - * @param rs the Velocity runtime services - * @throws Exception if an initialization error occurs - */ - public void init(RuntimeServices rs) throws Exception { - - // Swap the default logger instance with the global application logger - ConfigService configService = (ConfigService) - rs.getApplicationAttribute(ConfigService.class.getName()); - - this.logger = configService.getLogService(); - - Integer level = (Integer) rs.getApplicationAttribute(LOG_LEVEL); - if (level != null) { - logLevel = level; - - } else { - String msg = "Could not retrieve LOG_LEVEL from Runtime attributes"; - throw new IllegalStateException(msg); - } - - rs.setApplicationAttribute(LOG_INSTANCE, this); - } - - /** - * Tell whether or not a log level is enabled. - * - * @see LogChute#isLevelEnabled(int) - * - * @param level the logging level to test - * @return true if the given logging level is enabled - */ - public boolean isLevelEnabled(int level) { - if (level <= LogChute.TRACE_ID && logger.isTraceEnabled()) { - return true; - - } else if (level <= LogChute.DEBUG_ID && logger.isDebugEnabled()) { - return true; - - } else if (level <= LogChute.INFO_ID && logger.isInfoEnabled()) { - return true; - - } else if (level == LogChute.WARN_ID || level == LogChute.ERROR_ID) { - return true; - - } else { - return false; - } - } - - /** - * Log the given message and optional error at the specified logging level. - * - * @see LogChute#log(int, java.lang.String) - * - * @param level the logging level - * @param message the message to log - */ - public void log(int level, String message) { - if (level < logLevel) { - return; - } - - if (level == TRACE_ID) { - logger.trace(MSG_PREFIX + message); - - } else if (level == DEBUG_ID) { - logger.debug(MSG_PREFIX + message); - - } else if (level == INFO_ID) { - logger.info(MSG_PREFIX + message); - - } else if (level == WARN_ID) { - logger.warn(MSG_PREFIX + message); - - } else if (level == ERROR_ID) { - logger.error(MSG_PREFIX + message); - - } else { - throw new IllegalArgumentException("Invalid log level: " + level); - } - } - - /** - * Log the given message and optional error at the specified logging level. - *

- * If you need to customise the Click and Velocity runtime logging for your - * application modify this method. - * - * @see LogChute#log(int, java.lang.String, java.lang.Throwable) - * - * @param level the logging level - * @param message the message to log - * @param error the optional error to log - */ - public void log(int level, String message, Throwable error) { - if (level < logLevel) { - return; - } - - if (level == TRACE_ID) { - logger.trace(MSG_PREFIX + message, error); - - } else if (level == DEBUG_ID) { - logger.debug(MSG_PREFIX + message, error); - - } else if (level == INFO_ID) { - logger.info(MSG_PREFIX + message, error); - - } else if (level == WARN_ID) { - logger.warn(MSG_PREFIX + message, error); - - } else if (level == ERROR_ID) { - logger.error(MSG_PREFIX + message, error); - - } else { - throw new IllegalArgumentException("Invalid log level: " + level); - } - } - - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/XmlConfigService.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/service/XmlConfigService.java deleted file mode 100644 index e3a79d2105..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/service/XmlConfigService.java +++ /dev/null @@ -1,2241 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.service; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.StringTokenizer; -import java.util.TreeMap; - -import jakarta.servlet.ServletContext; - -import jakarta.servlet.http.HttpServletRequest; -import ognl.Ognl; - -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileUploadException; -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.Page; -import org.openidentityplatform.openam.click.PageInterceptor; -import org.apache.click.service.CommonsFileUploadService; -import org.apache.click.util.Bindable; -import org.openidentityplatform.openam.click.util.ClickUtils; -import org.apache.click.util.Format; -import org.apache.click.util.HtmlStringBuffer; -import org.apache.click.util.PropertyUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.Validate; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.EntityResolver; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -/** - * Provides a Click XML configuration service class. - *

- * This class reads Click configuration information from a file named - * click.xml. The service will first lookup the click.xml - * under the applications WEB-INF directory, and if not found - * attempt to load the configuration file from the classpath root. - *

- * Configuring Click through the click.xml file is the most common - * technique. - *

- * However you can instruct Click to use a different service implementation. - * Please see {@link org.apache.click.service.ConfigService} for more details. - */ -public class XmlConfigService implements ConfigService, EntityResolver { - - /** The name of the Click logger:   "org.apache.click". */ - static final String CLICK_LOGGER = "org.apache.click"; - - /** The click deployment directory path:   "/click". */ - static final String CLICK_PATH = "/click"; - - /** The default common page headers. */ - static final Map DEFAULT_HEADERS; - - /** - * The default velocity properties filename:   - * "/WEB-INF/velocity.properties". - */ - static final String DEFAULT_VEL_PROPS = "/WEB-INF/velocity.properties"; - - /** The click DTD file name:   "click.dtd". */ - static final String DTD_FILE_NAME = "click.dtd"; - - /** - * The resource path of the click DTD file:   - * "/org/apache/click/click.dtd". - */ - static final String DTD_FILE_PATH = "/org/apache/click/" + DTD_FILE_NAME; - - /** - * The user supplied macro file name:   "macro.vm". - */ - static final String MACRO_VM_FILE_NAME = "macro.vm"; - - /** The production application mode. */ - static final int PRODUCTION = 0; - - /** The profile application mode. */ - static final int PROFILE = 1; - - /** The development application mode. */ - static final int DEVELOPMENT = 2; - - /** The debug application mode. */ - static final int DEBUG = 3; - - /** The trace application mode. */ - static final int TRACE = 4; - - static final String[] MODE_VALUES = - { "production", "profile", "development", "debug", "trace" }; - - private static final Object PAGE_LOAD_LOCK = new Object(); - - /** - * The name of the Velocity logger:   "org.apache.velocity". - */ - static final String VELOCITY_LOGGER = "org.apache.velocity"; - - /** - * The global Velocity macro file name:   - * "VM_global_library.vm". - */ - static final String VM_FILE_NAME = "VM_global_library.vm"; - - /** Initialize the default headers. */ - static { - DEFAULT_HEADERS = new HashMap(); - DEFAULT_HEADERS.put("Pragma", "no-cache"); - DEFAULT_HEADERS.put("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0"); - DEFAULT_HEADERS.put("Expires", new Date(1L)); - } - - private static final String GOOGLE_APP_ENGINE = "Google App Engine"; - - // ------------------------------------------------ Package Private Members - - /** The Map of global page headers. */ - Map commonHeaders; - - /** The page automapping override page class for path list. */ - final List excludesList = new ArrayList(); - - /** The map of ClickApp.PageElm keyed on path. */ - final Map pageByPathMap = new HashMap(); - - /** The map of ClickApp.PageElm keyed on class. */ - final Map pageByClassMap = new HashMap(); - - /** The list of page packages. */ - final List pagePackages = new ArrayList(); - - // -------------------------------------------------------- Private Members - - /** The automatically bind controls, request parameters and models flag. */ - private ConfigService.AutoBinding autobinding; - - /** The Commons FileUpload service class. */ - private FileUploadService fileUploadService; - - /** The format class. */ - private Class formatClass; - - /** The character encoding of this application. */ - private String charset; - - /** The default application locale.*/ - private Locale locale; - - /** The application log service. */ - private LogService logService; - - /** - * The application mode: - * [ PRODUCTION | PROFILE | DEVELOPMENT | DEBUG | TRACE ]. - */ - private int mode; - - /** The list of application page interceptor instances. */ - private List pageInterceptorConfigList - = new ArrayList<>(); - - /** The ServletContext instance. */ - private ServletContext servletContext; - - /** The application ResourceService. */ - private ResourceService resourceService; - - /** The application TemplateService. */ - private TemplateService templateService; - - /** The application TemplateService. */ - private MessagesMapService messagesMapService; - - /** Flag indicating whether Click is running on Google App Engine. */ - private boolean onGoogleAppEngine = false; - - // --------------------------------------------------------- Public Methods - - /** - * @see org.openidentityplatform.openam.click.service.ConfigService#onInit(ServletContext) - * - * @param servletContext the application servlet context - * @throws Exception if an error occurs initializing the application - */ - public void onInit(ServletContext servletContext) throws Exception { - - Validate.notNull(servletContext, "Null servletContext parameter"); - - this.servletContext = servletContext; - - onGoogleAppEngine = servletContext.getServerInfo().startsWith(GOOGLE_APP_ENGINE); - - // Set default logService early to log errors when services fail. - logService = new ConsoleLogService(); - messagesMapService = new DefaultMessagesMapService(); - - InputStream inputStream = ClickUtils.getClickConfig(servletContext); - - try { - Document document = ClickUtils.buildDocument(inputStream, this); - - Element rootElm = document.getDocumentElement(); - - // Load the log service - loadLogService(rootElm); - - // Load the application mode and set the logger levels - loadMode(rootElm); - - if (logService.isInfoEnabled()) { - logService.info("*** Initializing Click " + ClickUtils.getClickVersion() - + " in " + getApplicationMode() + " mode ***"); - - String msg = "initialized LogService: " + logService.getClass().getName(); - getLogService().info(msg); - } - - // Deploy click resources - deployFiles(rootElm); - - // Load the format class - loadFormatClass(rootElm); - - // Load the common headers - loadHeaders(rootElm); - - // Load the pages - loadPages(rootElm); - - // Load the error and not-found pages - loadDefaultPages(); - - // Load the charset - loadCharset(rootElm); - - // Load the locale - loadLocale(rootElm); - - // Load the File Upload service - loadFileUploadService(rootElm); - - // Load the Templating service - loadTemplateService(rootElm); - - // Load the Resource service - loadResourceService(rootElm); - - // Load the Messages Map service - loadMessagesMapService(rootElm); - - // Load the PageInterceptors - loadPageInterceptors(rootElm); - - } finally { - ClickUtils.close(inputStream); - } - } - - /** - * @see org.apache.click.service.ConfigService#onDestroy() - */ - public void onDestroy() { - if (getFileUploadService() != null) { - getFileUploadService().onDestroy(); - } - if (getTemplateService() != null) { - getTemplateService().onDestroy(); - } - if (getResourceService() != null) { - getResourceService().onDestroy(); - } - if (getMessagesMapService() != null) { - getMessagesMapService().onDestroy(); - } - if (getLogService() != null) { - getLogService().onDestroy(); - } - } - - // --------------------------------------------------------- Public Methods - - /** - * Return the application mode String value:   ["production", - * "profile", "development", "debug"]. - * - * @return the application mode String value - */ - public String getApplicationMode() { - return MODE_VALUES[mode]; - } - - /** - * @see org.apache.click.service.ConfigService#getCharset() - * - * @return the application character encoding - */ - public String getCharset() { - return charset; - } - - /** - * @return the FileUpload service - * @see org.openidentityplatform.openam.click.service.ConfigService#getFileUploadService() - */ - public org.openidentityplatform.openam.click.service.FileUploadService getFileUploadService() { - return fileUploadService; - } - - /** - * @see org.apache.click.service.ConfigService#getLogService() - * - * @return the application log service. - */ - public LogService getLogService() { - return logService; - } - - /** - * @return the resource service - * @see org.apache.click.service.ConfigService#getResourceService() - */ - public org.openidentityplatform.openam.click.service.ResourceService getResourceService() { - return resourceService; - } - - /** - * @see org.apache.click.service.ConfigService#getTemplateService() - * - * @return the template service - */ - public TemplateService getTemplateService() { - return templateService; - } - - /** - * @see org.apache.click.service.ConfigService#getMessagesMapService() - * - * @return the messages map service - */ - public MessagesMapService getMessagesMapService() { - return messagesMapService; - } - - /** - * @see org.apache.click.service.ConfigService#createFormat() - * - * @return a new format object - */ - public Format createFormat() { - try { - return formatClass.newInstance(); - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * @see org.apache.click.service.ConfigService#getLocale() - * - * @return the application locale - */ - public Locale getLocale() { - return locale; - } - - /** - * @return the Page field auto binding mode { PUBLIC, ANNOTATION, NONE } - * @see org.apache.click.service.ConfigService#getAutoBindingMode() - */ - public ConfigService.AutoBinding getAutoBindingMode() { - return autobinding; - } - - /** - * @see org.apache.click.service.ConfigService#isProductionMode() - * - * @return true if the application is in "production" mode - */ - public boolean isProductionMode() { - return (mode == PRODUCTION); - } - - /** - * @see org.apache.click.service.ConfigService#isProfileMode() - * - * @return true if the application is in "profile" mode - */ - public boolean isProfileMode() { - return (mode == PROFILE); - } - - /** - * @see org.apache.click.service.ConfigService#isJspPage(String) - * - * @param path the Page ".htm" path - * @return true if JSP exists for the given ".htm" path - */ - public boolean isJspPage(String path) { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - int index = StringUtils.lastIndexOf(path, "."); - if (index > 0) { - buffer.append(path.substring(0, index)); - } else { - buffer.append(path); - } - buffer.append(".jsp"); - return pageByPathMap.containsKey(buffer.toString()); - } - - /** - * Return true if the given path is a Page class template, false - * otherwise. By default this method returns true if the path has a - * .htm or .jsp extension. - *

- * If you want to map alternative templates besides .htm and - * .jsp files you can override this method and provide extra - * checks against the given path whether it should be added as a - * template or not. - *

- * Below is an example showing how to allow .xml paths to - * be recognized as Page class templates. - * - *

-     * public class MyConfigService extends XmlConfigService {
-     *
-     *     protected boolean isTemplate(String path) {
-     *         // invoke default implementation
-     *         boolean isTemplate = super.isTemplate(path);
-     *
-     *         if (!isTemplate) {
-     *             // If path has an .xml extension, mark it as a template
-     *             isTemplate = path.endsWith(".xml");
-     *         }
-     *         return isTemplate;
-     *     }
-     * } 
- * - * Here is an example web.xml showing how to configure a custom - * ConfigService through the context parameter config-service-class. - * We also map *.xml requests to be routed through ClickServlet: - * - *
-     * <web-app xmlns="http://java.sun.com/xml/ns/j2ee"
-     *   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-     *   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
-     *   version="2.4">
-     *
-     *   <!-- Specify a custom ConfigService through the context param 'config-service-class' -->
-     *   <context-param>
-     *     <param-name>config-service-class</param-name>
-     *     <param-value>com.mycorp.service.MyConfigSerivce</param-value>
-     *   </context-param>
-     *
-     *   <servlet>
-     *     <servlet-name>ClickServlet</servlet-name>
-     *     <servlet-class>org.apache.click.ClickServlet</servlet-class>
-     *     <load-on-startup>0</load-on-startup>
-     *   </servlet>
-     *
-     *   <!-- NOTE: we still map the .htm extension -->
-     *   <servlet-mapping>
-     *     <servlet-name>ClickServlet</servlet-name>
-     *     <url-pattern>*.htm</url-pattern>
-     *   </servlet-mapping>
-     *
-     *   <!-- NOTE: we also map .xml extension in order to route xml requests to the ClickServlet -->
-     *   <servlet-mapping>
-     *     <servlet-name>ClickServlet</servlet-name>
-     *     <url-pattern>*.xml</url-pattern>
-     *   </servlet-mapping>
-     *
-     *   ...
-     *
-     * </web-app> 
- * - * Please note: even though you can add extra template mappings by - * overriding this method, it is still recommended to keep the default - * .htm mapping by invoking super.isTemplate(String). - * The reason being that Click ships with some default templates such as - * {@link org.apache.click.service.ConfigService#ERROR_PATH} and {@link org.apache.click.service.ConfigService#NOT_FOUND_PATH} - * that must be mapped as .htm. - *

- * Please see the ConfigService javadoc for details - * on how to configure a custom ConfigService implementation. - * - * @see org.apache.click.service.ConfigService#isTemplate(String) - * - * @param path the path to check if it is a Page class template or not - * @return true if the path is a Page class template, false otherwise - */ - public boolean isTemplate(String path) { - if (path.endsWith(".htm") || path.endsWith(".jsp")) { - return true; - } - return false; - } - - /** - * @see org.apache.click.service.ConfigService#getPageClass(String) - * - * @param path the page path - * @return the page class for the given path or null if no class is found - */ - public Class getPageClass(String path) { - - // If in production or profile mode. - if (mode <= PROFILE) { - XmlConfigService.PageElm page = (XmlConfigService.PageElm) pageByPathMap.get(path); - if (page == null) { - String jspPath = StringUtils.replace(path, ".htm", ".jsp"); - page = (XmlConfigService.PageElm) pageByPathMap.get(jspPath); - } - - if (page != null) { - return page.getPageClass(); - } else { - return null; - } - - // Else in development, debug or trace mode - } else { - - synchronized (PAGE_LOAD_LOCK) { - XmlConfigService.PageElm page = (XmlConfigService.PageElm) pageByPathMap.get(path); - if (page == null) { - String jspPath = StringUtils.replace(path, ".htm", ".jsp"); - page = (XmlConfigService.PageElm) pageByPathMap.get(jspPath); - } - - if (page != null) { - return page.getPageClass(); - } - - Class pageClass = null; - - try { - URL resource = servletContext.getResource(path); - if (resource != null) { - for (int i = 0; i < pagePackages.size(); i++) { - String pagesPackage = pagePackages.get(i).toString(); - - pageClass = getPageClass(path, pagesPackage); - - if (pageClass != null) { - page = new XmlConfigService.PageElm(path, - pageClass, - commonHeaders, - autobinding); - - pageByPathMap.put(page.getPath(), page); - addToClassMap(page); - - if (logService.isDebugEnabled()) { - String msg = path + " -> " + pageClass.getName(); - logService.debug(msg); - } - - break; - } - } - } - } catch (MalformedURLException e) { - //ignore - } - return pageClass; - } - } - } - - /** - * @see org.apache.click.service.ConfigService#getPagePath(Class) - * - * @param pageClass the page class - * @return path the page path or null if no path is found - * @throws IllegalArgumentException if the Page Class is not configured - * with a unique path - */ - public String getPagePath(Class pageClass) { - Object object = pageByClassMap.get(pageClass); - - if (object instanceof XmlConfigService.PageElm) { - XmlConfigService.PageElm page = (XmlConfigService.PageElm) object; - return page.getPath(); - - } else if (object instanceof List) { - HtmlStringBuffer buffer = new HtmlStringBuffer(); - buffer.append("Page class resolves to multiple paths: "); - buffer.append(pageClass.getName()); - buffer.append(" -> ["); - for (Iterator it = ((List) object).iterator(); it.hasNext();) { - XmlConfigService.PageElm pageElm = (XmlConfigService.PageElm) it.next(); - buffer.append(pageElm.getPath()); - if (it.hasNext()) { - buffer.append(", "); - } - } - buffer.append("]\nUse Context.createPage(String), or Context.getPageClass(String) instead."); - throw new IllegalArgumentException(buffer.toString()); - - } else { - return null; - } - } - - /** - * @see org.apache.click.service.ConfigService#getPageClassList() - * - * @return the list of configured page classes - */ - public List getPageClassList() { - List classList = new ArrayList(pageByClassMap.size()); - - Iterator i = pageByClassMap.keySet().iterator(); - while (i.hasNext()) { - Class pageClass = (Class) i.next(); - classList.add(pageClass); - } - - return classList; - } - - /** - * @see org.apache.click.service.ConfigService#getPageHeaders(String) - * - * @param path the path of the page - * @return a Map of headers for the given page path - */ - public Map getPageHeaders(String path) { - XmlConfigService.PageElm page = (XmlConfigService.PageElm) pageByPathMap.get(path); - if (page == null) { - String jspPath = StringUtils.replace(path, ".htm", ".jsp"); - page = (XmlConfigService.PageElm) pageByPathMap.get(jspPath); - } - - if (page != null) { - return page.getHeaders(); - } else { - return null; - } - } - - /** - * @see org.apache.click.service.ConfigService#getNotFoundPageClass() - * - * @return the page not found Page Class - */ - public Class getNotFoundPageClass() { - XmlConfigService.PageElm page = (XmlConfigService.PageElm) pageByPathMap.get(NOT_FOUND_PATH); - - if (page != null) { - return page.getPageClass(); - - } else { - return Page.class; - } - } - - /** - * @see org.apache.click.service.ConfigService#getErrorPageClass() - * - * @return the error handling page Page Class - */ - public Class getErrorPageClass() { - PageElm page = (XmlConfigService.PageElm) pageByPathMap.get(ERROR_PATH); - - if (page != null) { - return page.getPageClass(); - - } else { - return org.openidentityplatform.openam.click.util.ErrorPage.class; - } - } - - /** - * @see org.apache.click.service.ConfigService#getPageField(Class, String) - * - * @param pageClass the page class - * @param fieldName the name of the field - * @return the public field of the pageClass with the given name or null - */ - public Field getPageField(Class pageClass, String fieldName) { - return getPageFields(pageClass).get(fieldName); - } - - /** - * @see org.apache.click.service.ConfigService#getPageFieldArray(Class) - * - * @param pageClass the page class - * @return an array public fields for the given page class - */ - public Field[] getPageFieldArray(Class pageClass) { - Object object = pageByClassMap.get(pageClass); - - if (object instanceof XmlConfigService.PageElm) { - XmlConfigService.PageElm page = (XmlConfigService.PageElm) object; - return page.getFieldArray(); - - } else if (object instanceof List) { - List list = (List) object; - XmlConfigService.PageElm page = (XmlConfigService.PageElm) list.get(0); - return page.getFieldArray(); - - } else { - return null; - } - } - - /** - * @see org.apache.click.service.ConfigService#getPageFields(Class) - * - * @param pageClass the page class - * @return a Map of public fields for the given page class - */ - public Map getPageFields(Class pageClass) { - Object object = pageByClassMap.get(pageClass); - - if (object instanceof XmlConfigService.PageElm) { - XmlConfigService.PageElm page = (XmlConfigService.PageElm) object; - return page.getFields(); - - } else if (object instanceof List) { - List list = (List) object; - XmlConfigService.PageElm page = (XmlConfigService.PageElm) list.get(0); - return page.getFields(); - - } else { - return Collections.emptyMap(); - } - } - - /** - * @see org.apache.click.service.ConfigService#getPageInterceptors() - * - * @return the list of configured PageInterceptor instances - */ - public List getPageInterceptors() { - - if (pageInterceptorConfigList.isEmpty()) { - return Collections.emptyList(); - } - - List interceptorList = - new ArrayList(pageInterceptorConfigList.size()); - - for (XmlConfigService.PageInterceptorConfig pageInterceptorConfig : pageInterceptorConfigList) { - interceptorList.add(pageInterceptorConfig.getPageInterceptor()); - } - - return interceptorList; - } - - /** - * @see ConfigService#getServletContext() - * - * @return the application servlet context - */ - public ServletContext getServletContext() { - return servletContext; - } - - /** - * This method resolves the click.dtd for the XML parser using the - * classpath resource: /org/apache/click/click.dtd. - * - * @see EntityResolver#resolveEntity(String, String) - * - * @param publicId the DTD public id - * @param systemId the DTD system id - * @return resolved entity DTD input stream - * @throws SAXException if an error occurs parsing the document - * @throws IOException if an error occurs reading the document - */ - public InputSource resolveEntity(String publicId, String systemId) - throws SAXException, IOException { - - InputStream inputStream = ClickUtils.getResourceAsStream(DTD_FILE_PATH, getClass()); - - if (inputStream != null) { - return new InputSource(inputStream); - } else { - throw new IOException("could not load resource: " + DTD_FILE_PATH); - } - } - - // ------------------------------------------------------ Protected Methods - - /** - * Find and return the page class for the specified pagePath and - * pagesPackage. - *

- * For example if the pagePath is '/edit-customer.htm' and - * package is 'com.mycorp', the matching page class will be: - * com.mycorp.EditCustomer or com.mycorp.EditCustomerPage. - *

- * If the page path is '/admin/add-customer.htm' and package is - * 'com.mycorp', the matching page class will be: - * com.mycorp.admin.AddCustomer or - * com.mycorp.admin.AddCustomerPage. - * - * @param pagePath the path used for matching against a page class name - * @param pagesPackage the package of the page class - * @return the page class for the specified pagePath and pagesPackage - */ - protected Class getPageClass(String pagePath, String pagesPackage) { - // To understand this method lets walk through an example as the - // code plays out. Imagine this method is called with the arguments: - // pagePath='/pages/edit-customer.htm' - // pagesPackage='org.apache.click' - - String packageName = ""; - if (StringUtils.isNotBlank(pagesPackage)) { - // Append period after package - // packageName = 'org.apache.click.' - packageName = pagesPackage + "."; - } - - String className = ""; - - // Strip off extension. - // path = '/pages/edit-customer' - String path = pagePath.substring(0, pagePath.lastIndexOf(".")); - - // If page is excluded return the excluded class - Class excludePageClass = getExcludesPageClass(path); - if (excludePageClass != null) { - return excludePageClass; - } - - // Build complete packageName. - // packageName = 'org.apache.click.pages.' - // className = 'edit-customer' - if (path.indexOf("/") != -1) { - StringTokenizer tokenizer = new StringTokenizer(path, "/"); - while (tokenizer.hasMoreTokens()) { - String token = tokenizer.nextToken(); - if (tokenizer.hasMoreTokens()) { - packageName = packageName + token + "."; - } else { - className = token; - } - } - } else { - className = path; - } - - // CamelCase className. - // className = 'EditCustomer' - StringTokenizer tokenizer = new StringTokenizer(className, "_-"); - className = ""; - while (tokenizer.hasMoreTokens()) { - String token = tokenizer.nextToken(); - token = Character.toUpperCase(token.charAt(0)) + token.substring(1); - className += token; - } - - // className = 'org.apache.click.pages.EditCustomer' - className = packageName + className; - - Class pageClass = null; - try { - // Attempt to load class. - pageClass = ClickUtils.classForName(className); - - if (!Page.class.isAssignableFrom(pageClass)) { - String msg = "Automapped page class " + className - + " is not a subclass of org.apache.click.Page"; - throw new RuntimeException(msg); - } - - } catch (ClassNotFoundException cnfe) { - - boolean classFound = false; - - // Append "Page" to className and attempt to load class again. - // className = 'org.apache.click.pages.EditCustomerPage' - if (!className.endsWith("Page")) { - String classNameWithPage = className + "Page"; - try { - // Attempt to load class. - pageClass = ClickUtils.classForName(classNameWithPage); - - if (!Page.class.isAssignableFrom(pageClass)) { - String msg = "Automapped page class " + classNameWithPage - + " is not a subclass of org.apache.click.Page"; - throw new RuntimeException(msg); - } - - classFound = true; - - } catch (ClassNotFoundException cnfe2) { - } - } - - if (!classFound) { - if (logService.isDebugEnabled()) { - logService.debug(pagePath + " -> CLASS NOT FOUND"); - } - if (logService.isTraceEnabled()) { - logService.trace("class not found: " + className); - } - } - } - - return pageClass; - } - - /** - * Returns true if Click resources (JavaScript, CSS, images etc) packaged - * in jars can be deployed to the root directory of the webapp, false - * otherwise. - *

- * By default this method will return false in restricted environments where - * write access to the underlying file system is disallowed. Example - * environments where write access is not allowed include the WebLogic JEE - * server and Google App Engine. (Note: WebLogic provides the property - * "Archived Real Path Enabled" that controls whether web - * applications can access the file system or not. See the Click user manual - * for details). - * - * @return true if resources can be deployed, false otherwise - */ - protected boolean isResourcesDeployable() { - // Only deploy if writes are allowed - if (onGoogleAppEngine) { - // Google doesn't allow writes - return false; - } - return ClickUtils.isResourcesDeployable(servletContext); - } - - // ------------------------------------------------ Package Private Methods - - /** - * Loads all Click Pages defined in the click.xml file, including - * manually defined Pages, auto mapped Pages and excluded Pages. - * - * @param rootElm the root xml element containing the configuration - * @throws java.lang.ClassNotFoundException if the specified Page class can - * not be found on the classpath - */ - void loadPages(Element rootElm) throws ClassNotFoundException { - List pagesList = ClickUtils.getChildren(rootElm, "pages"); - - if (pagesList.isEmpty()) { - String msg = "required configuration 'pages' element missing."; - throw new RuntimeException(msg); - } - - List templates = getTemplateFiles(); - - for (int i = 0; i < pagesList.size(); i++) { - - Element pagesElm = (Element) pagesList.get(i); - - // Determine whether to use automapping - boolean automap = true; - String automapStr = pagesElm.getAttribute("automapping"); - if (StringUtils.isBlank(automapStr)) { - automapStr = "true"; - } - - if ("true".equalsIgnoreCase(automapStr)) { - automap = true; - } else if ("false".equalsIgnoreCase(automapStr)) { - automap = false; - } else { - String msg = "Invalid pages automapping attribute: " + automapStr; - throw new RuntimeException(msg); - } - - // Determine whether to use autobinding. - String autobindingStr = pagesElm.getAttribute("autobinding"); - if (StringUtils.isBlank(autobindingStr)) { - autobinding = AutoBinding.DEFAULT; - } else { - - if ("annotation".equalsIgnoreCase(autobindingStr)) { - autobinding = AutoBinding.ANNOTATION; - - } else if ("public".equalsIgnoreCase(autobindingStr)) { - autobinding = AutoBinding.DEFAULT; - - } else if ("default".equalsIgnoreCase(autobindingStr)) { - autobinding = AutoBinding.DEFAULT; - - } else if ("none".equalsIgnoreCase(autobindingStr)) { - autobinding = AutoBinding.NONE; - - // Provided for backward compatibility - } else if ("true".equalsIgnoreCase(autobindingStr)) { - autobinding = AutoBinding.DEFAULT; - - // Provided for backward compatibility - } else if ("false".equalsIgnoreCase(autobindingStr)) { - autobinding = AutoBinding.NONE; - - } else { - String msg = "Invalid pages autobinding attribute: " - + autobindingStr; - throw new RuntimeException(msg); - } - } - - // TODO: if autobinding is set to false an there are multiple pages how should this be handled - // Perhaps autobinding should be moved to and be a application wide setting? - // However the way its implemented above is probably fine for backward compatibility - // purposes, meaning the last defined autobinding wins - - String pagesPackage = pagesElm.getAttribute("package"); - if (StringUtils.isBlank(pagesPackage)) { - pagesPackage = ""; - } - - pagesPackage = pagesPackage.trim(); - if (pagesPackage.endsWith(".") && pagesPackage.length() > 1) { - pagesPackage = - pagesPackage.substring(0, pagesPackage.length() - 2); - } - - // Add the pages package to the list of page packages - pagePackages.add(pagesPackage); - - buildManualPageMapping(pagesElm, pagesPackage); - - if (automap) { - buildAutoPageMapping(pagesElm, pagesPackage, templates); - } - } - - buildClassMap(); - } - - /** - * Add manually defined Pages to the {@link #pageByPathMap}. - * - * @param pagesElm the xml element containing manually defined Pages - * @param pagesPackage the pages package prefix - * - * @throws java.lang.ClassNotFoundException if the specified Page class can - * not be found on the classpath - */ - void buildManualPageMapping(Element pagesElm, String pagesPackage) throws ClassNotFoundException { - - List pageList = ClickUtils.getChildren(pagesElm, "page"); - - if (!pageList.isEmpty() && logService.isDebugEnabled()) { - logService.debug("click.xml pages:"); - } - - for (int i = 0; i < pageList.size(); i++) { - Element pageElm = (Element) pageList.get(i); - - XmlConfigService.PageElm page = - new PageElm(pageElm, - pagesPackage, - commonHeaders, - autobinding); - - pageByPathMap.put(page.getPath(), page); - - if (logService.isDebugEnabled()) { - String msg = - page.getPath() + " -> " + page.getPageClass().getName(); - logService.debug(msg); - } - } - } - - /** - * Build the {@link #pageByPathMap} by associating template files with - * matching Java classes found on the classpath. - *

- * This method also rebuilds the {@link #excludesList}. This list contains - * URL paths that should not be auto-mapped. - * - * @param pagesElm the xml element containing the excluded URL paths - * @param pagesPackage the pages package prefix - * @param templates the list of templates to map to Page classes - */ - void buildAutoPageMapping(Element pagesElm, String pagesPackage, List templates) throws ClassNotFoundException { - - // Build list of automap path page class overrides - excludesList.clear(); - for (Iterator i = ClickUtils.getChildren(pagesElm, "excludes").iterator(); - i.hasNext();) { - - excludesList.add(new XmlConfigService.ExcludesElm((Element) i.next())); - } - - if (logService.isDebugEnabled()) { - logService.debug("automapped pages:"); - } - - for (int i = 0; i < templates.size(); i++) { - String pagePath = (String) templates.get(i); - - if (!pageByPathMap.containsKey(pagePath)) { - - Class pageClass = getPageClass(pagePath, pagesPackage); - - if (pageClass != null) { - XmlConfigService.PageElm page = - new XmlConfigService.PageElm(pagePath, - pageClass, - commonHeaders, - autobinding); - - pageByPathMap.put(page.getPath(), page); - - if (logService.isDebugEnabled()) { - String msg = - pagePath + " -> " + pageClass.getName(); - logService.debug(msg); - } - } - } - } - } - - /** - * Build the {@link #pageByClassMap} from the {@link #pageByPathMap} and - * delegate to {@link #addToClassMap(org.openidentityplatform.openam.click.service.XmlConfigService.PageElm)}. - */ - void buildClassMap() { - // Build pages by class map - for (Iterator i = pageByPathMap.values().iterator(); i.hasNext();) { - XmlConfigService.PageElm page = (XmlConfigService.PageElm) i.next(); - addToClassMap(page); - } - } - - /** - * Add the specified page to the {@link #pageByClassMap} where the Map's key - * holds the Page class and value holds the {@link XmlConfigService.PageElm}. - * - * @param page the PageElm containing metadata about a specific page - */ - void addToClassMap(XmlConfigService.PageElm page) { - Object value = pageByClassMap.get(page.pageClass); - if (value == null) { - pageByClassMap.put(page.pageClass, page); - - } else if (value instanceof List) { - ((List) value).add(page); - - } else if (value instanceof XmlConfigService.PageElm) { - List list = new ArrayList(); - list.add(value); - list.add(page); - pageByClassMap.put(page.pageClass, list); - - } else { - // should never occur - throw new IllegalStateException(); - } - } - - /** - * Load the Page headers from the specified xml element. - * - * @param parentElm the element to load the headers from - * @return the map of Page headers - */ - static Map loadHeadersMap(Element parentElm) { - Map headersMap = new HashMap(); - - List headerList = ClickUtils.getChildren(parentElm, "header"); - - for (int i = 0, size = headerList.size(); i < size; i++) { - Element header = (Element) headerList.get(i); - - String name = header.getAttribute("name"); - String type = header.getAttribute("type"); - String propertyValue = header.getAttribute("value"); - - Object value = null; - - if ("".equals(type) || "String".equalsIgnoreCase(type)) { - value = propertyValue; - } else if ("Integer".equalsIgnoreCase(type)) { - value = Integer.valueOf(propertyValue); - } else if ("Date".equalsIgnoreCase(type)) { - value = new Date(Long.parseLong(propertyValue)); - } else { - value = null; - String message = - "Invalid property type [String|Integer|Date]: " - + type; - throw new IllegalArgumentException(message); - } - - headersMap.put(name, value); - } - - return headersMap; - } - - // -------------------------------------------------------- Private Methods - - private Element getResourceRootElement(String path) throws IOException { - Document document = null; - InputStream inputStream = null; - try { - inputStream = ClickUtils.getResourceAsStream(path, getClass()); - - if (inputStream != null) { - document = ClickUtils.buildDocument(inputStream, this); - } - - } finally { - ClickUtils.close(inputStream); - } - - if (document != null) { - return document.getDocumentElement(); - - } else { - return null; - } - } - - private void deployControls(Element rootElm) throws Exception { - - if (rootElm == null) { - return; - } - - Element controlsElm = ClickUtils.getChild(rootElm, "controls"); - - if (controlsElm == null) { - return; - } - - List deployableList = ClickUtils.getChildren(controlsElm, "control"); - - for (int i = 0; i < deployableList.size(); i++) { - Element deployableElm = (Element) deployableList.get(i); - - String classname = deployableElm.getAttribute("classname"); - if (StringUtils.isBlank(classname)) { - String msg = - "'control' element missing 'classname' attribute."; - throw new RuntimeException(msg); - } - - Class deployClass = ClickUtils.classForName(classname); - Control control = (Control) deployClass.newInstance(); - - control.onDeploy(servletContext); - } - } - - private void deployControlSets(Element rootElm) throws Exception { - if (rootElm == null) { - return; - } - - Element controlsElm = ClickUtils.getChild(rootElm, "controls"); - - if (controlsElm == null) { - return; - } - - List controlSets = ClickUtils.getChildren(controlsElm, "control-set"); - - for (int i = 0; i < controlSets.size(); i++) { - Element controlSet = (Element) controlSets.get(i); - String name = controlSet.getAttribute("name"); - if (StringUtils.isBlank(name)) { - String msg = - "'control-set' element missing 'name' attribute."; - throw new RuntimeException(msg); - } - deployControls(getResourceRootElement("/" + name)); - } - } - - /** - * Deploy files from jars and Controls. - * - * @param rootElm the click.xml configuration DOM element - * @throws java.lang.Exception if files cannot be deployed - */ - private void deployFiles(Element rootElm) throws Exception { - - boolean isResourcesDeployable = isResourcesDeployable(); - - if (isResourcesDeployable) { - if (getLogService().isTraceEnabled()) { - String deployTarget = servletContext.getRealPath("/"); - getLogService().trace("resource deploy folder: " - + deployTarget); - } - - deployControls(getResourceRootElement("/click-controls.xml")); - deployControls(getResourceRootElement("/extras-controls.xml")); - deployControls(rootElm); - deployControlSets(rootElm); - deployResourcesOnClasspath(); - } - - if (!isResourcesDeployable) { - - HtmlStringBuffer buffer = new HtmlStringBuffer(); - if (onGoogleAppEngine) { - buffer.append("Google App Engine does not support deploying"); - buffer.append(" resources to the 'click' web folder.\n"); - - } else { - buffer.append("could not deploy Click resources to the 'click'"); - buffer.append(" web folder.\nThis can occur if the call to"); - buffer.append(" ServletContext.getRealPath(\"/\") returns null, which means"); - buffer.append(" the web application cannot determine the file system path"); - buffer.append(" to deploy files to. This issue also occurs if the web"); - buffer.append(" application is not allowed to write to the file"); - buffer.append(" system.\n"); - } - - buffer.append("To resolve this issue please see the Click user-guide:"); - buffer.append(" http://click.apache.org/docs/user-guide/html/ch05s03.html#deploying-restricted-env"); - buffer.append(" \nIgnore this warning once you have settled on a"); - buffer.append(" deployment strategy"); - getLogService().warn(buffer.toString()); - } - } - - /** - * Deploy from the classpath all resources found under the directory - * 'META-INF/resources/'. For backwards compatibility resources under the - * directory 'META-INF/web/' are also deployed. - *

- * Only jars and folders available on the classpath are scanned. - * - * @throws IOException if the resources cannot be deployed - */ - private void deployResourcesOnClasspath() throws IOException { - long startTime = System.currentTimeMillis(); - - // Find all jars and directories on the classpath that contains the - // directory "META-INF/resources/", and deploy those resources - String resourceDirectory = "META-INF/resources"; - - List resources = new DeployUtils(logService).findResources(resourceDirectory).getResources(); - for (String resource : resources) { - deployFile(resource, resourceDirectory); - } - - // For backward compatibility, find all jars and directories on the - // classpath that contains the directory "META-INF/web/", and deploy those - // resources - resourceDirectory = "META-INF/web"; - resources = new DeployUtils(logService).findResources(resourceDirectory).getResources(); - for (String resource : resources) { - deployFile(resource, resourceDirectory); - } - - logService.trace("deployed files from jars and folders - " - + (System.currentTimeMillis() - startTime) + " ms"); - } - - /** - * Deploy the specified file. - * - * @param file the file to deploy - * @param prefix the file prefix that must be removed when the file is - * deployed - */ - private void deployFile(String file, String prefix) { - // Only deploy resources containing the prefix - int pathIndex = file.indexOf(prefix); - if (pathIndex == 0) { - pathIndex += prefix.length(); - - // By default deploy to the web root dir - String targetDir = ""; - - // resourceName example -> click/table.css - String resourceName = file.substring(pathIndex); - int index = resourceName.lastIndexOf('/'); - - if (index != -1) { - // targetDir example -> click - targetDir = resourceName.substring(0, index); - } - - // Copy resources to web folder - ClickUtils.deployFile(servletContext, - file, - targetDir); - } - } - - private void loadMode(Element rootElm) { - Element modeElm = ClickUtils.getChild(rootElm, "mode"); - - String modeValue = "development"; - - if (modeElm != null) { - if (StringUtils.isNotBlank(modeElm.getAttribute("value"))) { - modeValue = modeElm.getAttribute("value"); - } - } - - modeValue = System.getProperty("click.mode", modeValue); - - if (modeValue.equalsIgnoreCase("production")) { - mode = PRODUCTION; - } else if (modeValue.equalsIgnoreCase("profile")) { - mode = PROFILE; - } else if (modeValue.equalsIgnoreCase("development")) { - mode = DEVELOPMENT; - } else if (modeValue.equalsIgnoreCase("debug")) { - mode = DEBUG; - } else if (modeValue.equalsIgnoreCase("trace")) { - mode = TRACE; - } else { - logService.error("invalid application mode: '" + modeValue - + "' - defaulted to '" + MODE_VALUES[DEBUG] + "'"); - mode = DEBUG; - } - - // Set log levels - if (logService instanceof ConsoleLogService) { - int logLevel = ConsoleLogService.INFO_LEVEL; - - if (mode == PRODUCTION) { - logLevel = ConsoleLogService.WARN_LEVEL; - - } else if (mode == DEVELOPMENT) { - - } else if (mode == DEBUG) { - logLevel = ConsoleLogService.DEBUG_LEVEL; - - } else if (mode == TRACE) { - logLevel = ConsoleLogService.TRACE_LEVEL; - } - - ((ConsoleLogService) logService).setLevel(logLevel); - } - } - - private void loadDefaultPages() throws ClassNotFoundException { - - if (!pageByPathMap.containsKey(ERROR_PATH)) { - XmlConfigService.PageElm page = - new XmlConfigService.PageElm("org.openidentityplatform.openam.click.util.ErrorPage", ERROR_PATH); - - pageByPathMap.put(ERROR_PATH, page); - } - - if (!pageByPathMap.containsKey(NOT_FOUND_PATH)) { - XmlConfigService.PageElm page = - new XmlConfigService.PageElm("org.openidentityplatform.openam.click.Page", NOT_FOUND_PATH); - - pageByPathMap.put(NOT_FOUND_PATH, page); - } - } - - private void loadHeaders(Element rootElm) { - Element headersElm = ClickUtils.getChild(rootElm, "headers"); - - if (headersElm != null) { - commonHeaders = - Collections.unmodifiableMap(loadHeadersMap(headersElm)); - } else { - commonHeaders = Collections.unmodifiableMap(DEFAULT_HEADERS); - } - } - - private void loadFormatClass(Element rootElm) - throws ClassNotFoundException { - - Element formatElm = ClickUtils.getChild(rootElm, "format"); - - if (formatElm != null) { - String classname = formatElm.getAttribute("classname"); - - if (classname == null) { - String msg = "'format' element missing 'classname' attribute."; - throw new RuntimeException(msg); - } - - formatClass = ClickUtils.classForName(classname); - - } else { - formatClass = org.apache.click.util.Format.class; - } - } - - private void loadFileUploadService(Element rootElm) throws Exception { - - Element fileUploadServiceElm = ClickUtils.getChild(rootElm, "file-upload-service"); - - if (fileUploadServiceElm != null) { - Class fileUploadServiceClass = CommonsFileUploadService.class; - - String classname = fileUploadServiceElm.getAttribute("classname"); - - if (StringUtils.isNotBlank(classname)) { - fileUploadServiceClass = ClickUtils.classForName(classname); - } - - fileUploadService = (FileUploadService) fileUploadServiceClass.newInstance(); - - Map propertyMap = loadPropertyMap(fileUploadServiceElm); - - for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { - String name = i.next().toString(); - String value = propertyMap.get(name).toString(); - - Ognl.setValue(name, fileUploadService, value); - } - - } else { - //throw new RuntimeException("unsupported file upload service"); - //fileUploadService = new CommonsFileUploadService(); - fileUploadService = new FileUploadService() { - @Override - public void onInit(ServletContext servletContext) { - - } - - @Override - public void onDestroy() { - - } - - @Override - public List parseRequest(HttpServletRequest request) throws FileUploadException { - return null; - } - }; - } - - if (getLogService().isDebugEnabled()) { - String msg = "initializing FileLoadService: " - + fileUploadService.getClass().getName(); - getLogService().debug(msg); - } - - fileUploadService.onInit(servletContext); - } - - private void loadLogService(Element rootElm) throws Exception { - Element logServiceElm = ClickUtils.getChild(rootElm, "log-service"); - - if (logServiceElm != null) { - Class logServiceClass = ConsoleLogService.class; - - String classname = logServiceElm.getAttribute("classname"); - - if (StringUtils.isNotBlank(classname)) { - logServiceClass = ClickUtils.classForName(classname); - } - - logService = (LogService) logServiceClass.newInstance(); - - Map propertyMap = loadPropertyMap(logServiceElm); - - for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { - String name = i.next().toString(); - String value = propertyMap.get(name).toString(); - - Ognl.setValue(name, logService, value); - } - } else { - logService = new ConsoleLogService(); - } - - logService.onInit(getServletContext()); - } - - private void loadMessagesMapService(Element rootElm) throws Exception { - Element messagesMapServiceElm = ClickUtils.getChild(rootElm, "messages-map-service"); - - if (messagesMapServiceElm != null) { - Class messagesMapServiceClass = DefaultMessagesMapService.class; - - String classname = messagesMapServiceElm.getAttribute("classname"); - - if (StringUtils.isNotBlank(classname)) { - messagesMapServiceClass = ClickUtils.classForName(classname); - } - - messagesMapService = (MessagesMapService) messagesMapServiceClass.newInstance(); - - Map propertyMap = loadPropertyMap(messagesMapServiceElm); - - for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { - String name = i.next().toString(); - String value = propertyMap.get(name).toString(); - - Ognl.setValue(name, messagesMapService, value); - } - } - - if (getLogService().isDebugEnabled()) { - String msg = "initializing MessagesMapService: " - + messagesMapService.getClass().getName(); - getLogService().debug(msg); - } - - messagesMapService.onInit(servletContext); - } - - private void loadPageInterceptors(Element rootElm) throws Exception { - List interceptorList = - ClickUtils.getChildren(rootElm, "page-interceptor"); - - for (Element interceptorElm : interceptorList) { - String classname = interceptorElm.getAttribute("classname"); - - String scopeValue = interceptorElm.getAttribute("scope"); - boolean applicationScope = "application".equalsIgnoreCase(scopeValue); - - Class interceptorClass = ClickUtils.classForName(classname); - - Map propertyMap = loadPropertyMap(interceptorElm); - List propertyList = new ArrayList(); - - for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { - String name = i.next().toString(); - String value = propertyMap.get(name).toString(); - - propertyList.add(new XmlConfigService.Property(name, value)); - } - - XmlConfigService.PageInterceptorConfig pageInterceptorConfig = - new XmlConfigService.PageInterceptorConfig(interceptorClass, applicationScope, propertyList); - - pageInterceptorConfigList.add(pageInterceptorConfig); - } - } - - private void loadResourceService(Element rootElm) throws Exception { - - Element resourceServiceElm = ClickUtils.getChild(rootElm, "resource-service"); - - if (resourceServiceElm != null) { - Class resourceServiceClass = ClickResourceService.class; - - String classname = resourceServiceElm.getAttribute("classname"); - - if (StringUtils.isNotBlank(classname)) { - resourceServiceClass = ClickUtils.classForName(classname); - } - - resourceService = (ResourceService) resourceServiceClass.newInstance(); - - Map propertyMap = loadPropertyMap(resourceServiceElm); - - for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { - String name = i.next().toString(); - String value = propertyMap.get(name).toString(); - - Ognl.setValue(name, resourceService, value); - } - - } else { - resourceService = new ClickResourceService(); - } - - if (getLogService().isDebugEnabled()) { - String msg = "initializing ResourceService: " - + resourceService.getClass().getName(); - getLogService().debug(msg); - } - - resourceService.onInit(servletContext); - } - - private void loadTemplateService(Element rootElm) throws Exception { - Element templateServiceElm = ClickUtils.getChild(rootElm, "template-service"); - - if (templateServiceElm != null) { - Class templateServiceClass = VelocityTemplateService.class; - - String classname = templateServiceElm.getAttribute("classname"); - - if (StringUtils.isNotBlank(classname)) { - templateServiceClass = ClickUtils.classForName(classname); - } - - templateService = (TemplateService) templateServiceClass.newInstance(); - - Map propertyMap = loadPropertyMap(templateServiceElm); - - for (Iterator i = propertyMap.keySet().iterator(); i.hasNext();) { - String name = i.next().toString(); - String value = propertyMap.get(name).toString(); - - Ognl.setValue(name, templateService, value); - } - - } else { - templateService = new VelocityTemplateService(); - } - - if (getLogService().isDebugEnabled()) { - String msg = "initializing TemplateService: " - + templateService.getClass().getName(); - getLogService().debug(msg); - } - - templateService.onInit(servletContext); - } - - private static Map loadPropertyMap(Element parentElm) { - Map propertyMap = new HashMap(); - - List propertyList = ClickUtils.getChildren(parentElm, "property"); - - for (int i = 0, size = propertyList.size(); i < size; i++) { - Element property = (Element) propertyList.get(i); - - String name = property.getAttribute("name"); - String value = property.getAttribute("value"); - - propertyMap.put(name, value); - } - - return propertyMap; - } - - private void loadCharset(Element rootElm) { - String localCharset = rootElm.getAttribute("charset"); - if (localCharset != null && localCharset.length() > 0) { - this.charset = localCharset; - } - } - - private void loadLocale(Element rootElm) { - String value = rootElm.getAttribute("locale"); - if (value != null && value.length() > 0) { - StringTokenizer tokenizer = new StringTokenizer(value, "_"); - if (tokenizer.countTokens() == 1) { - String language = tokenizer.nextToken(); - locale = new Locale(language); - } else if (tokenizer.countTokens() == 2) { - String language = tokenizer.nextToken(); - String country = tokenizer.nextToken(); - locale = new Locale(language, country); - } - } - } - - /** - * Return the list of templates within the web application. - * - * @return list of all templates within the web application - */ - private List getTemplateFiles() { - List fileList = new ArrayList(); - - Set resources = servletContext.getResourcePaths("/"); - if (onGoogleAppEngine) { - // resources could be immutable so create copy - Set tempResources = new HashSet(); - - // Load the two GAE preconfigured automapped folders - tempResources.addAll(servletContext.getResourcePaths("/page")); - tempResources.addAll(servletContext.getResourcePaths("/pages")); - tempResources.addAll(resources); - - // assign copy to resources - resources = Collections.unmodifiableSet(tempResources); - } - - // Add all resources within web application - for (Iterator i = resources.iterator(); i.hasNext();) { - String resource = (String) i.next(); - - if (isTemplate(resource)) { - fileList.add(resource); - - } else if (resource.endsWith("/")) { - if (!resource.equalsIgnoreCase("/WEB-INF/")) { - processDirectory(resource, fileList); - } - } - } - - Collections.sort(fileList); - - return fileList; - } - - private void processDirectory(String dirPath, List fileList) { - Set resources = servletContext.getResourcePaths(dirPath); - - if (resources != null) { - for (Iterator i = resources.iterator(); i.hasNext();) { - String resource = (String) i.next(); - - if (isTemplate(resource)) { - fileList.add(resource); - - } else if (resource.endsWith("/")) { - processDirectory(resource, fileList); - } - } - } - } - - private Class getExcludesPageClass(String path) { - for (int i = 0; i < excludesList.size(); i++) { - XmlConfigService.ExcludesElm override = - (XmlConfigService.ExcludesElm) excludesList.get(i); - - if (override.isMatch(path)) { - return override.getPageClass(); - } - } - - return null; - } - - /** - * Return an array of bindable fields for the given page class based on - * the binding mode. - * - * @param pageClass the page class - * @param mode the binding mode - * @return the field array of bindable fields - */ - private static Field[] getBindablePageFields(Class pageClass, AutoBinding mode) { - if (mode == AutoBinding.DEFAULT) { - - // Get @Bindable fields - Map fieldMap = getAnnotatedBindableFields(pageClass); - - // Add public fields - Field[] publicFields = pageClass.getFields(); - for (Field field : publicFields) { - fieldMap.put(field.getName(), field); - } - - // Copy the field map values into a field list - Field[] fieldArray = new Field[fieldMap.size()]; - - int i = 0; - for (Field field : fieldMap.values()) { - fieldArray[i++] = field; - } - - return fieldArray; - - } else if (mode == AutoBinding.ANNOTATION) { - - Map fieldMap = getAnnotatedBindableFields(pageClass); - - // Copy the field map values into a field list - Field[] fieldArray = new Field[fieldMap.size()]; - - int i = 0; - for (Field field : fieldMap.values()) { - fieldArray[i++] = field; - } - - return fieldArray; - - } else { - return new Field[0]; - } - } - - /** - * Return the fields annotated with the Bindable annotation. - * - * @param pageClass the page class - * @return the map of bindable fields - */ - private static Map getAnnotatedBindableFields(Class pageClass) { - - List pageClassList = new ArrayList(); - pageClassList.add(pageClass); - - Class parentClass = pageClass.getSuperclass(); - while (parentClass != null) { - // Include parent classes up to but excluding Page.class - if (parentClass.isAssignableFrom(Page.class)) { - break; - } - pageClassList.add(parentClass); - parentClass = parentClass.getSuperclass(); - } - - // Reverse class list so parents are processed first, with the - // actual page class fields processed last. This will enable the - // page classes fields to override parent class fields - Collections.reverse(pageClassList); - - Map fieldMap = new TreeMap(); - - for (Class aPageClass : pageClassList) { - - for (Field field : aPageClass.getDeclaredFields()) { - - if (field.getAnnotation(Bindable.class) != null) { - fieldMap.put(field.getName(), field); - - // If field is not public set accessibility true - if (!Modifier.isPublic(field.getModifiers())) { - field.setAccessible(true); - } - } - } - } - - return fieldMap; - } - - // ---------------------------------------------------------- Inner Classes - - /** - * Provide an Excluded Page class. - *

- * PLEASE NOTE this class is not for public use, and can be - * ignored. - */ - public static class ExcludePage extends Page { - - static final Map HEADERS = new HashMap(); - - static { - HEADERS.put("Cache-Control", "max-age=3600, public"); - } - - /** - * @see Page#getHeaders() - * - * @return the map of HTTP header to be set in the HttpServletResponse - */ - @Override - public Map getHeaders() { - return HEADERS; - } - } - - static class PageElm { - - final Map fields; - - final Field[] fieldArray; - - final Map headers; - - final Class pageClass; - - final String path; - - public PageElm(Element element, - String pagesPackage, - Map commonHeaders, - AutoBinding autobinding) - throws ClassNotFoundException { - - // Set headers - Map aggregationMap = new HashMap(commonHeaders); - Map pageHeaders = loadHeadersMap(element); - aggregationMap.putAll(pageHeaders); - headers = Collections.unmodifiableMap(aggregationMap); - - // Set path - String pathValue = element.getAttribute("path"); - if (pathValue.charAt(0) != '/') { - path = "/" + pathValue; - } else { - path = pathValue; - } - - // Retrieve page classname - String classname = element.getAttribute("classname"); - - if (classname == null) { - String msg = "No classname defined for page path " + path; - throw new RuntimeException(msg); - } - - Class tmpPageClass = null; - String classnameFound = null; - - try { - - // First, lookup classname as provided - tmpPageClass = ClickUtils.classForName(classname); - classnameFound = classname; - - } catch (ClassNotFoundException cnfe) { - - if (pagesPackage.trim().length() > 0) { - // For backward compatibility prefix classname with package name - String prefixedClassname = pagesPackage + "." + classname; - - try { - // CLK-704 - // For backward compatibility, lookup classname prefixed with the package name - - tmpPageClass = - ClickUtils.classForName(prefixedClassname); - classnameFound = prefixedClassname; - - } catch (ClassNotFoundException cnfe2) { - // Throw original exception which used the given classname - String msg = "No class was found for the Page classname: '" - + classname + "'."; - throw new RuntimeException(msg, cnfe); - } - } - } - - pageClass = tmpPageClass; - - if (!Page.class.isAssignableFrom(pageClass)) { - String msg = "Page class '" + classnameFound - + "' is not a subclass of org.apache.click.Page"; - throw new RuntimeException(msg); - } - - - fieldArray = XmlConfigService.getBindablePageFields(pageClass, autobinding); - - fields = new HashMap(); - for (Field field : fieldArray) { - fields.put(field.getName(), field); - } - } - - private PageElm(String path, - Class pageClass, - Map commonHeaders, - AutoBinding mode) { - - headers = Collections.unmodifiableMap(commonHeaders); - this.pageClass = pageClass; - this.path = path; - - fieldArray = getBindablePageFields(pageClass, mode); - - fields = new HashMap(); - for (Field field : fieldArray) { - fields.put(field.getName(), field); - } - } - - public PageElm(String classname, String path) - throws ClassNotFoundException { - - this.fieldArray = new Field[0]; - this.fields = Collections.emptyMap(); - this.headers = Collections.emptyMap(); - pageClass = ClickUtils.classForName(classname); - this.path = path; - } - - public Field[] getFieldArray() { - return fieldArray; - } - - public Map getFields() { - return fields; - } - - public Map getHeaders() { - return headers; - } - - public Class getPageClass() { - return pageClass; - } - - public String getPath() { - return path; - } - } - - static class ExcludesElm { - - final Set pathSet = new HashSet(); - final Set fileSet = new HashSet(); - - public ExcludesElm(Element element) throws ClassNotFoundException { - - String pattern = element.getAttribute("pattern"); - - if (StringUtils.isNotBlank(pattern)) { - StringTokenizer tokenizer = new StringTokenizer(pattern, ", "); - while (tokenizer.hasMoreTokens()) { - String token = tokenizer.nextToken(); - - if (token.charAt(0) != '/') { - token = "/" + token; - } - - int index = token.lastIndexOf("."); - if (index != -1) { - token = token.substring(0, index); - fileSet.add(token); - - } else { - index = token.indexOf("*"); - if (index != -1) { - token = token.substring(0, index); - } - pathSet.add(token); - } - } - } - } - - public Class getPageClass() { - return XmlConfigService.ExcludePage.class; - } - - public boolean isMatch(String resourcePath) { - if (fileSet.contains(resourcePath)) { - return true; - } - - for (String path : pathSet) { - if (resourcePath.startsWith(path)) { - return true; - } - } - - return false; - } - - @Override - public String toString() { - return getClass().getName() - + "[fileSet=" + fileSet + ",pathSet=" + pathSet + "]"; - } - } - - static class PageInterceptorConfig { - - final Class interceptorClass; - final boolean applicationScope; - final List properties; - PageInterceptor pageInterceptor; - - PageInterceptorConfig(Class interceptorClass, - boolean applicationScope, - List properties) { - - this.interceptorClass = interceptorClass; - this.applicationScope = applicationScope; - this.properties = properties; - } - - public PageInterceptor getPageInterceptor() { - PageInterceptor listener = null; - - // If cached interceptor not already created (application scope) - // or is scope request then create a new interceptor - if (pageInterceptor == null || !applicationScope) { - try { - listener = interceptorClass.newInstance(); - - Map ognlContext = new HashMap(); - - for (XmlConfigService.Property property : properties) { - PropertyUtils.setValueOgnl(listener, - property.getName(), - property.getValue(), - ognlContext); - } - - } catch (Exception e) { - throw new RuntimeException(e); - } - - if (applicationScope) { - pageInterceptor = listener; - } - - } else { - listener = pageInterceptor; - } - - return listener; - } - } - - static class Property { - final String name; - final String value; - - Property(String name, String value) { - this.name = name; - this.value = value; - } - - public String getName() { - return name; - } - - public String getValue() { - return value; - } - } - -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/util/ClickUtils.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/util/ClickUtils.java deleted file mode 100644 index 28297175e4..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/util/ClickUtils.java +++ /dev/null @@ -1,3354 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.util; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.URL; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.security.MessageDigest; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.MissingResourceException; -import java.util.ResourceBundle; -import java.util.TreeMap; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import jakarta.servlet.ServletContext; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.openidentityplatform.openam.click.Context; -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.Page; -import org.openidentityplatform.openam.click.ActionResult; -import org.apache.click.Stateful; -import org.openidentityplatform.openam.click.control.AbstractControl; -import org.openidentityplatform.openam.click.control.AbstractLink; -import org.apache.click.control.ActionLink; -import org.openidentityplatform.openam.click.control.Container; -import org.openidentityplatform.openam.click.control.Field; -import org.openidentityplatform.openam.click.control.Form; - -import org.apache.click.util.Format; -import org.apache.click.util.MessagesMap; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.ClassUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.Validate; -import org.openidentityplatform.openam.click.service.ConfigService; -import org.openidentityplatform.openam.click.service.LogService; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.EntityResolver; - -/** - * Provides miscellaneous Form, String and Stream utility methods. - */ -public class ClickUtils { - - // ------------------------------------------------------- Public Constants - - /** - * The resource versioning request attribute: key:   - * enable-resource-version. - *

- * If this attribute is set to true and Click is running in - * production or profile mode, Click resources returned - * from {@link org.apache.click.Control#getHeadElements()} will have a - * version indicator added to their path. - * - * @see org.apache.click.Control#getHeadElements() - * @see org.openidentityplatform.openam.click.util.ClickUtils#getResourceVersionIndicator(Context) - */ - public static final String ENABLE_RESOURCE_VERSION = "enable-resource-version"; - - /** - * The default Click configuration filename:   - * "/WEB-INF/click.xml". - */ - public static final String DEFAULT_APP_CONFIG = "/WEB-INF/click.xml"; - - /** The version indicator separator string. */ - public static final String VERSION_INDICATOR_SEP = "_"; - - /** The static web resource version number indicator string. */ - public static final String RESOURCE_VERSION_INDICATOR = - VERSION_INDICATOR_SEP + getClickVersion(); - - // ------------------------------------------------------ Private Constants - - /** The cached resource version indicator. */ - private static String cachedResourceVersionIndicator; - - /** The static application-wide resource version indicator. */ - private static String applicationVersion; - - /** The cached application version indicator string. */ - private static String cachedApplicationVersionIndicator; - - /** - * Character used to separate username and password in persistent cookies. - * 0x13 == "Device Control 3" non-printing ASCII char. Unlikely to appear - * in a username - */ - private static final char DELIMITER = 0x13; - - /* - * "Tweakable" parameters for the cookie encoding. NOTE: changing these - * and recompiling this class will essentially invalidate old cookies. - */ - private final static char ENCODE_CHAR_OFFSET1 = 'C'; - private final static char ENCODE_CHAR_OFFSET2 = 'i'; - - /** Hexadecimal characters for MD5 encoding. */ - private static final char[] HEXADECIMAL = { '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - - /** Ajax request header or parameter: "X-Requested-With". */ - private static final String X_REQUESTED_WITH = "X-Requested-With"; - - /** - * The array of escaped HTML character values, indexed on char value. - *

- * HTML entities values were derived from Jakarta Commons Lang - * org.apache.commons.lang.Entities class. - */ - private static final String[] HTML_ENTITIES = new String[9999]; - - static { - HTML_ENTITIES[34] = """; // " - double-quote - HTML_ENTITIES[38] = "&"; // & - ampersand - HTML_ENTITIES[60] = "<"; // < - less-than - HTML_ENTITIES[62] = ">"; // > - greater-than - HTML_ENTITIES[160] = " "; // non-breaking space - HTML_ENTITIES[161] = "¡"; // inverted exclamation mark - HTML_ENTITIES[162] = "¢"; // cent sign - HTML_ENTITIES[163] = "£"; // pound sign - HTML_ENTITIES[164] = "¤"; // currency sign - HTML_ENTITIES[165] = "¥"; // yen sign = yuan sign - HTML_ENTITIES[166] = "¦"; // broken bar = broken vertical bar - HTML_ENTITIES[167] = "§"; // section sign - HTML_ENTITIES[168] = "¨"; // diaeresis = spacing diaeresis - HTML_ENTITIES[169] = "©"; // © - copyright sign - HTML_ENTITIES[170] = "ª"; // feminine ordinal indicator - HTML_ENTITIES[171] = "«"; // left-pointing double angle quotation mark = left pointing guillemet - HTML_ENTITIES[172] = "¬"; //not sign - HTML_ENTITIES[173] = "­"; //soft hyphen = discretionary hyphen - HTML_ENTITIES[174] = "®"; // ® - registered trademark sign - HTML_ENTITIES[175] = "¯"; //macron = spacing macron = overline = APL overbar - HTML_ENTITIES[176] = "°"; //degree sign - HTML_ENTITIES[177] = "±"; //plus-minus sign = plus-or-minus sign - HTML_ENTITIES[178] = "²"; //superscript two = superscript digit two = squared - HTML_ENTITIES[179] = "³"; //superscript three = superscript digit three = cubed - HTML_ENTITIES[180] = "´"; //acute accent = spacing acute - HTML_ENTITIES[181] = "µ"; //micro sign - HTML_ENTITIES[182] = "¶"; //pilcrow sign = paragraph sign - HTML_ENTITIES[183] = "·"; //middle dot = Georgian comma = Greek middle dot - HTML_ENTITIES[184] = "¸"; //cedilla = spacing cedilla - HTML_ENTITIES[185] = "¹"; //superscript one = superscript digit one - HTML_ENTITIES[186] = "º"; //masculine ordinal indicator - HTML_ENTITIES[187] = "»"; //right-pointing double angle quotation mark = right pointing guillemet - HTML_ENTITIES[188] = "¼"; //vulgar fraction one quarter = fraction one quarter - HTML_ENTITIES[189] = "½"; //vulgar fraction one half = fraction one half - HTML_ENTITIES[190] = "¾"; //vulgar fraction three quarters = fraction three quarters - HTML_ENTITIES[191] = "¿"; //inverted question mark = turned question mark - HTML_ENTITIES[192] = "À"; // À - uppercase A, grave accent - HTML_ENTITIES[193] = "Á"; // Á - uppercase A, acute accent - HTML_ENTITIES[194] = "Â"; //  - uppercase A, circumflex accent - HTML_ENTITIES[195] = "Ã"; // à - uppercase A, tilde - HTML_ENTITIES[196] = "Ä"; // Ä - uppercase A, umlaut - HTML_ENTITIES[197] = "Å"; // Å - uppercase A, ring - HTML_ENTITIES[198] = "Æ"; // Æ - uppercase AE - HTML_ENTITIES[199] = "Ç"; // Ç - uppercase C, cedilla - HTML_ENTITIES[200] = "È"; // È - uppercase E, grave accent - HTML_ENTITIES[201] = "É"; // É - uppercase E, acute accent - HTML_ENTITIES[202] = "Ê"; // Ê - uppercase E, circumflex accent - HTML_ENTITIES[203] = "Ë"; // Ë - uppercase E, umlaut - HTML_ENTITIES[204] = "Ì"; // Ì - uppercase I, grave accent - HTML_ENTITIES[205] = "Í"; // Í - uppercase I, acute accent - HTML_ENTITIES[206] = "Î"; // Î - uppercase I, circumflex accent - HTML_ENTITIES[207] = "Ï"; // Ï - uppercase I, umlaut - HTML_ENTITIES[208] = "Ð"; // Ð - uppercase Eth, Icelandic - HTML_ENTITIES[209] = "Ñ"; // Ñ - uppercase N, tilde - HTML_ENTITIES[210] = "Ò"; // Ò - uppercase O, grave accent - HTML_ENTITIES[211] = "Ó"; // Ó - uppercase O, acute accent - HTML_ENTITIES[212] = "Ô"; // Ô - uppercase O, circumflex accent - HTML_ENTITIES[213] = "Õ"; // Õ - uppercase O, tilde - HTML_ENTITIES[214] = "Ö"; // Ö - uppercase O, umlaut - HTML_ENTITIES[215] = "×"; //multiplication sign - HTML_ENTITIES[216] = "Ø"; // Ø - uppercase O, slash - HTML_ENTITIES[217] = "Ù"; // Ù - uppercase U, grave accent - HTML_ENTITIES[218] = "Ú"; // Ú - uppercase U, acute accent - HTML_ENTITIES[219] = "Û"; // Û - uppercase U, circumflex accent - HTML_ENTITIES[220] = "Ü"; // Ü - uppercase U, umlaut - HTML_ENTITIES[221] = "Ý"; // Ý - uppercase Y, acute accent - HTML_ENTITIES[222] = "Þ"; // Þ - uppercase THORN, Icelandic - HTML_ENTITIES[223] = "ß"; // ß - lowercase sharps, German - HTML_ENTITIES[224] = "à"; // à - lowercase a, grave accent - HTML_ENTITIES[225] = "á"; // á - lowercase a, acute accent - HTML_ENTITIES[226] = "â"; // â - lowercase a, circumflex accent - HTML_ENTITIES[227] = "ã"; // ã - lowercase a, tilde - HTML_ENTITIES[228] = "ä"; // ä - lowercase a, umlaut - HTML_ENTITIES[229] = "å"; // å - lowercase a, ring - HTML_ENTITIES[230] = "æ"; // æ - lowercase ae - HTML_ENTITIES[231] = "ç"; // ç - lowercase c, cedilla - HTML_ENTITIES[232] = "è"; // è - lowercase e, grave accent - HTML_ENTITIES[233] = "é"; // é - lowercase e, acute accent - HTML_ENTITIES[234] = "ê"; // ê - lowercase e, circumflex accent - HTML_ENTITIES[235] = "ë"; // ë - lowercase e, umlaut - HTML_ENTITIES[236] = "ì"; // ì - lowercase i, grave accent - HTML_ENTITIES[237] = "í"; // í - lowercase i, acute accent - HTML_ENTITIES[238] = "î"; // î - lowercase i, circumflex accent - HTML_ENTITIES[239] = "ï"; // ï - lowercase i, umlaut - HTML_ENTITIES[240] = "ð"; // ð - lowercase eth, Icelandic - HTML_ENTITIES[241] = "ñ"; // ñ - lowercase n, tilde - HTML_ENTITIES[242] = "ò"; // ò - lowercase o, grave accent - HTML_ENTITIES[243] = "ó"; // ó - lowercase o, acute accent - HTML_ENTITIES[244] = "ô"; // ô - lowercase o, circumflex accent - HTML_ENTITIES[245] = "õ"; // õ - lowercase o, tilde - HTML_ENTITIES[246] = "ö"; // ö - lowercase o, umlaut - HTML_ENTITIES[247] = "÷"; // division sign - HTML_ENTITIES[248] = "ø"; // ø - lowercase o, slash - HTML_ENTITIES[249] = "ù"; // ù - lowercase u, grave accent - HTML_ENTITIES[250] = "ú"; // ú - lowercase u, acute accent - HTML_ENTITIES[251] = "û"; // û - lowercase u, circumflex accent - HTML_ENTITIES[252] = "ü"; // ü - lowercase u, umlaut - HTML_ENTITIES[253] = "ý"; // ý - lowercase y, acute accent - HTML_ENTITIES[254] = "þ"; // þ - lowercase thorn, Icelandic - HTML_ENTITIES[255] = "ÿ"; // ÿ - lowercase y, umlaut - // http://www.w3.org/TR/REC-html40/sgml/entities.html - // - HTML_ENTITIES[402] = "ƒ"; //latin small f with hook = function= florin, U+0192 ISOtech --> - // - HTML_ENTITIES[913] = "Α"; //greek capital letter alpha, U+0391 --> - HTML_ENTITIES[914] = "Β"; //greek capital letter beta, U+0392 --> - HTML_ENTITIES[915] = "Γ"; //greek capital letter gamma,U+0393 ISOgrk3 --> - HTML_ENTITIES[916] = "Δ"; //greek capital letter delta,U+0394 ISOgrk3 --> - HTML_ENTITIES[917] = "Ε"; //greek capital letter epsilon, U+0395 --> - HTML_ENTITIES[918] = "Ζ"; //greek capital letter zeta, U+0396 --> - HTML_ENTITIES[919] = "Η"; //greek capital letter eta, U+0397 --> - HTML_ENTITIES[920] = "Θ"; //greek capital letter theta,U+0398 ISOgrk3 --> - HTML_ENTITIES[921] = "Ι"; //greek capital letter iota, U+0399 --> - HTML_ENTITIES[922] = "Κ"; //greek capital letter kappa, U+039A --> - HTML_ENTITIES[923] = "Λ"; //greek capital letter lambda,U+039B ISOgrk3 --> - HTML_ENTITIES[924] = "Μ"; //greek capital letter mu, U+039C --> - HTML_ENTITIES[925] = "Ν"; //greek capital letter nu, U+039D --> - HTML_ENTITIES[926] = "Ξ"; //greek capital letter xi, U+039E ISOgrk3 --> - HTML_ENTITIES[927] = "Ο"; //greek capital letter omicron, U+039F --> - HTML_ENTITIES[928] = "Π"; //greek capital letter pi, U+03A0 ISOgrk3 --> - HTML_ENTITIES[929] = "Ρ"; //greek capital letter rho, U+03A1 --> - // - HTML_ENTITIES[931] = "Σ"; //greek capital letter sigma,U+03A3 ISOgrk3 --> - HTML_ENTITIES[932] = "Τ"; //greek capital letter tau, U+03A4 --> - HTML_ENTITIES[933] = "Υ"; //greek capital letter upsilon,U+03A5 ISOgrk3 --> - HTML_ENTITIES[934] = "Φ"; //greek capital letter phi,U+03A6 ISOgrk3 --> - HTML_ENTITIES[935] = "Χ"; //greek capital letter chi, U+03A7 --> - HTML_ENTITIES[936] = "Ψ"; //greek capital letter psi,U+03A8 ISOgrk3 --> - HTML_ENTITIES[937] = "Ω"; //greek capital letter omega,U+03A9 ISOgrk3 --> - HTML_ENTITIES[945] = "α"; //greek small letter alpha,U+03B1 ISOgrk3 --> - HTML_ENTITIES[946] = "β"; //greek small letter beta, U+03B2 ISOgrk3 --> - HTML_ENTITIES[947] = "γ"; //greek small letter gamma,U+03B3 ISOgrk3 --> - HTML_ENTITIES[948] = "δ"; //greek small letter delta,U+03B4 ISOgrk3 --> - HTML_ENTITIES[949] = "ε"; //greek small letter epsilon,U+03B5 ISOgrk3 --> - HTML_ENTITIES[950] = "ζ"; //greek small letter zeta, U+03B6 ISOgrk3 --> - HTML_ENTITIES[951] = "η"; //greek small letter eta, U+03B7 ISOgrk3 --> - HTML_ENTITIES[952] = "θ"; //greek small letter theta,U+03B8 ISOgrk3 --> - HTML_ENTITIES[953] = "ι"; //greek small letter iota, U+03B9 ISOgrk3 --> - HTML_ENTITIES[954] = "κ"; //greek small letter kappa,U+03BA ISOgrk3 --> - HTML_ENTITIES[955] = "λ"; //greek small letter lambda,U+03BB ISOgrk3 --> - HTML_ENTITIES[956] = "μ"; //greek small letter mu, U+03BC ISOgrk3 --> - HTML_ENTITIES[957] = "ν"; //greek small letter nu, U+03BD ISOgrk3 --> - HTML_ENTITIES[958] = "ξ"; //greek small letter xi, U+03BE ISOgrk3 --> - HTML_ENTITIES[959] = "ο"; //greek small letter omicron, U+03BF NEW --> - HTML_ENTITIES[960] = "π"; //greek small letter pi, U+03C0 ISOgrk3 --> - HTML_ENTITIES[961] = "ρ"; //greek small letter rho, U+03C1 ISOgrk3 --> - HTML_ENTITIES[962] = "ς"; //greek small letter final sigma,U+03C2 ISOgrk3 --> - HTML_ENTITIES[963] = "σ"; //greek small letter sigma,U+03C3 ISOgrk3 --> - HTML_ENTITIES[964] = "τ"; //greek small letter tau, U+03C4 ISOgrk3 --> - HTML_ENTITIES[965] = "υ"; //greek small letter upsilon,U+03C5 ISOgrk3 --> - HTML_ENTITIES[966] = "φ"; //greek small letter phi, U+03C6 ISOgrk3 --> - HTML_ENTITIES[967] = "χ"; //greek small letter chi, U+03C7 ISOgrk3 --> - HTML_ENTITIES[968] = "ψ"; //greek small letter psi, U+03C8 ISOgrk3 --> - HTML_ENTITIES[969] = "ω"; //greek small letter omega,U+03C9 ISOgrk3 --> - HTML_ENTITIES[977] = "ϑ"; //greek small letter theta symbol,U+03D1 NEW --> - HTML_ENTITIES[978] = "ϒ"; //greek upsilon with hook symbol,U+03D2 NEW --> - HTML_ENTITIES[982] = "ϖ"; //greek pi symbol, U+03D6 ISOgrk3 --> - // - HTML_ENTITIES[8226] = "•"; //bullet = black small circle,U+2022 ISOpub --> - // - HTML_ENTITIES[8230] = "…"; //horizontal ellipsis = three dot leader,U+2026 ISOpub --> - HTML_ENTITIES[8242] = "′"; //prime = minutes = feet, U+2032 ISOtech --> - HTML_ENTITIES[8243] = "″"; //double prime = seconds = inches,U+2033 ISOtech --> - HTML_ENTITIES[8254] = "‾"; //overline = spacing overscore,U+203E NEW --> - HTML_ENTITIES[8260] = "⁄"; //fraction slash, U+2044 NEW --> - // - HTML_ENTITIES[8472] = "℘"; //script capital P = power set= Weierstrass p, U+2118 ISOamso --> - HTML_ENTITIES[8465] = "ℑ"; //blackletter capital I = imaginary part,U+2111 ISOamso --> - HTML_ENTITIES[8476] = "ℜ"; //blackletter capital R = real part symbol,U+211C ISOamso --> - HTML_ENTITIES[8482] = "™"; //trade mark sign, U+2122 ISOnum --> - HTML_ENTITIES[8501] = "ℵ"; //alef symbol = first transfinite cardinal,U+2135 NEW --> - // - // - HTML_ENTITIES[8592] = "←"; //leftwards arrow, U+2190 ISOnum --> - HTML_ENTITIES[8593] = "↑"; //upwards arrow, U+2191 ISOnum--> - HTML_ENTITIES[8594] = "→"; //rightwards arrow, U+2192 ISOnum --> - HTML_ENTITIES[8595] = "↓"; //downwards arrow, U+2193 ISOnum --> - HTML_ENTITIES[8596] = "↔"; //left right arrow, U+2194 ISOamsa --> - HTML_ENTITIES[8629] = "↵"; //downwards arrow with corner leftwards= carriage return, U+21B5 NEW --> - HTML_ENTITIES[8656] = "⇐"; //leftwards double arrow, U+21D0 ISOtech --> - // - HTML_ENTITIES[8657] = "⇑"; //upwards double arrow, U+21D1 ISOamsa --> - HTML_ENTITIES[8658] = "⇒"; //rightwards double arrow,U+21D2 ISOtech --> - // - HTML_ENTITIES[8659] = "⇓"; //downwards double arrow, U+21D3 ISOamsa --> - HTML_ENTITIES[8660] = "⇔"; //left right double arrow,U+21D4 ISOamsa --> - // - HTML_ENTITIES[8704] = "∀"; //for all, U+2200 ISOtech --> - HTML_ENTITIES[8706] = "∂"; //partial differential, U+2202 ISOtech --> - HTML_ENTITIES[8707] = "∃"; //there exists, U+2203 ISOtech --> - HTML_ENTITIES[8709] = "∅"; //empty set = null set = diameter,U+2205 ISOamso --> - HTML_ENTITIES[8711] = "∇"; //nabla = backward difference,U+2207 ISOtech --> - HTML_ENTITIES[8712] = "∈"; //element of, U+2208 ISOtech --> - HTML_ENTITIES[8713] = "∉"; //not an element of, U+2209 ISOtech --> - HTML_ENTITIES[8715] = "∋"; //contains as member, U+220B ISOtech --> - // - HTML_ENTITIES[8719] = "∏"; //n-ary product = product sign,U+220F ISOamsb --> - // - HTML_ENTITIES[8721] = "∑"; //n-ary summation, U+2211 ISOamsb --> - // - HTML_ENTITIES[8722] = "−"; //minus sign, U+2212 ISOtech --> - HTML_ENTITIES[8727] = "∗"; //asterisk operator, U+2217 ISOtech --> - HTML_ENTITIES[8730] = "√"; //square root = radical sign,U+221A ISOtech --> - HTML_ENTITIES[8733] = "∝"; //proportional to, U+221D ISOtech --> - HTML_ENTITIES[8734] = "∞"; //infinity, U+221E ISOtech --> - HTML_ENTITIES[8736] = "∠"; //angle, U+2220 ISOamso --> - HTML_ENTITIES[8743] = "∧"; //logical and = wedge, U+2227 ISOtech --> - HTML_ENTITIES[8744] = "∨"; //logical or = vee, U+2228 ISOtech --> - HTML_ENTITIES[8745] = "∩"; //intersection = cap, U+2229 ISOtech --> - HTML_ENTITIES[8746] = "∪"; //union = cup, U+222A ISOtech --> - HTML_ENTITIES[8747] = "∫"; //integral, U+222B ISOtech --> - HTML_ENTITIES[8756] = "∴"; //therefore, U+2234 ISOtech --> - HTML_ENTITIES[8764] = "∼"; //tilde operator = varies with = similar to,U+223C ISOtech --> - // - HTML_ENTITIES[8773] = "≅"; //approximately equal to, U+2245 ISOtech --> - HTML_ENTITIES[8776] = "≈"; //almost equal to = asymptotic to,U+2248 ISOamsr --> - HTML_ENTITIES[8800] = "≠"; //not equal to, U+2260 ISOtech --> - HTML_ENTITIES[8801] = "≡"; //identical to, U+2261 ISOtech --> - HTML_ENTITIES[8804] = "≤"; //less-than or equal to, U+2264 ISOtech --> - HTML_ENTITIES[8805] = "≥"; //greater-than or equal to,U+2265 ISOtech --> - HTML_ENTITIES[8834] = "⊂"; //subset of, U+2282 ISOtech --> - HTML_ENTITIES[8835] = "⊃"; //superset of, U+2283 ISOtech --> - // - HTML_ENTITIES[8838] = "⊆"; //subset of or equal to, U+2286 ISOtech --> - HTML_ENTITIES[8839] = "⊇"; //superset of or equal to,U+2287 ISOtech --> - HTML_ENTITIES[8853] = "⊕"; //circled plus = direct sum,U+2295 ISOamsb --> - HTML_ENTITIES[8855] = "⊗"; //circled times = vector product,U+2297 ISOamsb --> - HTML_ENTITIES[8869] = "⊥"; //up tack = orthogonal to = perpendicular,U+22A5 ISOtech --> - HTML_ENTITIES[8901] = "⋅"; //dot operator, U+22C5 ISOamsb --> - // - // - HTML_ENTITIES[8968] = "⌈"; //left ceiling = apl upstile,U+2308 ISOamsc --> - HTML_ENTITIES[8969] = "⌉"; //right ceiling, U+2309 ISOamsc --> - HTML_ENTITIES[8970] = "⌊"; //left floor = apl downstile,U+230A ISOamsc --> - HTML_ENTITIES[8971] = "⌋"; //right floor, U+230B ISOamsc --> - HTML_ENTITIES[9001] = "⟨"; //left-pointing angle bracket = bra,U+2329 ISOtech --> - // - HTML_ENTITIES[9002] = "⟩"; //right-pointing angle bracket = ket,U+232A ISOtech --> - // - // - HTML_ENTITIES[9674] = "◊"; //lozenge, U+25CA ISOpub --> - // - HTML_ENTITIES[9824] = "♠"; //black spade suit, U+2660 ISOpub --> - // - HTML_ENTITIES[9827] = "♣"; //black club suit = shamrock,U+2663 ISOpub --> - HTML_ENTITIES[9829] = "♥"; //black heart suit = valentine,U+2665 ISOpub --> - HTML_ENTITIES[9830] = "♦"; //black diamond suit, U+2666 ISOpub --> - // - HTML_ENTITIES[338] = "Œ"; // -- latin capital ligature OE,U+0152 ISOlat2 --> - HTML_ENTITIES[339] = "œ"; // -- latin small ligature oe, U+0153 ISOlat2 --> - // - HTML_ENTITIES[352] = "Š"; // -- latin capital letter S with caron,U+0160 ISOlat2 --> - HTML_ENTITIES[353] = "š"; // -- latin small letter s with caron,U+0161 ISOlat2 --> - HTML_ENTITIES[376] = "Ÿ"; // -- latin capital letter Y with diaeresis,U+0178 ISOlat2 --> - // - HTML_ENTITIES[710] = "ˆ"; // -- modifier letter circumflex accent,U+02C6 ISOpub --> - HTML_ENTITIES[732] = "˜"; //small tilde, U+02DC ISOdia --> - // - HTML_ENTITIES[8194] = " "; //en space, U+2002 ISOpub --> - HTML_ENTITIES[8195] = " "; //em space, U+2003 ISOpub --> - HTML_ENTITIES[8201] = " "; //thin space, U+2009 ISOpub --> - HTML_ENTITIES[8204] = "‌"; //zero width non-joiner,U+200C NEW RFC 2070 --> - HTML_ENTITIES[8205] = "‍"; //zero width joiner, U+200D NEW RFC 2070 --> - HTML_ENTITIES[8206] = "‎"; //left-to-right mark, U+200E NEW RFC 2070 --> - HTML_ENTITIES[8207] = "‏"; //right-to-left mark, U+200F NEW RFC 2070 --> - HTML_ENTITIES[8211] = "–"; //en dash, U+2013 ISOpub --> - HTML_ENTITIES[8212] = "—"; //em dash, U+2014 ISOpub --> - HTML_ENTITIES[8216] = "‘"; //left single quotation mark,U+2018 ISOnum --> - HTML_ENTITIES[8217] = "’"; //right single quotation mark,U+2019 ISOnum --> - HTML_ENTITIES[8218] = "‚"; //single low-9 quotation mark, U+201A NEW --> - HTML_ENTITIES[8220] = "“"; //left double quotation mark,U+201C ISOnum --> - HTML_ENTITIES[8221] = "”"; //right double quotation mark,U+201D ISOnum --> - HTML_ENTITIES[8222] = "„"; //double low-9 quotation mark, U+201E NEW --> - HTML_ENTITIES[8224] = "†"; //dagger, U+2020 ISOpub --> - HTML_ENTITIES[8225] = "‡"; //double dagger, U+2021 ISOpub --> - HTML_ENTITIES[8240] = "‰"; //per mille sign, U+2030 ISOtech --> - HTML_ENTITIES[8249] = "‹"; //single left-pointing angle quotation mark,U+2039 ISO proposed --> - // - HTML_ENTITIES[8250] = "›"; //single right-pointing angle quotation mark,U+203A ISO proposed --> - // - HTML_ENTITIES[8364] = "€"; // -- euro sign, U+20AC NEW --> - }; - - /** - * The array of escaped XML character values, indexed on char value. - *

- * XML entities values were derived from Jakarta Commons Lang - * org.apache.commons.lang.Entities class. - */ - private static final String[] XML_ENTITIES = new String[63]; - - static { - XML_ENTITIES[34] = """; // " - double-quote - XML_ENTITIES[38] = "&"; // & - ampersand - XML_ENTITIES[39] = "'"; // ' - quote - XML_ENTITIES[60] = "<"; // < - less-than - XML_ENTITIES[62] = ">"; // > - greater-than - }; - - // --------------------------------------------------------- Public Methods - - /** - * Perform an auto post redirect to the specified target using the given - * response. If the params Map is defined then the form will post these - * values as name value pairs. If the compress value is true, this method - * will attempt to gzip compress the response content if requesting - * browser accepts "gzip" encoding. - *

- * Once this method has returned you should not attempt to write to the - * servlet response. - * - * @param request the servlet request - * @param response the servlet response - * @param target the target URL to send the auto post redirect to - * @param params the map of parameter values to post - * @param compress the flag to specify whether to attempt gzip compression - * of the response content - */ - public static void autoPostRedirect(HttpServletRequest request, - HttpServletResponse response, String target, Map params, - boolean compress) { - - Validate.notNull(request, "Null response parameter"); - Validate.notNull(response, "Null response parameter"); - Validate.notNull(target, "Null target parameter"); - - HtmlStringBuffer buffer = new HtmlStringBuffer(1024); - buffer.append(""); - buffer.append("

"); - for (Map.Entry entry : params.entrySet()) { - buffer.elementStart("textarea"); - buffer.appendAttribute("name", entry.getKey()); - buffer.elementEnd(); - buffer.append(entry.getValue()); - buffer.elementEnd("textarea"); - } - buffer.append("
"); - - // Determine whether browser will accept gzip compression - if (compress) { - compress = false; - Enumeration e = request.getHeaders("Accept-Encoding"); - - while (e.hasMoreElements()) { - String name = (String) e.nextElement(); - if (name.indexOf("gzip") != -1) { - compress = true; - break; - } - } - } - - OutputStream os = null; - GZIPOutputStream gos = null; - try { - response.setContentType("text/html"); - - if (compress) { - response.setHeader("Content-Encoding", "gzip"); - - os = response.getOutputStream(); - gos = new GZIPOutputStream(os); - gos.write(buffer.toString().getBytes()); - - } else { - response.setContentLength(buffer.length()); - - os = response.getOutputStream(); - os.write(buffer.toString().getBytes()); - } - - } catch (IOException ex) { - org.apache.click.util.ClickUtils.getLogService().error(ex.getMessage(), ex); - - } finally { - org.apache.click.util.ClickUtils.close(gos); - org.apache.click.util.ClickUtils.close(os); - } - } - - /** - * A helper method that binds the submitted request value to the Field's - * value. Since Field values are only bound during the "onProcess" - * event, this method can be used to bind a submitted Field value during - * the "onInit" event, which occurs before the - * "onProcess" event. - *

- * This is especially useful for dynamic Form and Page behavior where Field - * values are inspected during the "onInit" event to add or remove - * specific Fields. - *

- * Please note: this method won't bind disabled fields, unless the - * field has an incoming request parameter matching its name. If an incoming - * request parameter is present, this method will switch off the Field's - * disabled property. - *

- * This method delegates to - * {@link #canBind(org.openidentityplatform.openam.click.Control, org.openidentityplatform.openam.click.Context)} - * to check if the Field value can be bound. - *

- *

-     * public void onInit() {
-     *     Form form = new Form("form");
-     *     Select select = new Select("select");
-     *     select.setAttribute("onchange", "Click.submit(form, false)");
-     *
-     *     // Bind the select Field request value
-     *     ClickUtils.bind(select);
-     *
-     *     if (select.getValue() == COMPANY) {
-     *         form.add(new TextField("companyName"));
-     *     } else {
-     *         form.add(new TextField("fullname"));
-     *         form.add(new TextField("age"));
-     *     }
-     * } 
- * - * @param field the Field to bind - */ - public static void bind(Field field) { - Context context = Context.getThreadLocalContext(); - if (canBind(field, context)) { - bindField(field, context); - } - } - - /** - * A helper method that binds the submitted request value to the Link's - * value. See {@link #bind(org.openidentityplatform.openam.click.control.Field)} for a detailed - * description. - *

- * This method delegates to - * {@link #canBind(org.openidentityplatform.openam.click.Control, org.openidentityplatform.openam.click.Context)} - * to check if the Link value can be bound. - * - * @param link the AbstractLink to bind - */ - public static void bind(AbstractLink link) { - Context context = Context.getThreadLocalContext(); - if (canBind(link, context)) { - link.bindRequestValue(); - } - } - - /** - * A helper method that binds the submitted request values of all Fields - * and Links inside the given container or child containers. See - * {@link #bind(org.openidentityplatform.openam.click.control.Field)} for a detailed description. - *

- * This method delegates to - * {@link #canBind(org.openidentityplatform.openam.click.Control, org.openidentityplatform.openam.click.Context)} - * to check if the Container Fields and Links can be bound. - *

- * Below is an example to bind Form Field's during the onInit event: - * - *

-     * public void onInit() {
-     *     Form form = new Form("form");
-     *     Checkbox commentChk = new Checkbox("comment");
-     *     Select select = new Select("select");
-     *     select.setAttribute("onchange", "Click.submit(form, false)");
-     *
-     *     // Bind all Form Field request values
-     *     ClickUtils.bind(form);
-     *
-     *     if (select.getValue() == COMPANY) {
-     *         form.add(new TextField("companyName"));
-     *     } else {
-     *         form.add(new TextField("fullname"));
-     *         form.add(new TextField("age"));
-     *     }
-     *
-     *     if (commentChk.isChecked()) {
-     *         form.add(new TextArea("feedback"));
-     *     }
-     * } 
- * - * @param container the container which Fields and Links to bind - */ - public static void bind(Container container) { - Context context = Context.getThreadLocalContext(); - if (canBind(container, context)) { - bind(container, context); - } - } - - /** - * A helper method that binds and validates the Field's submitted request - * value. This method will return true if the validation succeeds, false - * otherwise. See {@link #bind(org.openidentityplatform.openam.click.control.Field)} for a - * detailed description. - *

- * This method delegates to - * {@link #canBind(org.openidentityplatform.openam.click.Control, org.openidentityplatform.openam.click.Context)} - * to check if the Field value can be bound and validated. - *

- * Please note: this method won't bind and validate disabled fields, - * unless the field has an incoming request parameter matching its name. - * If an incoming request parameter is present, this method will switch off - * the Field's disabled property. - *

- *

-     * public void onInit() {
-     *     Form form = new Form("form");
-     *     Select select = new Select("select", true);
-     *     select.addOption(Option.EMPTY_OPTION);
-     *
-     *     select.setAttribute("onchange", "Click.submit(form, false)");
-     *
-     *     // Bind the Field request value and validate it before continuing
-     *     if (ClickUtils.bindAndValidate(select)) {
-     *         if (select.getValue() == COMPANY) {
-     *             form.add(new TextField("companyName"));
-     *         } else {
-     *             form.add(new TextField("fullname"));
-     *             form.add(new TextField("age"));
-     *         }
-     *     }
-     * } 
- * - * @param field the Field to bind and validate - * @return true if field was bound and valid, or false otherwise - */ - public static boolean bindAndValidate(Field field) { - Context context = Context.getThreadLocalContext(); - if (canBind(field, context)) { - return bindAndValidate(field, context); - } - return false; - } - - /** - * A helper method that binds and validates the submitted request values - * of all Fields and Links inside the given container or child containers. - * This method will return true if the validation succeeds, false - * otherwise. - *

- * See {@link #bindAndValidate(org.openidentityplatform.openam.click.control.Form)} for a - * detailed description. - * - * @param container the container which Fields and Links to bind and - * validate - * @return true if all Fields are valid, false otherwise - */ - public static boolean bindAndValidate(Container container) { - Context context = Context.getThreadLocalContext(); - if (canBind(container, context)) { - return bindAndValidate(container, context); - } - return false; - } - - /** - * * A helper method that binds and validates the submitted request values - * of all Fields and Links inside the given Form or child containers. Note, - * the Form itself is also validated. - *

- * This method will return true if the validation succeeds, false otherwise. - * See {@link #bind(org.openidentityplatform.openam.click.control.Field)} for a detailed - * description. - *

- * This method delegates to - * {@link #canBind(org.openidentityplatform.openam.click.Control, org.openidentityplatform.openam.click.Context)} - * to check if the Form Fields and Links can be bound and validated. - * - *

-     * public void onInit() {
-     *     Form form = new Form("form");
-     *     Checkbox commentChk = new Checkbox("comment");
-     *     Select select = new Select("select", true);
-     *     select.addOption(Option.EMPTY_OPTION);
-     *
-     *     select.setAttribute("onchange", "Click.submit(form, false)");
-     *
-     *     // Bind all Form field request values and validate it before continuing
-     *     if (ClickUtils.bindAndValidate(form)) {
-     *
-     *         if (select.getValue() == COMPANY) {
-     *             form.add(new TextField("companyName"));
-     *         } else {
-     *             form.add(new TextField("fullname"));
-     *             form.add(new TextField("age"));
-     *         }
-     *
-     *         if (commentChk.isChecked()) {
-     *             form.add(new TextArea("feedback"));
-     *         }
-     *     }
-     * } 
- * - * @param form the form which Fields and Links to bind and validate - * @return true if the form, it's fields and links was bound and valid, false - * otherwise - */ - public static boolean bindAndValidate(Form form) { - Context context = Context.getThreadLocalContext(); - if (canBind(form, context)) { - return bindAndValidate(form, context); - } - return false; - } - - /** - * Return a new XML Document for the given input stream. - * - * @param inputStream the input stream - * @return new XML Document - * @throws RuntimeException if a parsing error occurs - */ - public static Document buildDocument(InputStream inputStream) { - return buildDocument(inputStream, null); - } - - /** - * Return a new XML Document for the given input stream and XML entity - * resolver. - * - * @param inputStream the input stream - * @param entityResolver the XML entity resolver - * @return new XML Document - * @throws RuntimeException if a parsing error occurs - */ - public static Document buildDocument(InputStream inputStream, - EntityResolver entityResolver) { - try { - DocumentBuilderFactory factory = - DocumentBuilderFactory.newInstance(); - - DocumentBuilder builder = factory.newDocumentBuilder(); - - if (entityResolver != null) { - builder.setEntityResolver(entityResolver); - } - - return builder.parse(inputStream); - - } catch (Exception ex) { - throw new RuntimeException("Error parsing XML", ex); - } - } - - /** - * Return true if the given control's request value can be bound, false - * otherwise. - *

- * The following algorithm is used to determine if the Control can be - * bound to a request value or not. - *

    - *
  • return false if the request is a forward. - * See {@link org.apache.click.Context#isForward()}
  • - *
  • return true if the request is an Ajax request. - * See {@link org.apache.click.Context#isAjaxRequest()}
  • - *
  • return true if the control has no parent Form
  • - *
  • return true if the control's parent Form was submitted, false otherwise. - * See {@link org.apache.click.control.Form#isFormSubmission()}
  • - *
- * - * @param control the control to check if it can be bound or not - * @param context the request context - * @return true if the given control request value be bound, false otherwise - */ - public static boolean canBind(Control control, Context context) { - - if (context.isForward()) { - return false; - } - - // This can cause issue with two fields inside a form with the same name. - if (context.isAjaxRequest()) { - return true; - } - - Form form = ContainerUtils.findForm(control); - if (form == null && control instanceof Form) { - form = (Form) control; - } - if (form != null) { - return form.isFormSubmission(); - } - return true; - } - - /** - * Returns the Class object associated with the class or - * interface with the given string name, using the current Thread context - * class loader. - * - * @param classname the name of the class to load - * @return the Class object - * @throws ClassNotFoundException if the class cannot be located - */ - public static Class classForName(String classname) - throws ClassNotFoundException { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - return Class.forName(classname, true, classLoader); - } - - /** - * Close the given closeable (Reader, Writer, Stream) and ignore any - * exceptions thrown. - * - * @param closeable the closeable (Reader, Writer, Stream) to close. - */ - public static void close(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException ioe) { - // Ignore - } - } - } - - /** - * Creates a template model of key/value pairs which can be used by template - * engines such as Velocity and Freemarker. - *

- * The following objects will be added to the model: - *

    - *
  • the Page {@link org.openidentityplatform.openam.click.Page#model model} Map key/value - * pairs - *
  • - *
  • context - the Servlet context path, e.g. /mycorp - *
  • - *
  • format - the Page {@link Format} object for formatting the display - * of objects. - *
  • - *
  • messages - the {@link MessagesMap} adaptor for the - * {@link org.apache.click.Page#getMessages()} method. - *
  • - *
  • path - the {@link org.openidentityplatform.openam.click.Page#path path} of the page - * template. - *
  • - *
  • request - the page {@link jakarta.servlet.http.HttpServletRequest} - * object. - *
  • - *
  • response - the page {@link jakarta.servlet.http.HttpServletResponse} - * object. - *
  • - *
  • session - the {@link SessionMap} adaptor for the users - * {@link jakarta.servlet.http.HttpSession}. - *
  • - *
- * - * @param page the page to populate the template model from - * @param context the request context - * @return a template model as a map - */ - public static Map createTemplateModel(final Page page, Context context) { - - ConfigService configService = ClickUtils.getConfigService(context.getServletContext()); - LogService logger = configService.getLogService(); - - final Map model = new HashMap(page.getModel()); - - final HttpServletRequest request = context.getRequest(); - - Object pop = model.put("request", request); - if (pop != null && !page.isStateful()) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"request\". The page model object " - + pop + " has been replaced with the request object"; - logger.warn(msg); - } - - pop = model.put("response", context.getResponse()); - if (pop != null && !page.isStateful()) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"response\". The page model object " - + pop + " has been replaced with the response object"; - logger.warn(msg); - } - - SessionMap sessionMap = new SessionMap(request.getSession(false)); - pop = model.put("session", sessionMap); - if (pop != null && !page.isStateful()) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"session\". The page model object " - + pop + " has been replaced with the request " - + " session"; - logger.warn(msg); - } - - pop = model.put("context", request.getContextPath()); - if (pop != null && !page.isStateful()) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"context\". The page model object " - + pop + " has been replaced with the request " - + " context path"; - logger.warn(msg); - } - - Format format = page.getFormat(); - if (format != null) { - pop = model.put("format", format); - if (pop != null && !page.isStateful()) { - String msg = page.getClass().getName() + " on " - + page.getPath() - + " model contains an object keyed with reserved " - + "name \"format\". The page model object " + pop - + " has been replaced with the format object"; - logger.warn(msg); - } - } - - String path = page.getPath(); - if (path != null) { - pop = model.put("path", path); - if (pop != null && !page.isStateful()) { - String msg = page.getClass().getName() + " on " - + page.getPath() - + " model contains an object keyed with reserved " - + "name \"path\". The page model object " + pop - + " has been replaced with the page path"; - logger.warn(msg); - } - } - - pop = model.put("messages", page.getMessages()); - if (pop != null && !page.isStateful()) { - String msg = page.getClass().getName() + " on " + page.getPath() - + " model contains an object keyed with reserved " - + "name \"messages\". The page model object " - + pop + " has been replaced with the request " - + " messages"; - logger.warn(msg); - } - - return model; - } - - /** - * Invalidate the specified cookie and delete it from the response object. - *

- * This method was derived from Atlassian CookieUtils method of - * the same name, release under the BSD License. - * - * @param request the servlet request - * @param response the servlet response - * @param cookieName The name of the cookie you want to delete - * @param path of the path the cookie you want to delete - */ - public static void invalidateCookie(HttpServletRequest request, - HttpServletResponse response, String cookieName, String path) { - - setCookie(request, response, cookieName, null, 0, path); - } - - /** - * Return true is this is an Ajax request, false otherwise. - *

- * An Ajax request is identified by the presence of the request header - * or request parameter: "X-Requested-With". - * "X-Requested-With" is the de-facto standard identifier used by - * Ajax libraries. - *

- * Note: incoming requests that contains a request parameter - * "X-Requested-With" will result in this method returning true, even - * though the request itself was not initiated through a XmlHttpRequest - * object. This allows one to programmatically enable Ajax requests. A common - * use case for this feature is when uploading files through an IFrame element. - * By specifying "X-Requested-With" as a request parameter the IFrame - * request will be handled like a normal Ajax request. - * - * @param request the servlet request - * @return true if this is an Ajax request, false otherwise - */ - public static boolean isAjaxRequest(HttpServletRequest request) { - return request.getHeader(X_REQUESTED_WITH) != null - || request.getParameter(X_REQUESTED_WITH) != null; - } - - /** - * Return true if the request is a multi-part content type POST request. - * - * @param request the page servlet request - * @return true if the request is a multi-part content type POST request - */ - public static boolean isMultipartRequest(HttpServletRequest request) { - //return ServletFileUpload.isMultipartContent(request); - //throw new RuntimeException("not implemented"); - return false; - } - - /** - * Invalidate the specified cookie and delete it from the response object. Deletes only cookies mapped - * against the root "/" path. Otherwise use - * {@link #invalidateCookie(HttpServletRequest, HttpServletResponse, String, String)} - *

- * This method was derived from Atlassian CookieUtils method of - * the same name, release under the BSD License. - * - * @see #invalidateCookie(HttpServletRequest, HttpServletResponse, String, String) - * - * @param request the servlet request - * @param response the servlet response - * @param cookieName The name of the cookie you want to delete. - */ - public static void invalidateCookie(HttpServletRequest request, - HttpServletResponse response, String cookieName) { - - invalidateCookie(request, response, cookieName, "/"); - } - - /** - * Return a resource bundle using the specified base name. - * - * @param baseName the base name of the resource bundle, a fully qualified class name - * @return a resource bundle for the given base name - * @throws MissingResourceException if no resource bundle for the specified base name can be found - */ - public static ResourceBundle getBundle(String baseName) { - return getBundle(baseName, Locale.getDefault()); - } - - /** - * Return a resource bundle using the specified base name and locale. - * - * @param baseName the base name of the resource bundle, a fully qualified class name - * @param locale the locale for which a resource bundle is desired - * @return a resource bundle for the given base name and locale - * @throws MissingResourceException if no resource bundle for the specified base name can be found - */ - public static ResourceBundle getBundle(String baseName, Locale locale) { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - return ResourceBundle.getBundle(baseName, locale, classLoader); - } - - /** - * Return the first XML child Element for the given parent Element and child - * Element name. - * - * @param parent the parent element to get the child from - * @param name the name of the child element - * @return the first child element for the given name and parent - */ - public static Element getChild(Element parent, String name) { - NodeList nodeList = parent.getChildNodes(); - for (int i = 0; i < nodeList.getLength(); i++) { - Node node = nodeList.item(i); - if (node instanceof Element) { - if (node.getNodeName().equals(name)) { - return (Element) node; - } - } - } - return null; - } - - /** - * Return the list of XML child Element elements with the given name from - * the given parent Element. - * - * @param parent the parent element to get the child from - * @param name the name of the child element - * @return the list of XML child elements for the given name - */ - public static List getChildren(Element parent, String name) { - List list = new ArrayList(); - NodeList nodeList = parent.getChildNodes(); - for (int i = 0; i < nodeList.getLength(); i++) { - Node node = nodeList.item(i); - if (node instanceof Element) { - if (node.getNodeName().equals(name)) { - list.add((Element) node); - } - } - } - return list; - } - - /** - * Return the InputStream for the Click configuration file click.xml. - * This method will first lookup the click.xml under the - * applications WEB-INF directory, and then if not found it will - * attempt to find the configuration file on the classpath root. - * - * @param servletContext the servlet context to obtain the Click configuration - * from - * @return the InputStream for the Click configuration file - * @throws RuntimeException if the resource could not be found - */ - public static InputStream getClickConfig(ServletContext servletContext) { - InputStream inputStream = - servletContext.getResourceAsStream(DEFAULT_APP_CONFIG); - - if (inputStream == null) { - inputStream = org.apache.click.util.ClickUtils.getResourceAsStream("/click.xml", org.apache.click.util.ClickUtils.class); - if (inputStream == null) { - String msg = - "could not find click app configuration file: " - + DEFAULT_APP_CONFIG + " or click.xml on classpath"; - throw new RuntimeException(msg); - } - } - - return inputStream; - } - - /** - * Return the application configuration service instance from the given - * servlet context. - * - * @param servletContext the servlet context to get the config service instance - * @return the application config service instance - */ - public static ConfigService getConfigService(ServletContext servletContext) { - ConfigService configService = (ConfigService) - servletContext.getAttribute(ConfigService.CONTEXT_NAME); - - if (configService != null) { - return configService; - - } else { - String msg = - "could not find ConfigService in the SerlvetContext under the" - + " name '" + ConfigService.CONTEXT_NAME + "'.\nThis can occur" - + " if ClickUtils.getConfigService() is called before" - + " ClickServlet is initialized by the servlet container.\n" - + "To fix ensure that ClickServlet is loaded at startup by" - + " editing your web.xml and setting the load-on-startup to 0:\n\n" - + " \n" - + " ClickServlet\n" - + " org.apache.click.ClickServlet\n" - + " 0\n" - + " \n"; - - throw new RuntimeException(msg); - } - } - - /** - * Returns the specified Cookie object, or null if the cookie does not exist. - *

- * This method was derived from Atlassian CookieUtils method of - * the same name, release under the BSD License. - * - * @param request the servlet request - * @param name the name of the cookie - * @return the Cookie object if it exists, otherwise null - */ - public static Cookie getCookie(HttpServletRequest request, String name) { - Cookie cookies[] = request.getCookies(); - - if (cookies == null || name == null || name.length() == 0) { - return null; - } - - //Otherwise, we have to do a linear scan for the cookie. - for (Cookie cookie : cookies) { - if (cookie.getName().equals(name)) { - return cookie; - } - } - - return null; - } - - /** - * Sets the given cookie values in the servlet response. - *

- * This will also put the cookie in a list of cookies to send with this request's response - * (so that in case of a redirect occurring down the chain, the first filter - * will always try to set this cookie again) - *

- * The cookie secure flag is set if the request is secure. - *

- * This method was derived from Atlassian CookieUtils method of - * the same name, release under the BSD License. - * - * @param request the servlet request - * @param response the servlet response - * @param name the cookie name - * @param value the cookie value - * @param maxAge the maximum age of the cookie in seconds. A negative - * value will expire the cookie at the end of the session, while 0 will delete - * the cookie. - * @param path the cookie path - * @return the Cookie object created and set in the response - */ - public static Cookie setCookie(HttpServletRequest request, HttpServletResponse response, - String name, String value, int maxAge, String path) { - - Cookie cookie = new Cookie(name, value); - cookie.setMaxAge(maxAge); - cookie.setPath(path); - cookie.setSecure(request.isSecure()); - response.addCookie(cookie); - - return cookie; - } - - /** - * Returns the value of the specified cookie as a String. If the cookie - * does not exist, the method returns null. - *

- * This method was derived from Atlassian CookieUtils method of - * the same name, release under the BSD License. - * - * @param request the servlet request - * @param name the name of the cookie - * @return the value of the cookie, or null if the cookie does not exist. - */ - public static String getCookieValue(HttpServletRequest request, String name) { - Cookie cookie = getCookie(request, name); - - if (cookie != null) { - return cookie.getValue(); - } - - return null; - } - - /** - * Return the Click Framework version string. - * - * @return the Click Framework version string - */ - public static String getClickVersion() { - ResourceBundle bundle = getBundle("click-control"); - return bundle.getString("click-version"); - } - - /** - * Return the web application version string. - * - * @return the web application version string - */ - public static String getApplicationVersion() { - return applicationVersion; - } - - /** - * Set the web application version string. - * - * @param applicationVersion the web application version string - */ - public static void setApplicationVersion(String applicationVersion) { - ClickUtils.applicationVersion = applicationVersion; - cachedApplicationVersionIndicator = null; - } - - /** - * Return Click's version indicator for static web resources - * (eg css, js and image files) if resource versioning is active, - * otherwise this method will return an empty string. - *

- * Click's resource versioning becomes active under the following - * conditions: - *

    - *
  • the {@link #ENABLE_RESOURCE_VERSION} request attribute must be set - * to true
  • - *
  • the application mode must be either "production" or "profile"
  • - *
- * - * The version indicator is based on the current Click release version. - * For example when using Click 1.4 this method will return the string - * "_1.4". - * - * @param context the request context - * @return a version indicator for web resources - */ - public static String getResourceVersionIndicator(Context context) { - if (cachedResourceVersionIndicator != null) { - return cachedResourceVersionIndicator; - } - - ConfigService configService = getConfigService(context.getServletContext()); - - boolean isProductionModes = configService.isProductionMode() - || configService.isProfileMode(); - - if (isProductionModes - && isEnableResourceVersion(context)) { - - cachedResourceVersionIndicator = RESOURCE_VERSION_INDICATOR; - return cachedResourceVersionIndicator; - - } else { - return ""; - } - } - - /** - * If resource versioning is active this method will return the - * application version indicator for static web resources - * (eg JavaScript and Css) otherwise this method will return an empty string. - *

- * Application resource versioning becomes active under the following - * conditions: - *

    - *
  • the {@link #ENABLE_RESOURCE_VERSION} request attribute must be set - * to true
  • - *
  • the application mode must be either "production" or "profile"
  • - *
- * - * The version indicator is based on the application version. - * For example if the application version is 1.2 this method will - * return the string "_1.2". - *

- * The application version can be set through the static method - * {@link #setApplicationVersion(java.lang.String)}. - * - * @return an application version indicator for web resources - */ - public static String getApplicationResourceVersionIndicator() { - // Return the cached version first - if (cachedApplicationVersionIndicator != null) { - return cachedApplicationVersionIndicator; - } - - // Check if the Context has been set - if (Context.hasThreadLocalContext()) { - - Context context = Context.getThreadLocalContext(); - ConfigService configService = ClickUtils.getConfigService(context.getServletContext()); - - boolean isProductionModes = configService.isProductionMode() - || configService.isProfileMode(); - - if (isProductionModes && ClickUtils.isEnableResourceVersion(context)) { - String version = getApplicationVersion(); - if (StringUtils.isNotBlank(version)) { - cachedApplicationVersionIndicator = VERSION_INDICATOR_SEP - + version; - return cachedApplicationVersionIndicator; - } - } - } - return ""; - } - - /** - * Return the given control CSS selector or null if no selector can be found. - *

- * Please note: it is highly recommended to set a control's ID - * attribute when dealing with Ajax requests. - *

- * The CSS selector is calculated as follows: - *

    - *
  1. if control.getId() is set, prepend it with a '#' char - * and return the value. An example selector will be: #field-id - *
  2. - * - *
  3. if control.getName() is set do the following: - *
      - *
    1. if the control is of type {@link org.apache.click.control.ActionLink}, - * it's "class" attribute selector will be returned. For example: - * a[class=red]. Please note: if the link class attribute is - * not set, the class attribute will be set to its name, prefixed with - * a dash, '-'. For example: a[class=-my-link]. - *
    2. - * - *
    3. if the control is not an ActionLink, it is assumed the control - * will render its "name" attribute and the name attribute - * selector will be returned. For example: input[name=my-button]. - *
    4. - *
    - *
  4. - * - *
  5. otherwise return null. - *
  6. - *
- * - * @param control the control which CSS selector to return - * @return the control CSS selector or null if no selector can be found - * @throws IllegalArgumentException if control is null - */ - public static String getCssSelector(Control control) { - if (control == null) { - throw new IllegalArgumentException("Control cannot be null"); - } - - String id = control.getId(); - String name = control.getName(); - String cssSelector = null; - - if (StringUtils.isNotBlank(id)) { - cssSelector = '#' + id; - } else if (StringUtils.isNotBlank(name)) { - String tag = null; - - // Try and create a more specific selector by retrieving the - // control's tag - if (control instanceof AbstractControl) { - tag = StringUtils.defaultString(((AbstractControl) control).getTag()); - } - - HtmlStringBuffer buffer = new HtmlStringBuffer(20); - - // Handle ActionLink (perhaps other link controls too?) differently - // as it doesn't render the "name" attribute. The "name" attribute - // is used by links for bookmarking purposes. Instead set the class - // attribute to the link's name and use that as the selector. - if (control instanceof ActionLink) { - ActionLink link = (ActionLink) control; - if (!link.hasAttribute("class")) { - link.setAttribute("class", '-' + name); - } - buffer.append(tag).append("[class*="); - buffer.append(link.getAttribute("class")).append("]"); - - } else { - buffer.append(tag).append("[name="); - buffer.append(name).append("]"); - } - cssSelector = buffer.toString(); - } - return cssSelector; - } - - /** - * Populate the given object's attributes with the Form's field values. - *

- * The specified Object can either be a POJO (plain old java object) or - * a {@link java.util.Map}. If a POJO is specified, its attributes are - * populated from matching form fields. If a map is specified, its - * key/value pairs are populated from matching form fields. - * - * @param form the Form to obtain field values from - * @param object the object to populate with field values - * @param debug log debug statements when populating the object - */ - public static void copyFormToObject(Form form, Object object, - boolean debug) { - - ContainerUtils.copyContainerToObject(form, object); - } - - /** - * Populate the given Form field values with the object's attributes. - *

- * The specified Object can either be a POJO (plain old java object) or - * a {@link java.util.Map}. If a POJO is specified, its attributes are - * copied to matching form fields. If a map is specified, its key/value - * pairs are copied to matching form fields. - * - * @param object the object to obtain attribute values from - * @param form the Form to populate - * @param debug log debug statements when populating the form - */ - public static void copyObjectToForm(Object object, Form form, - boolean debug) { - - ContainerUtils.copyObjectToContainer(object, form); - } - - /** - * Deploy the specified classpath resource to the given target directory - * under the web application root directory. - *

- * This method will not override any existing resources found in the - * target directory. - *

- * If an IOException or SecurityException occurs this method will log a - * warning message. - * - * @param servletContext the web applications servlet context - * @param resource the classpath resource name - * @param targetDir the target directory to deploy the resource to - */ - public static void deployFile(ServletContext servletContext, - String resource, String targetDir) { - - if (servletContext == null) { - throw new IllegalArgumentException("Null servletContext parameter"); - } - - if (StringUtils.isBlank(resource)) { - String msg = "Null resource parameter not defined"; - throw new IllegalArgumentException(msg); - } - - String realTargetDir = servletContext.getRealPath("/") + File.separator; - - if (StringUtils.isNotBlank(targetDir)) { - realTargetDir = realTargetDir + targetDir; - } - - - LogService logger = getConfigService(servletContext).getLogService(); - - try { - - // Create files deployment directory - File directory = new File(realTargetDir); - if (!directory.exists()) { - if (!directory.mkdirs()) { - String msg = - "could not create deployment directory: " + directory; - throw new IOException(msg); - } - } - - String destination = resource; - int index = resource.lastIndexOf('/'); - if (index != -1) { - destination = resource.substring(index + 1); - } - - destination = realTargetDir + File.separator + destination; - - File destinationFile = new File(destination); - - if (!destinationFile.exists()) { - InputStream inputStream = - getResourceAsStream(resource, org.apache.click.util.ClickUtils.class); - - if (inputStream != null) { - FileOutputStream fos = null; - try { - fos = new FileOutputStream(destinationFile); - byte[] buffer = new byte[1024]; - while (true) { - int length = inputStream.read(buffer); - if (length < 0) { - break; - } - fos.write(buffer, 0, length); - } - - if (logger.isTraceEnabled()) { - int lastIndex = - destination.lastIndexOf(File.separatorChar); - if (lastIndex != -1) { - destination = - destination.substring(lastIndex + 1); - } - String msg = - "deployed " + targetDir + "/" + destination; - logger.trace(msg); - } - - } finally { - close(fos); - close(inputStream); - } - } else { - String msg = - "could not locate classpath resource: " + resource; - throw new IOException(msg); - } - } - - } catch (IOException ioe) { - String msg = - "error occurred deploying resource " + resource - + ", error " + ioe; - logger.warn(msg); - - } catch (SecurityException se) { - String msg = - "error occurred deploying resource " + resource - + ", error " + se; - logger.warn(msg); - } - } - - /** - * Deploy the specified classpath resources to the given target directory - * under the web application root directory. - * - * @param servletContext the web applications servlet context - * @param resources the array of classpath resource names - * @param targetDir the target directory to deploy the resource to - */ - public static void deployFiles(ServletContext servletContext, - String[] resources, String targetDir) { - - if (resources == null) { - throw new IllegalArgumentException("Null resources parameter"); - } - - for (String resource : resources) { - ClickUtils.deployFile(servletContext, resource, targetDir); - } - } - - /** - * Deploys required files (from a file list) for a control that repsects a specific convention. - *

- * Convention: - *

- * There's a descriptor file generated by the tools/standalone/dev-tasks/ListFilesTask. - * The files to deploy are all in a subdirectory placed in the same directory with the control. - * See documentation for more details.

- * - * Usage:

- * In your Control simply use the code below, and everything should work automatically. - *

-     * public void onDeploy(ServletContext servletContext) {
-     *    ClickUtils.deployFileList(servletContext, HeavyControl.class, "click");
-     * } 
- * - * @param servletContext the web applications servlet context - * @param controlClass the class of the Control that has files for deployment - * @param targetDir target directory where to deploy the files to. In most cases this - * is only the reserved directory click - */ - public static void deployFileList(ServletContext servletContext, - Class controlClass, String targetDir) { - - String packageName = ClassUtils.getPackageName(controlClass); - packageName = StringUtils.replaceChars(packageName, '.', '/'); - packageName = "/" + packageName; - String controlName = ClassUtils.getShortClassName(controlClass); - - ConfigService configService = getConfigService(servletContext); - LogService logService = configService.getLogService(); - String descriptorFile = packageName + "/" + controlName + ".files"; - logService.debug("Use deployment descriptor file:" + descriptorFile); - - InputStream is = getResourceAsStream(descriptorFile, org.apache.click.util.ClickUtils.class); - List fileList = IOUtils.readLines(is); - if (fileList == null || fileList.isEmpty()) { - logService.info("there are no files to deploy for control " + controlClass.getName()); - return; - } - - // a target dir list is required cause the ClickUtils.deployFile() is too inflexible to autodetect - // required subdirectories. - List targetDirList = new ArrayList(fileList.size()); - for (int i = 0; i < fileList.size(); i++) { - String filePath = (String) fileList.get(i); - String destination = ""; - int index = filePath.lastIndexOf('/'); - if (index != -1) { - destination = filePath.substring(0, index + 1); - } - targetDirList.add(i, targetDir + "/" + destination); - fileList.set(i, packageName + "/" + filePath); - } - - for (int i = 0; i < fileList.size(); i++) { - String source = (String) fileList.get(i); - String targetDirName = targetDirList.get(i); - ClickUtils.deployFile(servletContext, source, targetDirName); - } - - } - - /** - * Return an encoded version of the Serializable object. The object - * will be serialized, compressed and Base 64 encoded. - * - * @param object the object to encode - * @return a serialized, compressed and Base 64 string encoding of the - * given object - * @throws IOException if an I/O error occurs - * @throws IllegalArgumentException if the object parameter is null, or if - * the object is not Serializable - */ - public static String encode(Object object) throws IOException { - if (object == null) { - throw new IllegalArgumentException("null object parameter"); - } - if (!(object instanceof Serializable)) { - throw new IllegalArgumentException("parameter not Serializable"); - } - - ByteArrayOutputStream bos = null; - GZIPOutputStream gos = null; - ObjectOutputStream oos = null; - - try { - bos = new ByteArrayOutputStream(); - gos = new GZIPOutputStream(bos); - oos = new ObjectOutputStream(gos); - - oos.writeObject(object); - - } finally { - close(oos); - close(gos); - close(bos); - } - - Base64 base64 = new Base64(); - - try { - byte[] byteData = base64.encode(bos.toByteArray()); - - return new String(byteData); - - } catch (Throwable t) { - String message = - "error occurred Base64 encoding: " + object + " : " + t; - throw new IOException(message); - } - } - - /** - * Return an object from the {@link #encode(Object)} string. - * - * @param string the encoded string - * @return an object from the encoded - * @throws ClassNotFoundException if the class could not be instantiated - * @throws IOException if an data I/O error occurs - */ - public static Object decode(String string) - throws ClassNotFoundException, IOException { - - Base64 base64 = new Base64(); - byte[] byteData = null; - - try { - byteData = base64.decode(string.getBytes()); - - } catch (Throwable t) { - String message = - "error occurred Base64 decoding: " + string + " : " + t; - throw new IOException(message); - } - - ByteArrayInputStream bis = null; - GZIPInputStream gis = null; - ObjectInputStream ois = null; - try { - bis = new ByteArrayInputStream(byteData); - gis = new GZIPInputStream(bis); - ois = new ObjectInputStream(gis); - - return ois.readObject(); - - } finally { - close(ois); - close(gis); - close(bis); - } - } - - /** - * Builds a cookie string containing a username and password. - *

- * Note: with open source this is not really secure, but it prevents users - * from snooping the cookie file of others and by changing the XOR mask and - * character offsets, you can easily tweak results. - *

- * This method was derived from Atlassian CookieUtils method of - * the same name, release under the BSD License. - * - * @param username the username - * @param password the password - * @param xorMask the XOR mask to encrypt the value with, must be same as - * as the value used to decrypt the cookie password - * @return String encoding the input parameters, an empty string if one of - * the arguments equals null - */ - public static String encodePasswordCookie(String username, String password, int xorMask) { - String encoding = new String(new char[]{DELIMITER, ENCODE_CHAR_OFFSET1, ENCODE_CHAR_OFFSET2}); - - return encodePasswordCookie(username, password, encoding, xorMask); - } - - /** - * Builds a cookie string containing a username and password, using offsets - * to customize the encoding. - *

- * Note: with open source this is not really secure, but it prevents users - * from snooping the cookie file of others and by changing the XOR mask and - * character offsets, you can easily tweak results. - *

- * This method was derived from Atlassian CookieUtils method of - * the same name, release under the BSD License. - * - * @param username the username - * @param password the password - * @param encoding a String used to customize cookie encoding (only the first 3 characters are used) - * @param xorMask the XOR mask to encrypt the value with, must be same as - * as the value used to decrypt the cookie password - * @return String encoding the input parameters, an empty string if one of - * the arguments equals null. - */ - public static String encodePasswordCookie(String username, String password, String encoding, int xorMask) { - StringBuilder buf = new StringBuilder(); - - if (username != null && password != null) { - - char offset1 = (encoding != null && encoding.length() > 1) - ? encoding.charAt(1) : ENCODE_CHAR_OFFSET1; - - char offset2 = (encoding != null && encoding.length() > 2) - ? encoding.charAt(2) : ENCODE_CHAR_OFFSET2; - - byte[] bytes = (username + DELIMITER + password).getBytes(); - int b; - - for (int n = 0; n < bytes.length; n++) { - b = bytes[n] ^ (xorMask + n); - buf.append((char) (offset1 + (b & 0x0F))); - buf.append((char) (offset2 + ((b >> 4) & 0x0F))); - } - } - - return buf.toString(); - } - - /** - * Decodes a cookie string containing a username and password. - *

- * This method was derived from Atlassian CookieUtils method of - * the same name, release under the BSD License. - * - * @param cookieVal the encoded cookie username and password value - * @param xorMask the XOR mask to decrypt the value with, must be same as - * as the value used to encrypt the cookie password - * @return String[] containing the username at index 0 and the password at - * index 1, or { null, null } if cookieVal equals - * null or the empty string. - */ - public static String[] decodePasswordCookie(String cookieVal, int xorMask) { - String encoding = new String(new char[]{DELIMITER, ENCODE_CHAR_OFFSET1, ENCODE_CHAR_OFFSET2}); - - return decodePasswordCookie(cookieVal, encoding, xorMask); - } - - /** - * Decodes a cookie string containing a username and password. - *

- * This method was derived from Atlassian CookieUtils method of - * the same name, release under the BSD License. - * - * @param cookieVal the encoded cookie username and password value - * @param encoding a String used to customize cookie encoding (only the first 3 characters are used) - * - should be the same string you used to encode the cookie! - * @param xorMask the XOR mask to decrypt the value with, must be same as - * as the value used to encrypt the cookie password - * @return String[] containing the username at index 0 and the password at - * index 1, or { null, null } if cookieVal equals - * null or the empty string. - */ - public static String[] decodePasswordCookie(String cookieVal, String encoding, - int xorMask) { - - // Check that the cookie value isn't null or zero-length - if (cookieVal == null || cookieVal.length() <= 0) { - return null; - } - - char offset1 = (encoding != null && encoding.length() > 1) - ? encoding.charAt(1) : ENCODE_CHAR_OFFSET1; - - char offset2 = (encoding != null && encoding.length() > 2) - ? encoding.charAt(2) : ENCODE_CHAR_OFFSET2; - - // Decode the cookie value - char[] chars = cookieVal.toCharArray(); - byte[] bytes = new byte[chars.length / 2]; - int b; - - for (int n = 0, m = 0; n < bytes.length; n++) { - b = chars[m++] - offset1; - b |= (chars[m++] - offset2) << 4; - bytes[n] = (byte) (b ^ (xorMask + n)); - } - - cookieVal = new String(bytes); - int pos = cookieVal.indexOf(DELIMITER); - - String username = (pos < 0) ? "" : cookieVal.substring(0, pos); - String password = (pos < 0) ? "" : cookieVal.substring(pos + 1); - - return new String[]{username, password}; - } - - /** - * URL encode the specified value using the "UTF-8" encoding scheme. - *

- * For example (http://host?name=value with spaces) will become - * (http://host?name=value+with+spaces). - *

- * This method uses {@link URLEncoder#encode(java.lang.String, java.lang.String)} - * internally. - * - * @param value the value to encode using "UTF-8" - * @return an encoded URL string - */ - public static String encodeURL(Object value) { - if (value == null) { - throw new IllegalArgumentException("Null object parameter"); - } - - try { - return URLEncoder.encode(value.toString(), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - /** - * URL decode the specified value using the "UTF-8" encoding scheme. - *

- * For example (http://host?name=value+with+spaces) will become - * (http://host?name=value with spaces). - *

- * This method uses {@link URLDecoder#decode(java.lang.String, java.lang.String)} - * internally. - * - * @param value the value to decode using "UTF-8" - * @return an encoded URL string - */ - public static String decodeURL(Object value) { - if (value == null) { - throw new IllegalArgumentException("Null object parameter"); - } - - try { - return URLDecoder.decode(value.toString(), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - /** - * Return an encoded URL value for the given object using the context - * request character encoding or "UTF-8" if the request character encoding - * is not specified. - *

- * For example (http://host?name=value with spaces) will become - * (http://host?name=value+with+spaces). - *

- * This method uses - * {@link URLEncoder#encode(java.lang.String, java.lang.String)} internally. - * - * @param object the object value to encode as a URL string - * @param context the context providing the request character encoding - * @return an encoded URL string - */ - public static String encodeUrl(Object object, Context context) { - if (object == null) { - throw new IllegalArgumentException("Null object parameter"); - } - if (context == null) { - throw new IllegalArgumentException("Null context parameter"); - } - - String charset = context.getRequest().getCharacterEncoding(); - - try { - if (charset == null) { - return URLEncoder.encode(object.toString(), "UTF-8"); - - } else { - return URLEncoder.encode(object.toString(), charset); - } - - } catch (UnsupportedEncodingException uee) { - throw new RuntimeException(uee); - } - } - - /** - * Return a HTML escaped string for the given string value. - * - * @param value the string value to escape - * @return the HTML escaped string value - */ - public static String escapeHtml(String value) { - if (requiresHtmlEscape(value)) { - - HtmlStringBuffer buffer = new HtmlStringBuffer(value.length() * 2); - - buffer.appendHtmlEscaped(value); - - return buffer.toString(); - - } else { - return value; - } - } - - /** - * Return an escaped string for the given string value. The following - * characters are escaped: <, >, ", ', &. - * - * @param value the string value to escape - * @return the escaped string value - */ - public static String escape(String value) { - if (requiresEscape(value)) { - - HtmlStringBuffer buffer = new HtmlStringBuffer(value.length() * 2); - - buffer.appendEscaped(value); - - return buffer.toString(); - - } else { - return value; - } - } - - /** - * Return true if the control has a submitted request value, false otherwise. - * - * @param control the control which request parameter to check - * @return true if the control has a submitted request value, false otherwise - */ - public static boolean hasRequestParameter(Control control) { - Context context = Context.getThreadLocalContext(); - if (canBind(control, context)) { - return context.hasRequestParameter(control.getName()); - } - return false; - } - - /** - * Invoke the named method on the given object and return the boolean - * result. - * - * @see org.apache.click.Control#setListener(Object, String) - * - * @param listener the object with the method to invoke - * @param method the name of the method to invoke - * @return true if the listener method returned true - */ - public static boolean invokeListener(Object listener, String method) { - - Object result = invokeMethod(listener, method); - - if (result instanceof Boolean) { - return (Boolean) result; - - } else { - - Method targetMethod = null; - try { - targetMethod = listener.getClass().getMethod(method); - - String msg = - "Invalid listener method, missing boolean return type: " - + targetMethod; - throw new RuntimeException(msg); - } catch (Exception e) { - String msg = "Exception occurred invoking public method: " + targetMethod; - throw new RuntimeException(msg, e); - } - } - } - - /** - * Invoke the named method on the given target and return the Object result. - * - * @param target the target object with the method to invoke - * @param method the name of the method to invoke - * @return an ActionResult instance - */ - public static ActionResult invokeAction(Object target, String method) { - - Object result = invokeMethod(target, method); - - if (result == null || result instanceof ActionResult) { - return (ActionResult) result; - - } else { - - Method targetMethod = null; - try { - targetMethod = target.getClass().getMethod(method); - - String msg = - "Invalid target method, missing ActionResult return type: " - + targetMethod; - throw new RuntimeException(msg); - } catch (Exception e) { - String msg = "Exception occurred invoking public method: " + targetMethod; - throw new RuntimeException(msg, e); - } - } - } - - /** - * Return true if static web content resource versioning is enabled. - * - * @param context the request context - * @return true if static web content resource versioning is enabled - */ - public static boolean isEnableResourceVersion(Context context) { - return "true".equals(context.getRequestAttribute(ENABLE_RESOURCE_VERSION)); - } - - /** - * Return the value string limited to maxlength characters. If the string - * gets curtailed, "..." is appended to it. - *

- * Adapted from Velocity Tools Formatter. - * - * @param value the string value to limit the length of - * @param maxlength the maximum string length - * @return a length limited string - */ - public static String limitLength(String value, int maxlength) { - return limitLength(value, maxlength, "..."); - } - - /** - * Return the value string limited to maxlength characters. If the string - * gets curtailed and the suffix parameter is appended to it. - *

- * Adapted from Velocity Tools Formatter. - * - * @param value the string value to limit the length of - * @param maxlength the maximum string length - * @param suffix the suffix to append to the length limited string - * @return a length limited string - */ - public static String limitLength(String value, int maxlength, String suffix) { - String ret = value; - if (value.length() > maxlength) { - ret = value.substring(0, maxlength - suffix.length()) + suffix; - } - return ret; - } - - /** - * Return the application LogService instance using thread local Context - * to perform the lookup. - * - * @return the application LogService instance - */ - public static LogService getLogService() { - Context context = Context.getThreadLocalContext(); - ServletContext servletContext = context.getServletContext(); - ConfigService configService = getConfigService(servletContext); - LogService logService = configService.getLogService(); - return logService; - } - - /** - * Return the list of Fields for the given Form, including any Fields - * contained in FieldSets. The list of returned fields will exclude any - * Button, FieldSet or Label fields. - * - * @param form the form to obtain the fields from - * @return the list of contained form fields - */ - public static List getFormFields(Form form) { - if (form == null) { - throw new IllegalArgumentException("Null form parameter"); - } - return ContainerUtils.getInputFields(form); - } - - /** - * Return the mime-type or content-type for the given filename/extension. - *

- * Example: - *

-     * // Lookup mimetype for file
-     * String mimeType = ClickUtils.getMimeType("hello-world.pdf");
-     *
-     * // Lookup mimetype for extension
-     * mimeType = ClickUtils.getMimeType("pdf");
-     * 
- * - * @param value the filename or extension to obtain the mime-type for - * @return the mime-type for the given filename/extension, or null if not - * found - */ - public static String getMimeType(String value) { - if (value == null) { - throw new IllegalArgumentException("null filename/extension parameter"); - } - - String ext = value; - - int index = value.lastIndexOf("."); - if (index != -1) { - ext = value.substring(index + 1); - } - - try { - ResourceBundle bundle = getBundle("org/apache/click/util/mime-type"); - - return bundle.getString(ext.toLowerCase()); - - } catch (MissingResourceException mre) { - return null; - } - } - - /** - * Return the given control's top level parent's localized messages Map. - *

- * This method will walk up to the control's parent page object and - * return pages messages. If the control's top level parent is a control - * then the parent's messages map will be returned. If the top level - * parent is not a Page or Control instance an empty map will be returned. - * - * @param control the control to get the parent messages Map for - * @return the top level parent's Map of localized messages - */ - public static Map getParentMessages(Control control) { - if (control == null) { - throw new IllegalArgumentException("Null control parameter"); - } - - Object parent = control.getParent(); - if (parent == null) { - return Collections.emptyMap(); - - } else { - while (parent != null) { - if (parent instanceof Control) { - control = (Control) parent; - parent = control.getParent(); - - if (parent == null) { - return control.getMessages(); - } - - } else if (parent instanceof Page) { - Page page = (Page) parent; - return page.getMessages(); - - } else if (parent != null) { - // Unknown parent class - return Collections.emptyMap(); - } - } - } - - return Collections.emptyMap(); - } - - /** - * Return the given control's top level parent's localized message for the - * specified name. - *

- * This method will walk up to the control's parent page object and for each - * parent control found, look for a message of the specified name. A - * message found in a parent control will override the message of a child - * control. - *

- * Given the following property files: - *

- * MyPage.properties - *

-     * myfield.label=Page 
- * - * and MyForm.properties - *
-     * myfield.label=Form 
- * - * and a the following snippet: - * - *
-     * public MyPage extends Page {
-     *     public void onInit() {
-     *         MyForm form = new MyForm("form");
-     *         TextField field = new TextField("myfield");
-     *         form.add(field);
-     *
-     *         //1.
-     *         System.out.println(ClickUtils.getParentMessage(field, "myfield.label"));
-     *
-     *         addControl(form);
-     *
-     *         //2.
-     *         System.out.println(ClickUtils.getParentMessage(field, "myfield.label"));
-     *     }
-     * }
-     * 
- * - * The first (1.) println statement above will output Form because - * at that stage MyForm is the highest level parent of field. - * getParentMessage will find the property myfield.label - * in the MyForm message properties and return Form - *

- * The second (2.) println statement will output Page as now - * MyPage is the highest level parent. On its first pass up the hierarchy, - * getParentMessage will find the property myfield.label - * in the MyForm message properties and on its second pass will find the - * same property in MyPage message properties. As MyPage is higher up the - * hierarchy than MyForm, MyPage will override MyForm and the property value - * will be Page. - * - * @param control the control to get the parent message for - * @param name the specific property name to find - * @return the top level parent's Map of localized messages - */ - public static String getParentMessage(Control control, String name) { - if (control == null) { - throw new IllegalArgumentException("Null control parameter"); - } - if (name == null) { - throw new IllegalArgumentException("Null name parameter"); - } - - Object parent = control.getParent(); - if (parent == null) { - return null; - - } else { - String message = null; - while (parent != null) { - if (parent instanceof Control) { - control = (Control) parent; - if (control != null) { - if (control.getMessages().containsKey(name)) { - message = control.getMessages().get(name); - } - } - - parent = control.getParent(); - if (parent == null) { - return message; - } - - } else if (parent instanceof Page) { - Page page = (Page) parent; - if (page.getMessages().containsKey(name)) { - message = page.getMessages().get(name); - } - return message; - - } else if (parent != null) { - // Unknown parent class - return null; - } - } - } - return null; - } - - /** - * Get the parent page of the given control or null if the control has no - * parent. This method will walk up the control's parent hierarchy to - * find its parent page. - * - * @param control the control to get the parent page from - * @return the parent page of the control or null if the control has no - * parent - */ - public static Page getParentPage(Control control) { - Object parent = control.getParent(); - - while (parent != null) { - if (parent instanceof Control) { - control = (Control) parent; - parent = control.getParent(); - - } else if (parent instanceof Page) { - return (Page) parent; - - } else if (parent != null) { - throw new RuntimeException("Invalid parent class"); - } - } - - return null; - } - - /** - * Return an ordered map of request parameters from the given request. - * - * @param request the servlet request to obtain request parameters from - * @return the ordered map of request parameters - */ - public static Map getRequestParameterMap(HttpServletRequest request) { - - TreeMap requestParams = new TreeMap(); - - Enumeration paramNames = request.getParameterNames(); - while (paramNames.hasMoreElements()) { - String name = paramNames.nextElement().toString(); - - String[] values = request.getParameterValues(name); - - if (values.length == 1) { - requestParams.put(name, values[0]); - - } else { - requestParams.put(name, values); - } - } - - return requestParams; - } - - /** - * Return the page resource path from the request. For example: - *

-     * http://www.mycorp.com/banking/secure/login.htm  ->  /secure/login.htm 
- * - * @param request the page servlet request - * @return the page resource path from the request - */ - public static String getResourcePath(HttpServletRequest request) { - // Adapted from VelocityViewServlet.handleRequest() method: - - // If we get here from RequestDispatcher.include(), getServletPath() - // will return the original (wrong) URI requested. The following - // special attribute holds the correct path. See section 8.3 of the - // Servlet 2.3 specification. - - String path = (String) - request.getAttribute("jakarta.servlet.include.servlet_path"); - - // Also take into account the PathInfo stated on - // SRV.4.4 Request Path Elements. - String info = (String) - request.getAttribute("jakarta.servlet.include.path_info"); - - if (path == null) { - path = request.getServletPath(); - info = request.getPathInfo(); - } - - if (info != null) { - path += info; - } - - return path; - } - - /** - * Return the requestURI from the request. For example: - *
-     * http://www.mycorp.com/banking/secure/login.htm  ->  /banking/secure/login.htm 
- * - * @param request the page servlet request - * @return the requestURI from the request - */ - public static String getRequestURI(HttpServletRequest request) { - // CLK-334. Adapted from VelocityViewServlet.handleRequest() method: - - // If we get here from RequestDispatcher.include(), getServletPath() - // will return the original (wrong) URI requested. The following - // special attribute holds the correct path. See section 8.3 of the - // Servlet 2.3 specification. - - String requestURI = (String) request.getAttribute("jakarta.servlet.include.request_uri"); - - if (requestURI == null) { - requestURI = request.getRequestURI(); - } - - if (requestURI != null && requestURI.endsWith(".jsp")) { - requestURI = StringUtils.replace(requestURI, ".jsp", ".htm"); - } - - return requestURI; - } - - /** - * Finds a resource with a given name. This method returns null if no - * resource with this name is found. - *

- * This method uses the current Thread context ClassLoader to find - * the resource. If the resource is not found the class loader of the given - * class is then used to find the resource. - * - * @param name the name of the resource - * @param aClass the class lookup the resource against, if the resource is - * not found using the current Thread context ClassLoader. - * @return the input stream of the resource if found or null otherwise - */ - public static InputStream getResourceAsStream(String name, Class aClass) { - Validate.notNull(name, "Parameter name is null"); - Validate.notNull(aClass, "Parameter aClass is null"); - - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - - InputStream inputStream = classLoader.getResourceAsStream(name); - if (inputStream == null) { - inputStream = aClass.getResourceAsStream(name); - } - - return inputStream; - } - - /** - * Finds a resource with a given name. This method returns null if no - * resource with this name is found. - *

- * This method uses the current Thread context ClassLoader to find - * the resource. If the resource is not found the class loader of the given - * class is then used to find the resource. - * - * @param name the name of the resource - * @param aClass the class lookup the resource against, if the resource is - * not found using the current Thread context ClassLoader. - * @return the URL of the resource if found or null otherwise - */ - public static URL getResource(String name, Class aClass) { - Validate.notNull(name, "Parameter name is null"); - Validate.notNull(aClass, "Parameter aClass is null"); - - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - - URL url = classLoader.getResource(name); - if (url == null) { - url = aClass.getResource(name); - } - - return url; - } - - /** - * Remove the control state from the session for the given stateful control, - * control name and request context. - * - * @param control the stateful control which state to remove - * @param controlName the name of the control which state to remove - * @param context the request context - */ - public static void removeState(Stateful control, String controlName, Context context) { - if (control == null) { - throw new IllegalStateException("Control cannot be null."); - } - if (controlName == null) { - throw new IllegalStateException(ClassUtils.getShortClassName(control.getClass()) - + " name has not been set. State cannot be removed until the name is set"); - } - if (context == null) { - throw new IllegalStateException("Context cannot be null."); - } - - String resourcePath = context.getResourcePath(); - Map pageMap = ClickUtils.getPageState(resourcePath, context); - if (pageMap != null) { - Object pop = pageMap.remove(controlName); - - if (pageMap.isEmpty()) { - // If this was the last state for the page, remove the page state map - context.removeSessionAttribute(resourcePath); - } else { - // Check if control state was emoved - if (pop != null) { - // If control state was removed, set session attribute to force - // session replication in a cluster - context.setSessionAttribute(resourcePath, pageMap); - } - } - } - } - - /** - * Restore the control state from the session for the given stateful control, - * control name and request context. - *

- * This method delegates to {@link org.apache.click.Stateful#setState(java.lang.Object)} - * to restore the control state. - * - * @param control the stateful control which state to restore - * @param controlName the name of the control which state to restore - * @param context the request context - */ - public static void restoreState(Stateful control, String controlName, Context context) { - if (control == null) { - throw new IllegalStateException("Control cannot be null."); - } - if (controlName == null) { - throw new IllegalStateException(ClassUtils.getShortClassName(control.getClass()) - + " name has not been set. State cannot be restored until the name is set"); - } - if (context == null) { - throw new IllegalStateException("Context cannot be null."); - } - - String resourcePath = context.getResourcePath(); - Map pageMap = ClickUtils.getPageState(resourcePath, context); - if (pageMap != null) { - control.setState(pageMap.get(controlName)); - } - } - - /** - * Save the control state in the session for the given stateful control, - * control name and request context. - *

- * * This method delegates to {@link org.apache.click.Stateful#getState()} - * to retrieve the control state to save. - * - * @param control the stateful control which state to save - * @param controlName the name of the control control which state to save - * @param context the request context - */ - public static void saveState(Stateful control, String controlName, Context context) { - if (control == null) { - throw new IllegalStateException("Control cannot be null."); - } - if (controlName == null) { - throw new IllegalStateException(ClassUtils.getShortClassName(control.getClass()) - + " name has not been set. State cannot be saved until the name is set"); - } - if (context == null) { - throw new IllegalStateException("Context cannot be null."); - } - - String resourcePath = context.getResourcePath(); - Map pageMap = getOrCreatePageState(resourcePath, context); - Object state = control.getState(); - if (state == null) { - // Set null state to see if it differs from previous state - Object pop = pageMap.put(controlName, state); - if (pop != null) { - // Previous state differs from current state, so set the - // session attribute to force session replication in a cluster - context.setSessionAttribute(resourcePath, pageMap); - } - } else { - pageMap.put(controlName, state); - // After control state has been added to the page state, set the - // session attribute to force session replication in a cluster - context.setSessionAttribute(resourcePath, pageMap); - } - } - - /** - * Return the getter method name for the given property name. - * - * @param property the property name - * @return the getter method name for the given property name. - */ - public static String toGetterName(String property) { - HtmlStringBuffer buffer = new HtmlStringBuffer(property.length() + 3); - - buffer.append("get"); - buffer.append(Character.toUpperCase(property.charAt(0))); - buffer.append(property.substring(1)); - - return buffer.toString(); - } - - /** - * Return the is getter method name for the given property name. - * - * @param property the property name - * @return the is getter method name for the given property name. - */ - public static String toIsGetterName(String property) { - HtmlStringBuffer buffer = new HtmlStringBuffer(property.length() + 3); - - buffer.append("is"); - buffer.append(Character.toUpperCase(property.charAt(0))); - buffer.append(property.substring(1)); - - return buffer.toString(); - } - - /** - * Return a field label string from the given field name. For example: - *

-     * faxNumber   ->   Fax Number 
- *

- * Note toLabel will return an empty String ("") if a null - * String name is specified. - * - * @param name the field name - * @return a field label string from the given field name - */ - public static String toLabel(String name) { - if (name == null) { - return ""; - } - - HtmlStringBuffer buffer = new HtmlStringBuffer(); - - for (int i = 0, size = name.length(); i < size; i++) { - char aChar = name.charAt(i); - - if (i == 0) { - buffer.append(Character.toUpperCase(aChar)); - - } else { - buffer.append(aChar); - - if (i < name.length() - 1) { - char nextChar = name.charAt(i + 1); - if (Character.isLowerCase(aChar) - && (Character.isUpperCase(nextChar) - || Character.isDigit(nextChar))) { - - // Add space before digits or uppercase letters - buffer.append(" "); - - } else if (Character.isDigit(aChar) - && (!Character.isDigit(nextChar))) { - - // Add space after digits - buffer.append(" "); - } - } - } - } - - return buffer.toString(); - } - - /** - * Return an 32 char MD5 encoded string from the given plain text. - * The returned value is MD5 hash compatible with Tomcat catalina Realm. - *

- * Adapted from org.apache.catalina.util.MD5Encoder - * - * @param plaintext the plain text value to encode - * @return encoded MD5 string - */ - public static String toMD5Hash(String plaintext) { - if (plaintext == null) { - throw new IllegalArgumentException("Null plaintext parameter"); - } - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - - md.update(plaintext.getBytes("UTF-8")); - - byte[] binaryData = md.digest(); - - char[] buffer = new char[32]; - - for (int i = 0; i < 16; i++) { - int low = (binaryData[i] & 0x0f); - int high = ((binaryData[i] & 0xf0) >> 4); - buffer[i * 2] = HEXADECIMAL[high]; - buffer[i * 2 + 1] = HEXADECIMAL[low]; - } - - return new String(buffer); - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Return a field name string from the given field label. - *

- * A label of " OK do it!" is returned as "okDoIt". Any &nbsp; - * characters will also be removed. - *

- * A label of "customerSelect" is returned as "customerSelect". - * - * @param label the field label or caption - * @return a field name string from the given field label - */ - public static String toName(String label) { - if (label == null) { - throw new IllegalArgumentException("Null label parameter"); - } - - boolean doneFirstLetter = false; - boolean lastCharBlank = false; - boolean hasWhiteSpace = (label.indexOf(' ') != -1); - - HtmlStringBuffer buffer = new HtmlStringBuffer(label.length()); - for (int i = 0, size = label.length(); i < size; i++) { - char aChar = label.charAt(i); - - if (aChar != ' ') { - if (Character.isJavaIdentifierPart(aChar)) { - if (lastCharBlank) { - if (doneFirstLetter) { - buffer.append(Character.toUpperCase(aChar)); - lastCharBlank = false; - } else { - buffer.append(Character.toLowerCase(aChar)); - lastCharBlank = false; - doneFirstLetter = true; - } - } else { - if (doneFirstLetter) { - if (hasWhiteSpace) { - buffer.append(Character.toLowerCase(aChar)); - } else { - buffer.append(aChar); - } - } else { - buffer.append(Character.toLowerCase(aChar)); - doneFirstLetter = true; - } - } - } - } else { - lastCharBlank = true; - } - } - - return buffer.toString(); - } - - /** - * Return the setter method name for the given property name. - * - * @param property the property name - * @return the setter method name for the given property name. - */ - public static String toSetterName(String property) { - HtmlStringBuffer buffer = new HtmlStringBuffer(property.length() + 3); - - buffer.append("set"); - buffer.append(Character.toUpperCase(property.charAt(0))); - buffer.append(property.substring(1)); - - return buffer.toString(); - } - - /** - * Returns true if Click resources (JavaScript, CSS, images etc) packaged - * in jars can be deployed to the root directory of the webapp, false - * otherwise. - *

- * This method will return false in restricted environments where write - * access to the underlying file system is disallowed. Examples where - * write access is not allowed include the WebLogic JEE server (this can be - * changed though) and Google App Engine. - * - * @param servletContext the application servlet context - * @return true if writes are allowed, false otherwise - */ - public static boolean isResourcesDeployable(ServletContext servletContext) { - try { - boolean canWrite = (servletContext.getRealPath("/") != null); - if (!canWrite) { - return false; - } - - // Since Google App Engine returns a value for getRealPath, check - // SecurityManager if writes are allowed - SecurityManager security = System.getSecurityManager(); - if (security != null) { - security.checkWrite("/click"); - } - return true; - } catch (Throwable e) { - return false; - } - } - - // -------------------------------------------------------- Package Methods - - /** - * Append the escaped string for the given character value to the - * buffer. The following characters are escaped: <, >, ", ', - * &. - * - * @param aChar the character value to escape - * @param buffer the string buffer to append the escaped value to - */ - static void appendEscapeChar(char aChar, HtmlStringBuffer buffer) { - int index = aChar; - - if (index < XML_ENTITIES.length && XML_ENTITIES[index] != null) { - buffer.append(XML_ENTITIES[index]); - - } else { - buffer.append(aChar); - } - } - - /** - * Append the escaped string for the given character value to the buffer. - * The following characters are escaped: <, >, ", ', &. - * - * @param value the String value to escape - * @param buffer the string buffer to append the escaped value to - */ - static void appendEscapeString(String value, HtmlStringBuffer buffer) { - char aChar; - for (int i = 0, size = value.length(); i < size; i++) { - aChar = value.charAt(i); - int index = aChar; - - if (index < XML_ENTITIES.length && XML_ENTITIES[index] != null) { - buffer.append(XML_ENTITIES[index]); - - } else { - buffer.append(aChar); - } - } - } - - /** - * Append the HTML escaped string for the given character value to the - * buffer. - * - * @param aChar the character value to escape - * @param buffer the string buffer to append the escaped value to - */ - static void appendHtmlEscapeChar(char aChar, HtmlStringBuffer buffer) { - int index = aChar; - - if (index < HTML_ENTITIES.length && HTML_ENTITIES[index] != null) { - buffer.append(HTML_ENTITIES[index]); - - } else { - buffer.append(aChar); - } - } - - /** - * Append the HTML escaped string for the given character value to the - * buffer. - * - * @param value the String value to escape - * @param buffer the string buffer to append the escaped value to - */ - static void appendHtmlEscapeString(String value, HtmlStringBuffer buffer) { - char aChar; - for (int i = 0, size = value.length(); i < size; i++) { - aChar = value.charAt(i); - int index = aChar; - - if (index < HTML_ENTITIES.length && HTML_ENTITIES[index] != null) { - buffer.append(HTML_ENTITIES[index]); - - } else { - buffer.append(aChar); - } - } - } - - /** - * Return true if the given character requires escaping. The following - * characters are escaped: <, >, ", ', &. - * - * @param aChar the character value to test - * @return true if the given character requires escaping - */ - static boolean requiresEscape(char aChar) { - int index = aChar; - - if (index < XML_ENTITIES.length) { - return XML_ENTITIES[index] != null; - - } else { - return false; - } - } - - /** - * Return true if the given string requires escaping of characters. - * The following characters require escaping: <, >, ", ', &. - * - * @param value the string value to test - * @return true if the given string requires escaping of characters - */ - static boolean requiresEscape(String value) { - if (value == null) { - return false; - } - - int length = value.length(); - for (int i = 0; i < length; i++) { - if (requiresEscape(value.charAt(i))) { - return true; - } - } - - return false; - } - - /** - * Return true if the given character requires HTML escaping. - * - * @param aChar the character value to test - * @return true if the given character requires HTML escaping - */ - static boolean requiresHtmlEscape(char aChar) { - int index = aChar; - - if (index < HTML_ENTITIES.length) { - return HTML_ENTITIES[index] != null; - - } else { - return false; - } - } - - /** - * Return true if the given string requires HTML escaping of characters. - * - * @param value the string value to test - * @return true if the given string requires HTML escaping of characters - */ - static boolean requiresHtmlEscape(String value) { - if (value == null) { - return false; - } - - int length = value.length(); - for (int i = 0; i < length; i++) { - if (requiresHtmlEscape(value.charAt(i))) { - return true; - } - } - - return false; - } - - // -------------------------------------------------------- Private Methods - - /** - * A helper method that binds the submitted request values of all Fields - * and Links inside the given container or child containers. - *

- * For Field controls, this method delegates to - * {@link #bindField(org.openidentityplatform.openam.click.control.Field, org.openidentityplatform.openam.click.Context)}. - * - * @param container the container which Fields and Links to bind - * @param context the request context - */ - private static void bind(Container container, Context context) { - for (int i = 0; i < container.getControls().size(); i++) { - Control control = container.getControls().get(i); - if (control instanceof Container) { - // Include fields but skip fieldSets - if (control instanceof Field) { - Field field = (Field) control; - bindField(field, context); - - } else if (control instanceof AbstractLink) { - ((AbstractLink) control).bindRequestValue(); - } - Container childContainer = (Container) control; - bind(childContainer, context); - - } else if (control instanceof Field) { - Field field = (Field) control; - bindField(field, context); - - } else if (control instanceof AbstractLink) { - ((AbstractLink) control).bindRequestValue(); - } - } - } - - /** - * A helper method that binds and validates the submitted request values - * of all Fields and Links inside the given container or child containers. - *

- * For Field controls, this method delegates to - * {@link #bindField(org.openidentityplatform.openam.click.control.Field, org.openidentityplatform.openam.click.Context)}. - * - * @param container the container which Fields and Links to bind and - * validate - * @param context the request context - * @return true if container fields and links was bound and valid, false - * otherwise - */ - private static boolean bindAndValidate(Container container, Context context) { - boolean valid = true; - for (int i = 0; i < container.getControls().size(); i++) { - Control control = container.getControls().get(i); - if (control instanceof Container) { - - // Bind and validate fields only - if (control instanceof Field) { - if (!bindAndValidate((Field) control, context)) { - valid = false; - } - - } else if (control instanceof AbstractLink) { - ((AbstractLink) control).bindRequestValue(); - } - - Container childContainer = (Container) control; - if (!bindAndValidate(childContainer, context)) { - valid = false; - } - - } else if (control instanceof Field) { - if (!bindAndValidate((Field) control, context)) { - valid = false; - } - - } else if (control instanceof AbstractLink) { - ((AbstractLink) control).bindRequestValue(); - } - } - return valid; - } - - /** - * A helper method that binds and validates the submitted request values - * of all Fields and Links inside the given Form or child containers. Note, - * the Form itself is also validated. - *

- * For Field controls, this method delegates to - * {@link #bindField(org.openidentityplatform.openam.click.control.Field, org.openidentityplatform.openam.click.Context)}. - * - * @param form the Form which Fields and Links to bind and validate - * @param context the request context - * @return true if the form, it's fields and links was bound and valid, false - * otherwise - */ - private static boolean bindAndValidate(Form form, Context context) { - if (!bindAndValidate((Container) form, context)) { - return false; - } - - if (form.getValidate()) { - // Keep reference to current error - String errorReference = form.getError(); - - // Validate form. If validation fails the form error will be changed - form.validate(); - - boolean valid = form.isValid(); - - // Revert back to original error - form.setError(errorReference); - - return valid; - } - return true; - } - - /** - * A helper method that binds and validates the Field's submitted request - * value. - *

- * This method delegates to - * {@link #bindField(org.openidentityplatform.openam.click.control.Field, org.openidentityplatform.openam.click.Context)} - * to bind the field value. - * - * @param field the Field to bind and validate - * @param context the request context - * @return true if field was bound and valid, or false otherwise - */ - private static boolean bindAndValidate(Field field, Context context) { - boolean continueProcessing = bindField(field, context); - if (!continueProcessing) { - return true; - } - - if (field.getValidate()) { - // Keep reference to current error - String errorReference = field.getError(); - - // Validate field. If validation fails the field error will be changed - field.validate(); - - boolean valid = field.isValid(); - - // Revert back to original error - field.setError(errorReference); - - return valid; - } - return true; - } - - /** - * Bind the field to its incoming request parameter, returning true if the - * field value was bound, false otherwise. - *

- * Please note: this method won't bind disabled fields, - * unless the field has an incoming request parameter matching its name. - * If an incoming request parameter is present, this method will switch off - * the Field's disabled property. - * - * @param field the field which value to bind to its request parameter - * @param context the request context - * @return true if the field was bound to its request parameter, false - * otherwise - */ - private static boolean bindField(Field field, Context context) { - if (field.isDisabled()) { - // Switch off disabled property if control has incoming request - // parameter. Normally this means the field was enabled via JS - if (context.hasRequestParameter(field.getName())) { - field.setDisabled(false); - } else { - return false; - } - } - field.bindRequestValue(); - return true; - } - - /** - * Retrieve or create the map where page state is stored in. - * - * @see #getPageState(java.lang.String, org.openidentityplatform.openam.click.Context) - * - * @param pagePath the path under which the page state is stored in the - * session - * @param context the request context - * @return the map where page state is stored in - */ - private static Map getOrCreatePageState(String pagePath, Context context) { - Map pageMap = getPageState(pagePath, context); - if (pageMap == null) { - pageMap = new HashMap(); - } - return pageMap; - } - - /** - * Retrieve the map for the given pagePath from the session where page state - * is stored in. - * - * @param pagePath the path under which the page state is stored in the - * session - * @param context the request context - * @return the map where page state is stored in - */ - private static Map getPageState(String pagePath, Context context) { - Object storedPageValue = context.getSessionAttribute(pagePath); - Map pageMap = null; - if (storedPageValue != null) { - pageMap = (Map) storedPageValue; - } - return pageMap; - } - - /** - * Invoke the named method on the given target object and return the result. - * - * @param target the target object with the method to invoke - * @param method the name of the method to invoke - * @return Object the target method result - */ - private static Object invokeMethod(Object target, String method) { - if (target == null) { - throw new IllegalArgumentException("Null target parameter"); - } - if (method == null) { - throw new IllegalArgumentException("Null method parameter"); - } - - Method targetMethod = null; - boolean isAccessible = true; - try { - Class targetClass = target.getClass(); - targetMethod = targetClass.getMethod(method); - - // Change accessible for anonymous inner classes public methods - // only. Conditional checks: - // #1 - Target method is not accessible - // #2 - Anonymous inner classes are not public - // #3 - Only modify public methods - // #4 - Anonymous inner classes have no declaring class - // #5 - Anonymous inner classes have $ in name - if (!targetMethod.isAccessible() - && !Modifier.isPublic(targetClass.getModifiers()) - && Modifier.isPublic(targetMethod.getModifiers()) - && targetClass.getDeclaringClass() == null - && targetClass.getName().indexOf('$') != -1) { - - isAccessible = false; - targetMethod.setAccessible(true); - } - - return targetMethod.invoke(target); - - } catch (InvocationTargetException ite) { - - Throwable e = ite.getTargetException(); - if (e instanceof RuntimeException) { - throw (RuntimeException) e; - - } else if (e instanceof Exception) { - String msg = - "Exception occurred invoking public method: " + targetMethod; - - throw new RuntimeException(msg, e); - - } else if (e instanceof Error) { - String msg = - "Error occurred invoking public method: " + targetMethod; - - throw new RuntimeException(msg, e); - - } else { - String msg = - "Error occurred invoking public method: " + targetMethod; - - throw new RuntimeException(msg, e); - } - - } catch (Exception e) { - String msg = - "Exception occurred invoking public method: " + targetMethod; - - throw new RuntimeException(msg, e); - - } finally { - if (targetMethod != null && !isAccessible) { - targetMethod.setAccessible(false); - } - } - } -} diff --git a/openam-core/src/main/java/org/openidentityplatform/openam/click/util/ContainerUtils.java b/openam-core/src/main/java/org/openidentityplatform/openam/click/util/ContainerUtils.java deleted file mode 100644 index d55186dc43..0000000000 --- a/openam-core/src/main/java/org/openidentityplatform/openam/click/util/ContainerUtils.java +++ /dev/null @@ -1,1431 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 org.openidentityplatform.openam.click.util; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -import ognl.DefaultTypeConverter; -import ognl.Ognl; -import ognl.OgnlOps; - -import org.openidentityplatform.openam.click.Control; -import org.openidentityplatform.openam.click.Page; -import org.apache.click.control.Button; -import org.openidentityplatform.openam.click.control.Container; -import org.openidentityplatform.openam.click.control.Field; -import org.apache.click.control.FieldSet; -import org.openidentityplatform.openam.click.control.Form; -import org.apache.click.control.Label; -import org.apache.click.service.LogService; -import org.apache.click.util.ClickUtils; -import org.apache.click.util.HtmlStringBuffer; -import org.apache.click.util.PropertyUtils; -import org.apache.commons.lang.ClassUtils; - -/** - * Provides Container access and copy utilities. - */ -public class ContainerUtils { - - /** - * Populate the given object attributes from the Containers field values. - *

- * If a Field and object attribute matches, the object attribute is set to - * the Object returned from the method - * {@link org.apache.click.control.Field#getValueObject()}. If an object - * attribute is a primitive, the Object returned from - * {@link org.apache.click.control.Field#getValueObject()} will be converted - * into the specific primitive e.g. Integer will become int and Boolean will - * become boolean. - *

- * The fieldList specifies which fields to copy to the object. This allows - * one to include or exclude certain Container fields before populating the - * object. - *

- * The following example shows how to exclude disabled fields from - * populating a customer object: - *

-     * public void onInit() {
-     *     List formFields = new ArrayList();
-     *     for(Iterator it = form.getFieldList().iterator(); it.hasNext(); ) {
-     *         Field field = (Field) formFields.next();
-     *         // Exclude disabled fields
-     *         if (!field.isDisabled()) {
-     *             formFields.add(field);
-     *         }
-     *     }
-     *     Customer customer = new Customer();
-     *     ContainerUtils.copyContainerToObject(form, customer, formFields);
-     * }
-     * 
- * - * The specified Object can either be a POJO (plain old java object) or - * a {@link java.util.Map}. If a POJO is specified, its attributes are - * populated from matching container fields. If a map is specified, its - * key/value pairs are populated from matching container fields. - * - * @param container the fieldList Container - * @param object the object to populate with field values - * @param fieldList the list of fields to obtain values from - * - * @throws IllegalArgumentException if container, object or fieldList is - * null - */ - public static void copyContainerToObject(Container container, - Object object, List fieldList) { - - if (container == null) { - throw new IllegalArgumentException("Null container parameter"); - } - - if (object == null) { - throw new IllegalArgumentException("Null object parameter"); - } - - if (fieldList == null) { - throw new IllegalArgumentException("Null fieldList parameter"); - } - - if (fieldList.isEmpty()) { - LogService logService = org.apache.click.util.ClickUtils.getLogService(); - if (logService.isDebugEnabled()) { - String containerClassName = - ClassUtils.getShortClassName(container.getClass()); - logService.debug(" " + containerClassName - + " has no fields to copy from"); - } - //Exit early. - return; - } - - String objectClassname = object.getClass().getName(); - objectClassname = - objectClassname.substring(objectClassname.lastIndexOf(".") + 1); - - // If the given object is a map, its key/value pair is populated from - // the fields name/value pair. - if (object instanceof Map) { - copyFieldsToMap(fieldList, (Map) object); - // Exit after populating the map. - return; - } - - LogService logService = org.apache.click.util.ClickUtils.getLogService(); - - Set properties = getObjectPropertyNames(object); - Map ognlContext = Ognl.createDefaultContext( - object, null, new ContainerUtils.FixBigDecimalTypeConverter(), null); - - for (Field field : fieldList) { - - // Ignore disabled field as their values are not submitted in HTML - // forms - if (field.isDisabled()) { - continue; - } - - if (!hasMatchingProperty(field, properties)) { - continue; - } - - String fieldName = field.getName(); - - ensureObjectPathNotNull(object, fieldName); - - try { - PropertyUtils.setValueOgnl(object, fieldName, field.getValueObject(), ognlContext); - - if (logService.isDebugEnabled()) { - String containerClassName = - ClassUtils.getShortClassName(container.getClass()); - String msg = " " + containerClassName + " -> " - + objectClassname + "." + fieldName + " : " - + field.getValueObject(); - - logService.debug(msg); - } - - } catch (Exception e) { - String msg = - "Error incurred invoking " + objectClassname + "." - + fieldName + " with " + field.getValueObject() - + " error: " + e.toString(); - - logService.debug(msg); - } - } - } - - /** - * Populate the given object attributes from the Containers field values. - * - * @see #copyContainerToObject(org.openidentityplatform.openam.click.control.Container, java.lang.Object, java.util.List) - * - * @param container the Container to obtain field values from - * @param object the object to populate with field values - */ - public static void copyContainerToObject(Container container, - Object object) { - List fieldList = getInputFields(container); - copyContainerToObject(container, object, fieldList); - } - - /** - * Populate the given Container field values from the object attributes. - *

- * If a Field and object attribute matches, the Field value is set to the - * object attribute using the method - * {@link org.apache.click.control.Field#setValueObject(java.lang.Object)}. If - * an object attribute is a primitive it is first converted to its proper - * wrapper class e.g. int will become Integer and boolean will become - * Boolean. - *

- * The fieldList specifies which fields to populate from the object. This - * allows one to exclude or include specific fields. - *

- * The specified Object can either be a POJO (plain old java object) or - * a {@link java.util.Map}. If a POJO is specified, its attributes are - * copied to matching container fields. If a map is specified, its key/value - * pairs are copied to matching container fields. - * - * @param object the object to obtain attribute values from - * @param container the Container to populate - * @param fieldList the list of fields to populate from the object - * attributes - */ - public static void copyObjectToContainer(Object object, - Container container, List fieldList) { - if (object == null) { - throw new IllegalArgumentException("Null object parameter"); - } - - if (container == null) { - throw new IllegalArgumentException("Null container parameter"); - } - - if (container == null) { - throw new IllegalArgumentException("Null fieldList parameter"); - } - - if (fieldList.isEmpty()) { - LogService logService = org.apache.click.util.ClickUtils.getLogService(); - if (logService.isDebugEnabled()) { - String containerClassName = - ClassUtils.getShortClassName(container.getClass()); - logService.debug(" " + containerClassName - + " has no fields to copy to"); - } - //Exit early. - return; - } - - String objectClassname = object.getClass().getName(); - objectClassname = - objectClassname.substring(objectClassname.lastIndexOf(".") + 1); - - //If the given object is a map, populate the fields name/value from - //the maps key/value pair. - if (object instanceof Map) { - - copyMapToFields((Map) object, fieldList); - //Exit after populating the fields. - return; - } - - Set properties = getObjectPropertyNames(object); - - LogService logService = org.apache.click.util.ClickUtils.getLogService(); - - for (Field field : fieldList) { - - if (!hasMatchingProperty(field, properties)) { - continue; - } - - String fieldName = field.getName(); - try { - Object result = PropertyUtils.getValue(object, fieldName); - - field.setValueObject(result); - - if (logService.isDebugEnabled()) { - String containerClassName = - ClassUtils.getShortClassName(container.getClass()); - String msg = " " + containerClassName + " <- " - + objectClassname + "." + fieldName + " : " - + result; - logService.debug(msg); - } - - } catch (Exception e) { - String msg = "Error incurred invoking " + objectClassname + "." - + fieldName + " error: " + e.toString(); - - logService.debug(msg); - } - } - } - - /** - * Populate the given Container field values from the object attributes. - * - * @see #copyObjectToContainer(java.lang.Object, org.openidentityplatform.openam.click.control.Container, java.util.List) - * - * @param object the object to obtain attribute values from - * @param container the Container to populate - */ - public static void copyObjectToContainer(Object object, - Container container) { - - List fieldList = getInputFields(container); - copyObjectToContainer(object, container, fieldList); - } - - /** - * Find and return the first control with a matching name in the specified - * container. - *

- * If no matching control is found in the specified container, child - * containers will be recursively scanned for a match. - * - * @param container the container that is searched for a control with a - * matching name - * @param name the name of the control to find - * @return the control which name matched the given name - */ - public static Control findControlByName(Container container, String name) { - Control control = container.getControl(name); - - if (control != null) { - return control; - - } else { - for (Control childControl : container.getControls()) { - - if (childControl instanceof Container) { - Container childContainer = (Container) childControl; - Control found = findControlByName(childContainer, name); - if (found != null) { - return found; - } - } - } - } - return null; - } - - /** - * Find and return the specified controls parent Form or null - * if no Form is present. - * - * @param control the control to check for Form - * @return the controls parent Form or null if no parent is a Form - */ - public static Form findForm(Control control) { - while (control.getParent() != null && !(control.getParent() instanceof Page)) { - control = (Control) control.getParent(); - if (control instanceof Form) { - return (Form) control; - } - } - return null; - } - - /** - * Return the list of Buttons for the given Container, recursively including - * any Fields contained in child containers. - * - * @param container the container to obtain the buttons from - * @return the list of contained buttons - */ - public static List