Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions api/src/org/labkey/api/data/SQLFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,22 @@

import static org.labkey.api.query.ExprColumn.STR_TABLE_ALIAS;

/**
* Holds both the SQL text and JDBC parameter values to use during invocation.
*/
/// A composable SQL builder that pairs SQL text with its JDBC parameter values, ensuring
/// they travel together through query construction. Implements [Appendable] and
/// [CharSequence] for fluent assembly of SQL statements.
///
/// Provides type-safe `appendValue()` methods for inlining literals of common
/// types (integers, strings, dates, GUIDs, etc.) and `add()` methods for binding
/// JDBC `?` parameters. Fragments can be composed via `append(SQLFragment)` to
/// merge both SQL text and parameter lists.
///
/// Supports Common Table Expressions (CTEs) through
/// [#addCommonTableExpression(SqlDialect, Object, String, SQLFragment)], which
/// manages deduplication, token substitution, and correct ordering of WITH clauses
/// across nested and combined fragments.
///
/// Enforces basic SQL injection safeguards by rejecting unmatched quotes and
/// semicolons in appended text.
public class SQLFragment implements Appendable, CharSequence
{
public static final String FEATUREFLAG_DISABLE_STRICT_CHECKS = "sqlfragment-disable-strict-checks";
Expand Down
22 changes: 14 additions & 8 deletions api/src/org/labkey/api/dataiterator/DataIterator.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,20 @@
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
* User: matthewb
* Date: May 16, 2011
*
* Sticking with the jdbc style 1-based indexing
*
* Column 0 is the row number, used for error reporting
*/
/// A cursor-style interface for iterating over tabular row data, analogous to
/// [java.sql.ResultSet] but designed for LabKey's ETL and import pipelines.
/// Extends [DataIteratorBuilder] (so it can return itself) and [Closeable].
///
/// Columns use JDBC-style **1-based indexing**. Column 0 is reserved for the
/// row number, used for error reporting. Call `next()` to advance the cursor,
/// then `getSupplier(int)` to obtain a `Supplier` for retrieving column values.
/// Prefer `getSupplier(int)` over `get(int)`. Values may be `null`,
/// a real value, or an `MvFieldWrapper` for missing-value indicators.
///
/// Implementations are typically composed into a processing pipeline where each
/// `DataIterator` wraps another, adding transformations such as coercion,
/// validation, deduplication, or auditing (see [WrapperDataIterator],
/// [CoerceDataIterator], [DetailedAuditLogDataIterator], etc.).
public interface DataIterator extends DataIteratorBuilder, Closeable
{
String ROWNUMBER_COLUMNNAME = "_rowNumber"; // TODO change to something like DataIterator.class().getName() + "#_rowNumber"
Expand Down
31 changes: 28 additions & 3 deletions api/src/org/labkey/api/jsp/JspTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,34 @@

import org.labkey.api.util.TestContext;

/**
* Base class for junit tests implemented in a JSP
*/
/// Base class for junit tests implemented in a JSP.
///
/// Example minimal JSP test (MyTestCase.jsp):
///
/// ```jsp
/// <%@ page import="org.junit.After" %>
/// <%@ page import="org.junit.Before" %>
/// <%@ page import="org.junit.Test" %>
/// <%@ page import="static org.junit.Assert.assertTrue" %>
/// <%@ page extends="org.labkey.api.jsp.JspTest.BVT" %>
/// <%!
/// @Before
/// public void setUp()
/// {
/// }
///
/// @After
/// public void tearDown()
/// {
/// }
///
/// @Test
/// public void testExample()
/// {
/// assertTrue(true);
/// }
/// %>
/// ```
public abstract class JspTest extends JspContext
{
protected final TestContext testContext;
Expand Down
80 changes: 74 additions & 6 deletions api/src/org/labkey/api/util/DOM.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,80 @@
import static org.labkey.api.util.HtmlString.unsafe;
import static org.labkey.api.util.PageFlowUtil.filter;

/**
* Builders to safely create properly encoded HTML.
* Many of the element classes take a var-arg Object array to represent their children,
* see {@link DOM#appendBody(Appendable, Object)} for details about what's supported, and {@link DomTestCase} for
* example usages.
*/
/// A Java DSL for safely building properly encoded HTML. All text content is automatically
/// HTML-encoded via [PageFlowUtil#filter], preventing XSS vulnerabilities. Uppercase static
/// factory methods (`DIV()`, `TABLE()`, `SPAN()`, etc.) return [Renderable] objects that nest
/// to compose an HTML tree.
///
/// ## Recommended imports
///
/// ```java
/// import static org.labkey.api.util.DOM.*;
/// import static org.labkey.api.util.DOM.Attribute.*;
/// ```
///
/// ## Elements, attributes, and classes
///
/// Elements accept an optional [Attributes] argument followed by var-arg children.
/// Use [#at(Attribute, Object, Object...)] for attributes, [#cl(String...)] for CSS classes,
/// and chain them together via the [_Attributes] builder:
///
/// ```java
/// DIV(cl("container"),
/// H1(id("title"), "Hello World"),
/// A(at(href, "https://example.com").cl("nav-link").data("section", "main"), "Click here"),
/// P(at(style, "color:red"), "Styled text"),
/// TR(cl(isOdd, "labkey-alternate-row", "labkey-row"), TD("cell"))
/// )
/// ```
///
/// ## Dynamic content
///
/// Children can be arrays, [Iterable]s, or [Stream]s of supported types:
///
/// ```java
/// // Stream of options in a select
/// SELECT(id("users"), Stream.of("alice", "bob", "charles").map(DOM::OPTION))
///
/// // Building rows in a loop
/// List<Renderable> rows = new ArrayList<>();
/// for (var item : items)
/// rows.add(TR(TD(item.getName()), TD(String.valueOf(item.getCount()))));
/// TABLE(cl("labkey-data-region"), THEAD(TR(TH("Name"), TH("Count"))), TBODY(rows))
/// ```
///
/// ## LabKey extensions ([LK])
///
/// ```java
/// LK.FORM(at(method, "POST", action, url), INPUT(at(type, "text", name, "q")), INPUT(at(type, "submit")))
/// LK.CHECKBOX(at(name, "enabled")) // checkbox + hidden field marker
/// LK.FA("plus-square") // font-awesome icon
/// LK.ERRORS(bindingResult) // render Spring validation errors
/// ```
///
/// ## Rendering to output
///
/// ```java
/// HtmlString html = DOM.createHtmlFragment(DIV("hello"), BR(), DIV("world")); // to HtmlString
/// myRenderable.appendTo(out); // to Appendable (JspWriter, StringBuilder)
/// String raw = DIV("test").renderToString(); // to String
/// ```
///
/// ## Template support
///
/// Use [#renderTemplate(Renderable, Appendable)] with [#BODY_PLACE_HOLDER] to split rendering
/// around a body that is not yet available (e.g. for JSP `BodyTagSupport` or `WebPartFrame`):
///
/// ```java
/// Renderable frame = DIV(cl("frame"), DIV(cl("header"), "Title"), BODY_PLACE_HOLDER, DIV(cl("footer"), "Footer"));
/// HtmlString endMarkup = DOM.renderTemplate(frame, out);
/// // ... render body content to out ...
/// out.write(endMarkup.toString());
/// ```
///
/// Supported child types: `null` (ignored), [CharSequence] (HTML-encoded), [Number]/[Boolean]
/// (rendered as-is), [Renderable], [HtmlString] (no encoding), arrays, [Iterable]s, and [Stream]s
/// of these types. See [#appendBody(Appendable, Object)] for details and [DomTestCase] for more examples.
public class DOM
{
public interface Attributes extends Iterable<Map.Entry<Object,Object>> {}
Expand Down
15 changes: 15 additions & 0 deletions api/src/org/labkey/api/util/HtmlString.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@
import java.io.Serializable;
import java.util.Objects;

/// An immutable wrapper around a [String] known to contain safe, properly encoded HTML. Instances are
/// created via the [#of] factory methods, which HTML-encode their input, or via [#unsafe],
/// which trusts the caller to supply pre-encoded markup.
///
/// `HtmlString` is the fundamental unit of safe HTML in the LabKey API. It implements
/// [DOM.Renderable], so it can be used as a child element in the [DOM] HTML-building DSL
/// (e.g., `DIV(myHtmlString)`). When you need to build up HTML incrementally through concatenation,
/// use [HtmlStringBuilder], which acts as a mutable builder and produces an `HtmlString` via
/// [HtmlStringBuilder#getHtmlString()]. For constructing structured HTML trees with elements and
/// attributes, prefer the [DOM] DSL instead.
///
/// @see HtmlStringBuilder
/// @see DOM
/// @see DOM.Renderable
/// @see SafeToRender
public final class HtmlString implements SafeToRender, DOM.Renderable, Comparable<HtmlString>, Serializable, JSONString
{
// Helpful constants for convenience (and efficiency)
Expand Down
8 changes: 8 additions & 0 deletions api/src/org/labkey/api/util/HtmlStringBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@

import java.util.regex.Pattern;

/// A mutable builder for constructing an [HtmlString] incrementally. Plain `String` arguments
/// passed to [#append(String)] are automatically HTML-encoded; pre-encoded [HtmlString] values
/// are appended as-is. Call [#getHtmlString()] to obtain the final immutable [HtmlString].
///
/// For structured HTML with elements and attributes, prefer the [DOM] DSL instead.
///
/// @see HtmlString
/// @see DOM
public class HtmlStringBuilder implements HasHtmlString, SafeToRender
{
private final StringBuilder _sb = new StringBuilder();
Expand Down
30 changes: 26 additions & 4 deletions api/src/org/labkey/api/view/ActionURL.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,32 @@

import static org.hamcrest.CoreMatchers.containsString;

/**
* Encapsulates URL generation and parsing based on controller/container/action conventions.
* This class has to be kept in sync with ViewServlet.
*/
/// Represents a URL that follows LabKey's container/action routing conventions.
/// Extends [URLHelper] with structured access to the controller name, action name, and
/// container path, generating URLs in the format: `/contextPath/containerPath/controller-action.view`
///
/// Scheme, host, and port are lazily initialized from [AppProps] and only needed when
/// generating absolute URLs.
///
/// ### Examples
///
/// ```java
/// // From an action class (preferred)
/// ActionURL url = new ActionURL(MyAction.class, container);
///
/// // From controller/action strings
/// ActionURL url = new ActionURL("core", "login", container);
///
/// // Parsed from a URL string
/// ActionURL url = new ActionURL("/containerPath/core-login.view?returnUrl=...");
///
/// // Adding parameters (fluent API)
/// url.addParameter("key", "value")
/// .addReturnUrl(returnUrl);
/// ```
///
/// @see URLHelper
/// @see ViewServlet
public class ActionURL extends URLHelper implements Cloneable
{
private static boolean useContainerRelativeURL()
Expand Down
19 changes: 18 additions & 1 deletion api/src/org/labkey/api/view/HtmlView.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,24 @@
import org.labkey.api.util.HtmlStringBuilder;
import org.labkey.api.writer.HtmlWriter;

/** Renders a fixed set of HTML at the content of the view */
/// A view that renders a fixed block of HTML content. Accepts either an [HtmlString] or a [DOM.Renderable].
///
/// ```java
/// // From safe, pre-encoded HTML
/// HtmlView view = new HtmlView(HtmlString.of("Hello, world!"));
///
/// // With a title (automatically uses PORTAL frame)
/// HtmlView view = new HtmlView("My Section", HtmlString.of("Some content"));
///
/// // From a DOM renderable
/// HtmlView view = new HtmlView(DOM.DIV("Hello"));
///
/// // Plain text (auto-escaped)
/// HtmlView view = HtmlView.of("User-supplied text");
///
/// // Error message styled with labkey-error
/// HtmlView view = HtmlView.err("Something went wrong");
/// ```
public class HtmlView extends WebPartView<Object>
{
private String _contentType = null;
Expand Down
53 changes: 48 additions & 5 deletions api/src/org/labkey/api/view/HttpView.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,54 @@
import java.util.function.Supplier;


/**
* BEWARE: Our shorts are showing a bit here. Our primary HTML components (aka views) are not spring Views.
* Architecturally, they act like ModelAndView. We may want to fix this in the future, but we're moving forward with
* this conceptual co-mingling for now.
*/
/// Base class for LabKey's server-side HTML rendering components (views).
///
/// `HttpView` serves a dual role: it is both a Spring [View] (capable of rendering a response)
/// and a [ModelAndView][org.springframework.web.servlet.ModelAndView] (carrying its own model bean
/// of type `ModelBean`). This dual nature means an `HttpView` can be rendered directly by Spring's
/// `DispatcherServlet` and can also be composed inside other views via [#include(ModelAndView)].
///
/// ## Subclassing
///
/// Subclasses typically override one of the `renderInternal` methods:
///
/// - [#renderInternal(Object, PrintWriter)] — for simple HTML output via a `PrintWriter`.
/// - [#renderInternal(Object, HttpServletRequest, HttpServletResponse)] — when full access to the
/// servlet request/response is needed (e.g., [JspView]).
///
/// ## View stack and thread-local context
///
/// During rendering, `HttpView` maintains a thread-local stack of active views
/// ([ViewStackEntry] records). This allows any code running on the render thread to access
/// the current request, response, [ViewContext], and [PageConfig] via static helpers:
///
/// - [#currentRequest()] / [#currentResponse()]
/// - [#currentContext()] — the [ViewContext] of the innermost `HttpView` on the stack.
/// - [#currentPageConfig()] — the active page configuration.
/// - [#currentView()] / [#currentModel()]
///
/// The stack is pushed automatically in [#render] and can be initialized for non-view
/// contexts (e.g., filters, servlets) via [#initForRequest].
///
/// ## Composition
///
/// Views can be nested using named child views stored in [#_views]:
///
/// - [#setBody] / [#getBody] — convenience accessors for the well-known `BODY` slot.
/// - [#setView(String, ModelAndView)] / [#getView(String)] — arbitrary named slots.
/// - [#include(ModelAndView)] — renders a child view inline within the current response.
///
/// ## Client dependencies
///
/// Each view can declare CSS/JS dependencies via [#addClientDependency]. These are
/// aggregated recursively over the view tree by [#getClientDependencies()] so that page
/// templates can emit the full set of required resources.
///
/// @param <ModelBean> the type of the model object this view renders
/// @see WebPartView
/// @see JspView
/// @see HtmlView
/// @see org.labkey.api.query.QueryView
public abstract class HttpView<ModelBean> extends DefaultModelAndView<ModelBean> implements View, HasViewContext
{
private static final int _debug = Debug.getLevel(HttpView.class);
Expand Down
8 changes: 8 additions & 0 deletions api/src/org/labkey/api/view/JspView.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@
import java.util.Map;


/// A view that renders a JSP page, optionally with a typed model object and validation errors.
///
/// ```java
/// // Render a JSP with a model bean
/// JspView<MyForm> view = new JspView<>("/org/labkey/mymodule/view/myPage.jsp", form);
/// ```
///
/// @param <ModelClass> the type of the model object accessible from the JSP via `getModelBean()`
public class JspView<ModelClass> extends WebPartView<ModelClass>
{
protected String _path;
Expand Down
Loading