Skip to content
Draft
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
14 changes: 9 additions & 5 deletions core/src/main/java/org/kohsuke/stapler/AncestorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,17 @@ class AncestorImpl implements Ancestor {
private final boolean endsWithSlash;

AncestorImpl(RequestImpl req, Object object) {
this.owner = req.ancestors;
this(req.ancestors, object, req.tokens, req.getContextPath());
}

AncestorImpl(List<AncestorImpl> owner, Object object, TokenList tokens, String contextPath) {
this.owner = owner;
listIndex = owner.size();
this.object = object;
this.tokens = req.tokens.rawTokens;
this.index = req.tokens.idx;
this.endsWithSlash = req.tokens.endsWithSlash;
this.contextPath = req.getContextPath();
this.tokens = tokens.rawTokens;
this.index = tokens.idx;
this.endsWithSlash = tokens.endsWithSlash;
this.contextPath = contextPath;
}

void addToOwner() {
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/org/kohsuke/stapler/Dispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

package org.kohsuke.stapler;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.servlet.ServletException;
import java.io.IOException;
Expand Down Expand Up @@ -53,6 +54,17 @@ public abstract class Dispatcher {
public abstract boolean dispatch(RequestImpl req, ResponseImpl rsp, Object node)
throws IOException, ServletException, IllegalAccessException, InvocationTargetException;

/**
* If this is a purely navigational dispatcher, indicates the next ancestor object.
* @param node starting point
* @param tokens the path to parse through
* @return the next node, if any
*/
@CheckForNull
public Object next(Object node, TokenList tokens) {
return null;
}

/**
* Diagnostic string that explains this dispatch rule.
*/
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/java/org/kohsuke/stapler/MetaClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,19 @@ public boolean doDispatch(RequestImpl req, ResponseImpl rsp, Object node)
}
}

@Override
protected Object doNext(Object node, TokenList tokens) {
if (isAccepted) {
String token = tokens.next();
return ff.invoke(req, rsp, node, token); // TODO will need a new method in Function too
} else {
String token = tokens.next();
tokens.prev();
return null;
// TODO is FilteredGetterTriggerListener relevant in this context?
}
}

@Override
public String toString() {
if (isAccepted) {
Expand Down
22 changes: 22 additions & 0 deletions core/src/main/java/org/kohsuke/stapler/NameBasedDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

package org.kohsuke.stapler;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import jakarta.servlet.ServletException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
Expand Down Expand Up @@ -67,4 +68,25 @@ public final boolean dispatch(RequestImpl req, ResponseImpl rsp, Object node)

protected abstract boolean doDispatch(RequestImpl req, ResponseImpl rsp, Object node)
throws IOException, ServletException, IllegalAccessException, InvocationTargetException;

@Override
public final Object next(Object node, TokenList tokens) {
if (!tokens.hasMore() || !tokens.peek().equals(name)) {
return null;
}
if (tokens.countRemainingTokens() <= argCount) {
return null;
}
tokens.next();
var n = doNext(node, tokens);
if (n == null) {
tokens.prev();
}
return n;
}

@CheckForNull
protected Object doNext(Object node, TokenList tokens) {
return null;
}
}
16 changes: 16 additions & 0 deletions core/src/main/java/org/kohsuke/stapler/Stapler.java
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,22 @@ protected void service(HttpServletRequest req, HttpServletResponse rsp) throws S
}
}

/**
* Calculates what ancestors in path would have been used for a given URL.
* Like actual request processing, this will call methods like {@code getXXX}
* expecting them to have no side effects.
* Will not load views, run {@code doXXX} methods, or otherwise invoke terminal operations.
* @param servletPath like {@link StaplerRequest2#getServletPath}
* @return like {@link StaplerRequest2#getAncestors}
*/
public List<Ancestor> parseAncestors(String servletPath) {
var tokens = new TokenList(servletPath);
var ancestors = new ArrayList<AncestorImpl>();
// TODO follow same logic as tryInvoke incl. addToOwner, StaplerProxy, StaplerOverridable, StaplerFallback,
// but call Dispatcher.next after finding MetaClass
return Collections.unmodifiableList(ancestors);
}

/**
* Tomcat and GlassFish returns a fresh {@link InputStream} every time
* {@link URLConnection#getInputStream()} is invoked in their {@code org.apache.naming.resources.DirContextURLConnection}.
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/java/org/kohsuke/stapler/StaplerRequest2.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,21 @@ public interface StaplerRequest2 extends HttpServletRequest {
*/
Ancestor findAncestor(Object o);

/**
* Finds ancestor objects which can be inferred from the referer header, if any.
* This is useful when the current request is triggered by JavaScript from a page
* and needs to look up certain types of objects in that page.
*/
default List<Ancestor> getAncestorsFromReferer() {
var referer = getReferer();
var rootPath = getRootPath();
if (referer != null && referer.startsWith(rootPath)) {
return getStapler().parseAncestors(referer.substring(rootPath.length()));
} else {
return List.of();
}
}

/**
* Short for {@code getParameter(name)!=null}
*/
Expand Down