Commit 3763ee73 authored by Florian's avatar Florian Committed by arnaudroques
Browse files

Improve multipage (index) handling

parent db313154
......@@ -34,6 +34,7 @@ import jakarta.servlet.http.HttpServletResponse;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.servlet.utility.UmlExtractor;
import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor;
/**
* Check servlet of the webapp.
......@@ -46,7 +47,8 @@ public class CheckSyntaxServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// build the UML source from the compressed request parameter
String uml = UmlExtractor.getUmlSource(getSource(request.getRequestURI()));
final String url = request.getRequestURI();
final String uml = UmlExtractor.getUmlSource(UrlDataExtractor.getEncodedDiagram(url, ""));
// generate the response
DiagramResponse dr = new DiagramResponse(response, getOutputFormat(), request);
......@@ -55,23 +57,6 @@ public class CheckSyntaxServlet extends HttpServlet {
} catch (IIOException e) {
// Browser has closed the connection, do nothing
}
dr = null;
}
/**
* Extract UML source from URI.
*
* @param uri the complete URI as returned by `request.getRequestURI()`
*
* @return the encoded UML text
*/
public String getSource(String uri) {
String[] result = uri.split("/check/", 2);
if (result.length != 2) {
return "";
} else {
return result[1];
}
}
/**
......
......@@ -166,10 +166,14 @@ public class DiagramResponse {
* Produce and send the image map of the uml diagram in HTML format.
*
* @param uml textual UML diagram source
* @param idx diagram index of {@code uml} to send
*
* @throws IOException if an input or output exception occurred
*/
public void sendMap(String uml) throws IOException {
public void sendMap(String uml, int idx) throws IOException {
if (idx < 0) {
idx = 0;
}
response.setContentType(getContentType());
SourceStringReader reader = new SourceStringReader(uml);
final BlockUml blockUml = reader.getBlocks().get(0);
......@@ -177,7 +181,7 @@ public class DiagramResponse {
addHeaderForCache(blockUml);
}
final Diagram diagram = blockUml.getDiagram();
ImageData map = diagram.exportDiagram(new NullOutputStream(), 0,
ImageData map = diagram.exportDiagram(new NullOutputStream(), idx,
new FileFormatOption(FileFormat.PNG, false));
if (map.containsCMapData()) {
PrintWriter httpOut = response.getWriter();
......@@ -237,7 +241,7 @@ public class DiagramResponse {
*
* @param response http response
*/
public static void addHeaders(HttpServletResponse response) {
private static void addHeaders(HttpServletResponse response) {
response.addHeader("X-Powered-By", POWERED_BY);
response.addHeader("X-Patreon", "Support us on https://plantuml.com/patreon");
response.addHeader("X-Donate", "https://plantuml.com/paypal");
......
......@@ -34,6 +34,7 @@ import jakarta.servlet.http.HttpServletResponse;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.servlet.utility.UmlExtractor;
import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor;
/**
* MAP servlet of the webapp.
......@@ -46,32 +47,17 @@ public class MapServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// build the UML source from the compressed request parameter
String uml = UmlExtractor.getUmlSource(getSource(request.getRequestURI()));
final String url = request.getRequestURI();
final String uml = UmlExtractor.getUmlSource(UrlDataExtractor.getEncodedDiagram(url, ""));
final int idx = UrlDataExtractor.getIndex(url, 0);
// generate the response
DiagramResponse dr = new DiagramResponse(response, getOutputFormat(), request);
try {
dr.sendMap(uml);
dr.sendMap(uml, idx);
} catch (IIOException e) {
// Browser has closed the connection, do nothing
}
dr = null;
}
/**
* Extract UML source from URI.
*
* @param uri the complete URI as returned by `request.getRequestURI()`
*
* @return the encoded UML text
*/
public String getSource(String uri) {
String[] result = uri.split("/map/", 2);
if (result.length != 2) {
return "";
} else {
return result[1];
}
}
/**
......
......@@ -45,6 +45,7 @@ import net.sourceforge.plantuml.code.TranscoderUtil;
import net.sourceforge.plantuml.png.MetadataTag;
import net.sourceforge.plantuml.servlet.utility.Configuration;
import net.sourceforge.plantuml.servlet.utility.UmlExtractor;
import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor;
/**
* Original idea from Achim Abeling for Confluence macro.
......@@ -69,10 +70,6 @@ public class PlantUmlServlet extends HttpServlet {
* Regex pattern to fetch last part of the URL.
*/
private static final Pattern URL_PATTERN = Pattern.compile("^.*[^a-zA-Z0-9\\-\\_]([a-zA-Z0-9\\-\\_]+)");
/**
* Regex pattern to fetch encoded uml text from an "uml" URL.
*/
private static final Pattern RECOVER_UML_PATTERN = Pattern.compile("/uml/(.*)");
static {
OptionFlags.ALLOW_INCLUDE = false;
......@@ -94,8 +91,11 @@ public class PlantUmlServlet extends HttpServlet {
return;
}
// diagram index to render
final int idx = UrlDataExtractor.getIndex(request.getRequestURI());
// forward to index.jsp
prepareRequestForDispatch(request, text);
prepareRequestForDispatch(request, text, idx);
final RequestDispatcher dispatcher = request.getRequestDispatcher("/index.jsp");
dispatcher.forward(request, response);
}
......@@ -107,6 +107,9 @@ public class PlantUmlServlet extends HttpServlet {
) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
// diagram index to render
final int idx = UrlDataExtractor.getIndex(request.getRequestURI());
// encoded diagram source
String encoded;
try {
......@@ -117,7 +120,7 @@ public class PlantUmlServlet extends HttpServlet {
e.printStackTrace();
}
redirectNow(request, response, encoded);
redirectNow(request, response, encoded, idx);
}
/**
......@@ -174,15 +177,16 @@ public class PlantUmlServlet extends HttpServlet {
* @throws IOException if an input or output exception occurred
*/
private String getTextFromUrl(HttpServletRequest request) throws IOException {
final Matcher recoverUml = RECOVER_UML_PATTERN.matcher(
request.getRequestURI().substring(request.getContextPath().length())
);
// the URL form has been submitted
if (recoverUml.matches()) {
final String data = recoverUml.group(1);
return getTranscoder().decode(data);
// textual diagram source from request URI
String url = request.getRequestURI();
if (url.contains("/uml/") && !url.endsWith("/uml/")) {
final String encoded = UrlDataExtractor.getEncodedDiagram(request.getRequestURI(), "");
if (!encoded.isEmpty()) {
return getTranscoder().decode(encoded);
}
}
String url = request.getParameter("url");
// textual diagram source from "url" parameter
url = request.getParameter("url");
if (url != null && !url.trim().isEmpty()) {
// Catch the last part of the URL if necessary
final Matcher matcher = URL_PATTERN.matcher(url);
......@@ -203,11 +207,12 @@ public class PlantUmlServlet extends HttpServlet {
*
* @throws IOException if an input or output exception occurred
*/
private void prepareRequestForDispatch(HttpServletRequest request, String text) throws IOException {
// diagram sources
private void prepareRequestForDispatch(HttpServletRequest request, String text, int idx) throws IOException {
final String encoded = getTranscoder().encode(text);
final String index = (idx < 0) ? "" : idx + "/";
// diagram sources
request.setAttribute("decoded", text);
request.setAttribute("encoded", encoded);
request.setAttribute("index", idx);
// properties
request.setAttribute("showSocialButtons", Configuration.get("SHOW_SOCIAL_BUTTONS"));
request.setAttribute("showGithubRibbon", Configuration.get("SHOW_GITHUB_RIBBON"));
......@@ -217,10 +222,10 @@ public class PlantUmlServlet extends HttpServlet {
// image URLs
final boolean hasImg = !text.isEmpty();
request.setAttribute("hasImg", hasImg);
request.setAttribute("imgurl", hostpath + "/png/" + encoded);
request.setAttribute("svgurl", hostpath + "/svg/" + encoded);
request.setAttribute("txturl", hostpath + "/txt/" + encoded);
request.setAttribute("mapurl", hostpath + "/map/" + encoded);
request.setAttribute("imgurl", hostpath + "/png/" + index + encoded);
request.setAttribute("svgurl", hostpath + "/svg/" + index + encoded);
request.setAttribute("txturl", hostpath + "/txt/" + index + encoded);
request.setAttribute("mapurl", hostpath + "/map/" + index + encoded);
// map for diagram source if necessary
final boolean hasMap = PlantumlUtils.hasCMapData(text);
request.setAttribute("hasMap", hasMap);
......@@ -276,7 +281,32 @@ public class PlantUmlServlet extends HttpServlet {
HttpServletResponse response,
String encoded
) throws IOException {
final String result = request.getContextPath() + "/uml/" + encoded;
redirectNow(request, response, encoded, null);
}
/**
* Send redirect response to encoded uml text.
*
* @param request http request
* @param response http response
* @param encoded encoded uml text
* @param index diagram index
*
* @throws IOException if an input or output exception occurred
*/
private void redirectNow(
HttpServletRequest request,
HttpServletResponse response,
String encoded,
Integer index
) throws IOException {
final String result;
if (index == null || index < 0) {
result = request.getContextPath() + "/uml/" + encoded;
} else {
result = request.getContextPath() + "/uml/" + index + "/" + encoded;
}
response.sendRedirect(result);
}
......
......@@ -25,8 +25,6 @@ package net.sourceforge.plantuml.servlet;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.IIOException;
......@@ -34,9 +32,11 @@ import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.OptionFlags;
import net.sourceforge.plantuml.servlet.utility.UmlExtractor;
import net.sourceforge.plantuml.servlet.utility.UrlDataExtractor;
/**
* Common service servlet to produce diagram from compressed UML source contained in the end part of the requested URI.
......@@ -44,11 +44,6 @@ import net.sourceforge.plantuml.servlet.utility.UmlExtractor;
@SuppressWarnings("SERIAL")
public abstract class UmlDiagramService extends HttpServlet {
/**
* Regex pattern to fetch encoded uml text from an URL.
*/
private static final Pattern RECOVER_UML_PATTERN = Pattern.compile("/\\w+/(\\d+/)?(.*)");
static {
OptionFlags.ALLOW_INCLUDE = false;
if ("true".equalsIgnoreCase(System.getenv("ALLOW_PLANTUML_INCLUDE"))) {
......@@ -58,12 +53,14 @@ public abstract class UmlDiagramService extends HttpServlet {
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
final String url = request.getRequestURI();
final String encoded = UrlDataExtractor.getEncodedDiagram(url, "");
final int idx = UrlDataExtractor.getIndex(url, 0);
// build the UML source from the compressed request parameter
final String[] sourceAndIdx = getSourceAndIdx(request);
final int idx = Integer.parseInt(sourceAndIdx[1]);
final String uml;
try {
uml = UmlExtractor.getUmlSource(sourceAndIdx[0]);
uml = UmlExtractor.getUmlSource(encoded);
} catch (Exception e) {
e.printStackTrace();
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad Request");
......@@ -75,18 +72,15 @@ public abstract class UmlDiagramService extends HttpServlet {
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
// build the UML source from the compressed request parameter
final String[] sourceAndIdx = getSourceAndIdx(request);
final int idx = Integer.parseInt(sourceAndIdx[1]);
final int idx = UrlDataExtractor.getIndex(request.getRequestURI(), 0);
// read textual diagram source from request body
final StringBuilder uml = new StringBuilder();
final BufferedReader in = request.getReader();
while (true) {
final String line = in.readLine();
if (line == null) {
break;
try (BufferedReader in = request.getReader()) {
String line;
while ((line = in.readLine()) != null) {
uml.append(line).append('\n');
}
uml.append(line).append('\n');
}
doDiagramResponse(request, response, uml.toString(), idx);
......@@ -118,33 +112,6 @@ public abstract class UmlDiagramService extends HttpServlet {
}
}
/**
* Extracts the UML source text and its index from the HTTP request.
*
* @param request http request
*
* @return the UML source text and its index
*/
public final String[] getSourceAndIdx(HttpServletRequest request) {
final Matcher recoverUml = RECOVER_UML_PATTERN.matcher(
request.getRequestURI().substring(
request.getContextPath().length()));
// the URL form has been submitted
if (recoverUml.matches()) {
final String data = recoverUml.group(2);
if (data.length() >= 4) {
String idx = recoverUml.group(1);
if (idx == null) {
idx = "0";
} else {
idx = idx.substring(0, idx.length() - 1);
}
return new String[]{data, idx };
}
}
return new String[]{"", "0"};
}
/**
* Gives the wished output format of the diagram. This value is used by the DiagramResponse class.
*
......
......@@ -6,26 +6,33 @@ hide empty members
hide empty methods
hide empty fields
abstract class UmlDiagramService {
public void doGet(HttpServletRequest rq, HttpServletResponse rsp)
abstract public String getSource( String uri)
abstract FileFormat getOutputFormat()
+ doGet(request: HttpServletRequest, response: HttpServletResponse) : void
+ doPost(request: HttpServletRequest, response: HttpServletResponse) : void
+ {abstract} getOutputFormat() : FileFormat
}
class DiagramResponse {
DiagramResponse( HttpServletResponse r, FileFormat f)
void sendDiagram( String uml)
void sendMap( String uml)
+ DiagramResponse(res: HttpServletResponse, fmt: FileFormat, req: HttpServletRequest)
+ sendDiagram(uml: String, idx: int) : void
+ sendMap(uml: String, idx: int) : void
+ sendCheck(uml: String) : void
}
abstract HttpServlet <|-- UmlDiagramService
abstract HttpServlet <|-- MapServlet
abstract HttpServlet <|-- ProxyServlet
UmlDiagramService <|-- PngServlet
UmlDiagramService <|-- SvgServlet
HttpServlet <|-- CheckSyntaxServlet
HttpServlet <|-- LanguageServlet
HttpServlet <|-- MapServlet
HttpServlet <|-- PlantUmlServlet
HttpServlet <|-- ProxyServlet
HttpServlet <|-- OldProxyServlet
HttpServlet <|-- UmlDiagramService
UmlDiagramService <|-- AsciiServlet
UmlDiagramService <|-- Base64Servlet
UmlDiagramService <|-- EpsServlet
UmlDiagramService <|-- EpsTextServlet
UmlDiagramService <|-- AsciiServlet
UmlDiagramService o- DiagramResponse
MapServlet o-- DiagramResponse
ProxyServlet o-- DiagramResponse
UmlDiagramService <|-- ImgServlet
UmlDiagramService <|-- SvgServlet
UmlDiagramService o-- DiagramResponse
DiagramResponse --o CheckSyntaxServlet
DiagramResponse --o MapServlet
DiagramResponse --o ProxyServlet
@enduml
## Sequence diagram ##
......@@ -33,14 +40,17 @@ ProxyServlet o-- DiagramResponse
@startuml
title Generation of a PNG image illustrated
PngServlet -> PngServlet : getSource()
PngServlet -> UmlExtractor : getUmlSource()
UmlExtractor --> PngServlet
PngServlet -> PngServlet : getOutputFormat()
PngServlet -> DiagramResponse : <<create>>
PngServlet -> DiagramResponse : sendDiagram()
ImgServlet -> UrlDataExtractor : getEncodedDiagram()
UrlDataExtractor --> ImgServlet : encoded
ImgServlet -> UrlDataExtractor : getIndex()
UrlDataExtractor --> ImgServlet : index
ImgServlet -> UmlExtractor : getUmlSource()
UmlExtractor --> ImgServlet : decoded
ImgServlet -> ImgServlet : getOutputFormat()
ImgServlet -> "dr:DiagramResponse" as dr ** : <<create>>
ImgServlet -> dr : sendDiagram()
participant "PlantUML library" as Lib #99FF99
DiagramResponse -> Lib : generateImage()
Lib --> DiagramResponse
DiagramResponse --> PngServlet
@enduml
\ No newline at end of file
dr -> Lib : generateImage()
Lib --> dr
dr --> ImgServlet
@enduml
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* Project Info: https://plantuml.com
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlantUML distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package net.sourceforge.plantuml.servlet.utility;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility class to extract the index and diagram source from an URL, e.g., returned by `request.getRequestURI()`.
*/
public abstract class UrlDataExtractor {
/**
* URL regex pattern to easily extract index and encoded diagram.
*/
private static final Pattern URL_PATTERN = Pattern.compile(
"/\\w+(?:/(?<idx>\\d+))?(?:/(?<encoded>[^/]+))?/?$"
);
/**
* Get diagram index from URL.
*
* @param url URL to analyse, e.g., returned by `request.getRequestURI()`
*
* @return if exists diagram index; otherwise -1
*/
public static int getIndex(final String url) {
return getIndex(url, -1);
}
/**
* Get diagram index from URL.
*
* @param url URL to analyse, e.g., returned by `request.getRequestURI()`
* @param fallback fallback index if no index exists in {@code url}
*
* @return if exists diagram index; otherwise {@code fallback}
*/
public static int getIndex(final String url, final int fallback) {
final Matcher matcher = URL_PATTERN.matcher(url);
if (!matcher.find()) {
return fallback;
}
String idx = matcher.group("idx");
if (idx == null) {
return fallback;
}
return Integer.parseInt(idx);
}
/**
* Get encoded diagram source from URL.
*
* @param url URL to analyse, e.g., returned by `request.getRequestURI()`
*
* @return if exists diagram index; otherwise `null`
*/
public static String getEncodedDiagram(final String url) {
return getEncodedDiagram(url, null);
}
/**
* Get encoded diagram source from URL.
*
* @param url URL to analyse, e.g., returned by `request.getRequestURI()`
* @param fallback fallback if no encoded diagram source exists in {@code url}
*
* @return if exists diagram index; otherwise {@code fallback}
*/
public static String getEncodedDiagram(final String url, final String fallback) {
final Matcher matcher = URL_PATTERN.matcher(url);
if (!matcher.find()) {
return fallback;
}
String encoded = matcher.group("encoded");
if (encoded == null) {
return fallback;
}
return encoded;
}
}
......@@ -3,7 +3,6 @@
<%
// diagram sources
String decoded = request.getAttribute("decoded").toString();
String encoded = request.getAttribute("encoded").toString();
// properties
boolean showSocialButtons = (boolean)request.getAttribute("showSocialButtons");
boolean showGithubRibbon = (boolean)request.getAttribute("showGithubRibbon");
......
......@@ -216,4 +216,88 @@ public class TestForm extends WebappTestCase {
}
}
/**
* Verifies that an multipage diagram renders correct given index.
*
* Bob -> Alice : hello
* newpage
* Bob <- Alice : hello
* Bob -> Alice : let's talk
* Bob <- Alice : better not
* Bob -> Alice : <&rain> bye
* newpage
* Bob <- Alice : bye
*/
public void testIndexPage() throws IOException {
try (final WebClient webClient = new WebClient()) {
HtmlPage page = webClient.getPage(
getServerUrl() + "/uml/1/" +
"SyfFKj2rKt3CoKnELR1Io4ZDoSddoaijBqXCJ-Lo0ahQwA99Eg7go4ajKIzMA4dCoKPNdfHQKf9Qf92NNuAknqQjA34ppquXgJ8Lbrr0AG00"
);
// Analyze response
List<HtmlForm> forms = page.getForms();
assertEquals(2, forms.size());
// Ensure the Text field is correct
String text = ((HtmlTextArea)(forms.get(0).getFirstByXPath("//textarea[contains(@name, 'text')]"))).getTextContent();
assertEquals(
"@startuml\nBob -> Alice : hello\nnewpage\nBob <- Alice : hello\nBob -> Alice : let's talk\nBob <- Alice : better not\nBob -> Alice : <&rain> bye\nnewpage\nBob <- Alice : bye\n@enduml",
text
);
// Ensure the URL field is correct