[jboss-cvs] jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/links ...
Christian Bauer
christian at hibernate.org
Thu Mar 22 08:16:07 EDT 2007
User: cbauer
Date: 07/03/22 08:16:07
Modified: examples/wiki/src/main/org/jboss/seam/wiki/core/links
WikiLinkResolver.java
Added: examples/wiki/src/main/org/jboss/seam/wiki/core/links
WikiTextParser.java WikiTextRenderer.java
WikiLink.java DefaultWikiLinkResolver.java
Log:
Cleaned up wiki core link engine
Revision Changes Path
1.5 +128 -162 jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/links/WikiLinkResolver.java
(In the diff below, changes in quantity of whitespace are not shown.)
Index: WikiLinkResolver.java
===================================================================
RCS file: /cvsroot/jboss/jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/links/WikiLinkResolver.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -b -r1.4 -r1.5
--- WikiLinkResolver.java 20 Mar 2007 02:38:14 -0000 1.4
+++ WikiLinkResolver.java 22 Mar 2007 12:16:07 -0000 1.5
@@ -1,173 +1,139 @@
package org.jboss.seam.wiki.core.links;
-import org.jboss.seam.annotations.Name;
-import org.jboss.seam.annotations.In;
-import org.jboss.seam.annotations.Transactional;
-import org.jboss.seam.annotations.AutoCreate;
-import org.jboss.seam.wiki.core.model.Document;
-import org.jboss.seam.wiki.core.model.Directory;
-import org.jboss.seam.wiki.core.dao.NodeDAO;
-import org.jboss.seam.wiki.core.model.*;
-import org.jboss.seam.wiki.core.ui.WikiUtil;
-import org.jboss.seam.wiki.core.ui.WikiLink;
-
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
import java.util.Map;
+import java.util.regex.Pattern;
- at Name("wikiLinkResolver")
- at AutoCreate
-public class WikiLinkResolver {
-
- // Prepended to primary keys in the database, e.g. [This is a stored link=>wiki://5]
- public static final String WIKI_PROTOCOL = "wiki://([0-9]+)";
-
- // Known protocols are rendered as is
- public static final String KNOWN_PROTOCOLS = "(http://)|(https://)|(ftp://)|(mailto:)";
-
- // Render these strings whenever [=>wiki://123] needs to be resolved but can't
- public static final String BROKENLINK_URL = "PageDoesNotExist";
- public static final String BROKENLINK_DESCRIPTION = "?BROKEN LINK?";
+/**
+ * The heart of the wiki, converts and resolves human-readable link tags from and to permanent
+ * links that can be stored (or read) persistently. Also resolves link texts (from stored
+ * link tags) to <tt>WikiLink</tt> objects, for rendering.
+ * <p>
+ * Use the supplied regular expressions to implement the methods, or parse the wiki text
+ * completely by hand and convert/resolve links.
+ *
+ * @author Christian Bauer
+ */
+public interface WikiLinkResolver {
- // Match [GROUP1=>GROUP2], used to replace links from user input with wiki:// URLs
- public static final String WIKILINK_REGEX_FORWARD =
+ /**
+ * Matches known protocols, e.g. [=>http://foo.bar], which can be ignored and "resolved" as-is
+ */
+ public static final String REGEX_KNOWN_PROTOCOL = "(http://)|(https://)|(ftp://)|(mailto:)";
+
+ /**
+ * Prepended to primary identifiers when links are stored, e.g. [This is a stored link=>wiki://5]
+ */
+ public static final String REGEX_WIKI_PROTOCOL = "wiki://([0-9]+)";
+
+ /**
+ * Match [GROUP1=>GROUP1], used to replace links from user input with wiki:// URLs - used
+ * in <tt>convertToWikiProtocol()</tt>.
+ */
+ public static final String REGEX_WIKILINK_FORWARD =
Pattern.quote("[") + "([^" + Pattern.quote("]") + "|" + Pattern.quote("[") + "]*)" +
"=>([^(?://)@" + Pattern.quote("]") + Pattern.quote("[") + "]+)" + Pattern.quote("]");
- // Match "Foo Bar|Baz Brrr" as two groups
- public static final String WIKILINK_REGEX_CROSSAREA = "^(.+)" + Pattern.quote("|") + "(.*)$";
-
- // Match [GROUP1=>wiki://GROUP2], used to replace wiki:// URLs with page names
- public static final String WIKILINK_REGEX_REVERSE =
+ /**
+ * Match [GROUP1=>wiki://GROUP2], used to replace wiki:// URLs with page names
+ */
+ public static final String REGEX_WIKILINK_REVERSE =
Pattern.quote("[") + "([^" + Pattern.quote("]") + "|" + Pattern.quote("[") + "]*)" +
- "=>" + WIKI_PROTOCOL + Pattern.quote("]");
+ "=>" + REGEX_WIKI_PROTOCOL + Pattern.quote("]");
+
+ /**
+ * Match "Foo Bar|Baz Brrr" as two groups
+ */
+ public static final String REGEX_WIKILINK_CROSSAREA = "^(.+)" + Pattern.quote("|") + "(.*)$";
- @In
- private NodeDAO nodeDAO;
+ /**
+ * Replaces clear text links such as <tt>[=>Target Name]</tt> in <tt>wikiText</tt> with
+ * <tt>[=>wiki://id]</tt> strings, usually resolves the target name as a unique wiki name in some data store.
+ * The <tt>currentAreaNumber</tt> of the current document is supplied and can be used as the namespace for scoped resolving.
+ * <p>
+ * This method should be called whenever a wiki document is stored, we want to store the permanent
+ * identifiers of a target node. That way, the target node can be renamed and the document that links
+ * to that target node still contains the valid link.
+ * </p><p>
+ * Either parse the <tt>wikiText</tt> by hand to find and replace links, or use the
+ * <tt>REGEX_WIKILINK_FORWARD</tt> pattern which matches <tt>[GROUP1=>GROUP2]</tt>.
+ * Convert the target name (<tt>GROUP2</tt>) to a unique wiki name, and then to some primary
+ * identifier which you can lookup again in the future in a reliable fashion. <tt>GROUP1</tt> is
+ * the optional link description entered by the user, you need to keep this string and only replace
+ * <tt>GROUP2</tt> with a permanent identifier (prefixed with the <tt>wiki://</tt> protocol).
+ * </p><p>
+ * Note that cross-namespace linking should be supported, so in addition to <tt>[=>Target Name]</tt>,
+ * links can be entered by the user as <tt>[=>Target Area|Target Name]</tt>. To resolve these link
+ * texts, use <tt>REGEX_WIKILINK_CROSSAREA</tt> on the original <tt>GROUP1</tt>, which produces
+ * two groups. Ignore the given <tt>currentAreaNumber</tt> parameter and resolve in the target namespace entered by
+ * the user on the link tag.
+ * </p><p>
+ * Example pseudo code:
+ * </p><p>
+ * <pre>
+ * if (targetName = wikiText.match(REGEX_WIKI_LINK_FORWARD)) {
+ *
+ * if (targetNamespace, newTargetName = targetName.match(REGEX_WIKILINK_CROSSAREA) {
+ *
+ * wikiText.replace( resolveNodeId(targetNamespace, newTargetName) );
+ *
+ * } else {
+ * wikiText.replace( resolveNodeId(givenNamespace, targetName) );
+ * }
+ * }
+ * </pre>
+ *
+ * @param currentAreaNumber The currennt area useable as the namespace for scoped resolving
+ * @param wikiText Text with wiki markup containing [=>Target Name] links
+ * @return The <tt>wikiText</tt> with all <tt>[=>Target Name]<tt> links replaced with <tt>[=>wiki://id]</tt>
+ */
+ public String convertToWikiProtocol(Long currentAreaNumber, String wikiText);
+
+ /**
+ * Replace stored text links such as <tt>[Link description=>wiki://id]</tt> with clear text target names, so
+ * users can edit the link again in clear text.
+ * </p><p>
+ * Either parse by hand or use the <tt>REGEX_WIKILINK_REVERSE</tt> pattern, which matches
+ * <tt>[GROUP1=>wiki://GROUP2]. Replace with <tt>[GROUP1=>Target Name]</tt> or, if the target is not in
+ * the same namespace as the given <tt>area</tt> parameter, append the area:
+ * <tt>[GROUP1=>Target Area|Target Name]</tt>.
+ *
+ * @param currentAreaNumber The current area useable as the namespace for scoped resolving
+ * @param wikiText Text with wiki markup containing [=>wiki://id] links
+ * @return The <tt>wikiText</tt> with all <tt>[=>wiki://id]<tt> links replaced with <tt>[=>Target Name]</tt>
+ */
+ public String convertFromWikiProtocol(Long currentAreaNumber, String wikiText);
- // Only injected during rendering of a document, for updating of resolved links
- @In(required = false) private Document currentDocument;
- // Always needed for resolving (is current area)
- @In private Directory currentDirectory;
-
- public String convertToWikiLinks(Directory area, String wikiText) {
- if (wikiText == null) return null;
-
- StringBuffer replacedWikiText = new StringBuffer(wikiText.length());
- Matcher matcher = Pattern.compile(WIKILINK_REGEX_FORWARD).matcher(wikiText);
-
- // Replace with [Link Text=>wiki://<node id>] or leave as is if not found
- while (matcher.find()) {
- String linkText = matcher.group(2);
- Node node = resolveCrossAreaLinkText(area, linkText);
- if (node != null) matcher.appendReplacement(replacedWikiText, "[$1=>wiki://" + node.getId() + "]");
- }
- matcher.appendTail(replacedWikiText);
- return replacedWikiText.toString();
- }
-
- public String convertFromWikiLinks(Directory area, String wikiText) {
- if (wikiText == null) return null;
-
- StringBuffer replacedWikiText = new StringBuffer(wikiText.length());
- Matcher matcher = Pattern.compile(WIKILINK_REGEX_REVERSE).matcher(wikiText);
-
- // Replace with [Link Text=>Page Name] or replace with BROKENLINK "page name"
- while (matcher.find()) {
-
- // Find the node by PK
- Node node = nodeDAO.findNode(Long.valueOf(matcher.group(2)));
-
- // Node is in current area, just use its name
- if (node != null && node.getAreaNumber().equals(area.getAreaNumber())) {
- matcher.appendReplacement(replacedWikiText, "[$1=>" + node.getName() + "]");
-
- // Node is in different area, prepend the area name
- } else if (node != null && !node.getAreaNumber().equals(area.getAreaNumber())) {
- matcher.appendReplacement(replacedWikiText, "[$1=>" + node.getArea().getName() + "|" + node.getName() + "]");
-
- // Couldn't find it anymore, its a broken link
- } else {
- matcher.appendReplacement(replacedWikiText, "[$1=>" + BROKENLINK_DESCRIPTION + "]");
- }
- }
- matcher.appendTail(replacedWikiText);
- return replacedWikiText.toString();
- }
-
- @Transactional
- public void resolveWikiLink(Map<String, WikiLink> links, String linkText) {
-
- // Don't resolve twice
- if (links.containsKey(linkText)) return;
-
- Matcher wikiUrlMatcher = Pattern.compile(WIKI_PROTOCOL).matcher(linkText);
- Matcher knownProtocolMatcher = Pattern.compile(KNOWN_PROTOCOLS).matcher(linkText);
-
- WikiLink wikiLink;
-
- // Check if its a common protocol
- if (knownProtocolMatcher.find()) {
- wikiLink = new WikiLink(null, false, linkText, linkText, true);
-
- // Check if it is a wiki protocol
- } else if (wikiUrlMatcher.find()) {
-
- // Find the node by PK
- Node node = nodeDAO.findNode(Long.valueOf(wikiUrlMatcher.group(1)));
- if (node != null) {
- wikiLink = new WikiLink(node, false, WikiUtil.renderURL(node), node.getName(), false);
- } else {
- wikiLink = new WikiLink(null, true, BROKENLINK_URL, BROKENLINK_DESCRIPTION, false);
- }
-
- // Try a WikiWord search in the current or named area
- // (This can happen if the string [foo=>bar] or [foo=>bar|baz] was stored in the database because bar or baz
- // didn't exist at the time of saving, so we need to resolve it now and replace it with wiki://123)
- } else {
-
- Node node = resolveCrossAreaLinkText(currentDirectory, linkText);
- if (node!=null) {
- wikiLink = new WikiLink(node, false, WikiUtil.renderURL(node), node.getName(), false);
- // Run the converter again and UPDATE the currentDocument (yes, this happens during rendering!)
- currentDocument.setContent(convertToWikiLinks(currentDirectory, currentDocument.getContent()));
- // This should be updated in the database during the next flush()
-
- } else {
- /* TODO: Not sure we should actually implement this..., one of these things that the wiki "designers" got wrong
- // OK, so it's not any recognized URL and we can't find a node with that wikiname
- // Let's assume its a page name and render /Area/WikiLink (but encoded, so it gets transported fully)
- // into the edit page when the user clicks on the link to create the document
- try {
- String encodedPagename = currentDirectory.getWikiname() + "/" + URLEncoder.encode(linkText, "UTF-8");
- wikiLink = new WikiLink(null, true, encodedPagename, linkText);
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e); // Java is so great...
- }
- */
- wikiLink = new WikiLink(null, true, BROKENLINK_URL, BROKENLINK_DESCRIPTION, false);
- }
- }
- links.put(linkText, wikiLink);
- }
-
- private Node resolveCrossAreaLinkText(Node currentArea, String linkText) {
- Matcher crossLinkMatcher = Pattern.compile(WIKILINK_REGEX_CROSSAREA).matcher(linkText);
- if (crossLinkMatcher.find()) {
- // Try to find the node in the referenced area
- String areaName = crossLinkMatcher.group(1);
- String nodeName = crossLinkMatcher.group(2);
- Node crossLinkArea = nodeDAO.findArea(WikiUtil.convertToWikiName(areaName));
- if ( crossLinkArea != null && (nodeName == null || nodeName.length() == 0) )
- return crossLinkArea; // Support [=>This is an Area Link|] syntax
- else if (crossLinkArea != null)
- return nodeDAO.findNodeInArea(crossLinkArea.getAreaNumber(), WikiUtil.convertToWikiName(nodeName));
- } else {
- // Try the current area
- return nodeDAO.findNodeInArea(currentArea.getAreaNumber(), WikiUtil.convertToWikiName(linkText));
- }
- return null;
- }
+ /**
+ * Resolve the given <tt>linkText</tt> to an instance of <tt>WikiLink</tt> and put it in the <tt>link</tt> map.
+ * <p>
+ * The <tt>WikiLink</tt> objects are used during rendering, the rules are as follows:
+ * <ul>
+ * <li>If the <tt>linkText</tt> matches <tt>REGEX_KNOWN_PROTOCOL</tt>, don't resolve but create
+ * a <tt>WikiLink</tt> instance that contains <tt>url</tt>, <tt>description</tt> (same as <tt>url</tt>),
+ * <tt>broken=false</tt>, <tt>external=true</tt>. The <tt>url</tt> is the actual <tt>linkText</tt>, as-is.
+ * </li>
+ * <li>If the <tt>linkText</tt> matches <tt>REGEX_WIKI_PROTOCOL</tt>, resolve it and create
+ * a <tt>WikiLink</tt> instance that contains the resolved <tt>Node</tt> instance, the node name
+ * as <tt>description</tt>, no <tt>url</tt>, and <tt>external=false</tt>. If the <tt>linkText</tt>
+ * can't be resolved to a <tt>Node</tt>, set <tt>broken=true</tt>, a null <tt>node</tt>, and whatever
+ * <tt>url</tt> and <tt>description</tt> you want to render for a broken link.
+ * </li>
+ * <li>Otherwise, the <tt>linkText</tt> represents a clear text link such as <tt>Target Name</tt> or
+ * <tt>Target Area|TargetName</tt>, which you can resolve if you want and return a
+ * <tt>WikiLink</tt> instance as in the previous rule. If it can't be resolved, return a broken link
+ * indicator as described in the previous rule. If it has been resolved, you may indicate that the
+ * original document that contains this <tt>linkText</tt> should be updated in the datastore (usually
+ * by passing its wiki text content through <tt>convertToWikiProtocol</tt>) - set <tt>requiresUpdating=true</tt>
+ * on the <tt>WikiLink</tt> instance. It's the job of the client of this resolver to handle this flag
+ * (or ignore it).
+ *</li>
+ * </ul>
+ *
+ * @param currentAreaNumber The current area useable as the namespace for scoped resolving
+ * @param links A map of all resolved <tt>WikiLink</tt> objects, keyed by <tt>linkText</tt>
+ * @param linkText A stored link text, such as "wiki://123" or "http://foo.bar" or "Target Area|Target Name]"
+ */
+ public void resolveLinkText(Long currentAreaNumber, Map<String, WikiLink> links, String linkText);
}
1.1 date: 2007/03/22 12:16:07; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/links/WikiTextParser.java
Index: WikiTextParser.java
===================================================================
package org.jboss.seam.wiki.core.links;
import org.jboss.seam.text.SeamTextParser;
import org.jboss.seam.text.SeamTextLexer;
import org.jboss.seam.wiki.core.model.File;
import org.jboss.seam.wiki.core.model.Document;
import org.jboss.seam.wiki.core.model.Directory;
import org.jboss.seam.wiki.util.WikiUtil;
import org.jboss.seam.Component;
import antlr.ANTLRException;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.io.StringReader;
/**
* Parses SeamText markup and also resolves link and macro tags as wiki links and wiki plugins.
* <p>
* Requires and looks up the contextual variables <tt>currentDocument</tt> and <tt>currentDirectory</tt>.
* Picks the <tt>WikiLinkResolver</tt> present in the contextual variable <tt>wikiLinkResolver</tt>. Calls
* out to a <tt>WikiRender</tt> for the actual in-document rendering of wiki links and wiki plugins. Might update
* the <tt>currentDocument</tt>'s content, this change should be flushed to the datastore after calling
* the parser.
* </p><p>
* After parsing, all links to attachments and all external links are pushed onto the renderer, where they
* can be used to render an attachment list or similar.
*
* @author Christian Bauer
*/
public class WikiTextParser extends SeamTextParser {
private WikiTextRenderer renderer;
private WikiLinkResolver resolver;
private Directory currentDirectory;
private Document currentDocument;
private Map<String, WikiLink> resolvedLinks = new HashMap<String, WikiLink>();
private List<WikiLink> attachments = new ArrayList<WikiLink>();
private List<WikiLink> externalLinks = new ArrayList<WikiLink>();
public WikiTextParser(String wikiText, WikiTextRenderer renderer) {
super(new SeamTextLexer(new StringReader(wikiText)));
this.renderer = renderer;
resolver = (WikiLinkResolver)Component.getInstance("wikiLinkResolver");
currentDocument = (Document)Component.getInstance("currentDocument");
if (currentDocument == null) throw new RuntimeException("WikiTextParser needs the currentDocument context variable");
currentDirectory = (Directory)Component.getInstance("currentDirectory");
if (currentDirectory == null) throw new RuntimeException("WikiTextParser needs the currentDirectory context variable");
}
/**
* Start parsing the wiki text and resolve wiki links and wiki plugins.
* <p>
* If <tt>updateResolvedLinks</tt> is enabled, the <t>currentDocument</tt>'s content will
* be udpated after parsing the wiki text. This only occurs if we hit a link during link
* resolution that needs to be updated. You should flush this modification do the data store.
*
* @param updateResolvedLinks Set updated content on <tt>currentDocument</tt>
*/
public void parse(boolean updateResolvedLinks) {
try {
startRule();
if (updateResolvedLinks) {
for (Map.Entry<String, WikiLink> entry: resolvedLinks.entrySet()) {
if(entry.getValue().isRequiresUpdating()) {
// One of the links we parsed and resolved requires updating of the current document, run
// the protocol converter - which is usally only called when storing a document.
currentDocument.setContent(
resolver.convertToWikiProtocol(currentDirectory.getAreaNumber(), currentDocument.getContent())
);
// Yes, this might happen during rendering, we flush() and UPDATE the document!
break; // One is enough
}
}
}
renderer.setAttachmentLinks(attachments);
renderer.setExternalLinks(externalLinks);
}
catch (ANTLRException re) {
// TODO: Do we ever get this exception?
throw new RuntimeException(re);
}
}
protected String linkTag(String descriptionText, String linkText) {
resolver.resolveLinkText(currentDirectory.getAreaNumber(), resolvedLinks, linkText);
WikiLink link = resolvedLinks.get((linkText));
if (link == null) return "";
// Override the description of the WikiLink with description found in tag
String finalDescriptionText =
(descriptionText!=null && descriptionText.length() > 0 ? descriptionText : link.getDescription());
link.setDescription(finalDescriptionText);
// Link to file (inline or attached)
if (WikiUtil.isFile(link.getNode())) {
File file = (File)link.getNode();
if (file.getImageMetaInfo() == null || 'A' == file.getImageMetaInfo().getThumbnail()) {
// It's an attachment
if (!attachments.contains(link)) attachments.add(link);
return renderer.renderFileAttachmentLink((attachments.indexOf(link)+1), link);
} else {
// It's an embedded thumbnail
return renderer.renderThumbnailImageInlineLink(link);
}
}
// External link
if (link.isExternal()) {
if (!externalLinks.contains(link)) externalLinks.add(link);
return renderer.renderExternalLink(link);
}
// Regular link
return renderer.renderInlineLink(link);
}
protected String macroInclude(String macroName) {
// Filter out any dangerous characters
return renderer.renderMacro(macroName.replaceAll("[^\\p{Alnum}]+", ""));
}
}
1.1 date: 2007/03/22 12:16:07; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/links/WikiTextRenderer.java
Index: WikiTextRenderer.java
===================================================================
package org.jboss.seam.wiki.core.links;
import java.util.List;
/**
* Called by the WikiTextParser to render [A Link=>Target] and [<=MacroName].
*
* @author Christian Bauer
*/
public interface WikiTextRenderer {
public String renderInlineLink(WikiLink inlineLink);
public String renderExternalLink(WikiLink externalLink);
public String renderThumbnailImageInlineLink(WikiLink inlineLink);
public String renderFileAttachmentLink(int attachmentNumber, WikiLink attachmentLink);
public void setAttachmentLinks(List<WikiLink> attachmentLinks);
public void setExternalLinks(List<WikiLink> externalLinks);
public String renderMacro(String macroName);
}
1.1 date: 2007/03/22 12:16:07; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/links/WikiLink.java
Index: WikiLink.java
===================================================================
package org.jboss.seam.wiki.core.links;
import org.jboss.seam.wiki.core.model.Node;
/**
* Simple value holder for link resolution and rendering.
*
* @author Christian Bauer
*/
public class WikiLink {
Node node;
boolean requiresUpdating = false;
String url;
String description;
boolean broken = false;
boolean external = false;
public WikiLink(boolean broken, boolean external) {
this.broken = broken;
this.external = external;
}
public Node getNode() { return node; }
public void setNode(Node node) { this.node = node; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public boolean isBroken() { return broken; }
public boolean isExternal() { return external; }
public boolean isRequiresUpdating() { return requiresUpdating; }
public void setRequiresUpdating(boolean requiresUpdating) { this.requiresUpdating = requiresUpdating; }
public String toString() {
return "Node:" + node + " Description: " + description + " URL: " + url;
}
}
1.1 date: 2007/03/22 12:16:07; author: cbauer; state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/core/links/DefaultWikiLinkResolver.java
Index: DefaultWikiLinkResolver.java
===================================================================
package org.jboss.seam.wiki.core.links;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.wiki.core.model.Directory;
import org.jboss.seam.wiki.core.dao.NodeDAO;
import org.jboss.seam.wiki.core.model.*;
import org.jboss.seam.wiki.util.WikiUtil;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Map;
/**
* A default implementation of <tt>WikiLinkResolver</tt>.
*
* @author Christian Bauer
*/
@Name("wikiLinkResolver")
@AutoCreate
public class DefaultWikiLinkResolver implements WikiLinkResolver {
// Render these strings whenever [=>wiki://123] needs to be resolved but can't
public static final String BROKENLINK_URL = "PageDoesNotExist";
public static final String BROKENLINK_DESCRIPTION = "?BROKEN LINK?";
@In
private NodeDAO nodeDAO;
public String convertToWikiProtocol(Long currentAreaNumber, String wikiText) {
if (wikiText == null) return null;
StringBuffer replacedWikiText = new StringBuffer(wikiText.length());
Matcher matcher = Pattern.compile(REGEX_WIKILINK_FORWARD).matcher(wikiText);
// Replace with [Link Text=>wiki://<node id>] or leave as is if not found
while (matcher.find()) {
String linkText = matcher.group(2);
Node node = resolveCrossAreaLinkText(currentAreaNumber, linkText);
if (node != null) matcher.appendReplacement(replacedWikiText, "[$1=>wiki://" + node.getId() + "]");
}
matcher.appendTail(replacedWikiText);
return replacedWikiText.toString();
}
public String convertFromWikiProtocol(Long currentAreaNumber, String wikiText) {
if (wikiText == null) return null;
StringBuffer replacedWikiText = new StringBuffer(wikiText.length());
Matcher matcher = Pattern.compile(REGEX_WIKILINK_REVERSE).matcher(wikiText);
// Replace with [Link Text=>Page Name] or replace with BROKENLINK "page name"
while (matcher.find()) {
// Find the node by PK
Node node = nodeDAO.findNode(Long.valueOf(matcher.group(2)));
// Node is in current area, just use its name
if (node != null && node.getAreaNumber().equals(currentAreaNumber)) {
matcher.appendReplacement(replacedWikiText, "[$1=>" + node.getName() + "]");
// Node is in different area, prepend the area name
} else if (node != null && !node.getAreaNumber().equals(currentAreaNumber)) {
Directory area = nodeDAO.findArea(node.getAreaNumber());
matcher.appendReplacement(replacedWikiText, "[$1=>" + area.getName() + "|" + node.getName() + "]");
// Couldn't find it anymore, its a broken link
} else {
matcher.appendReplacement(replacedWikiText, "[$1=>" + BROKENLINK_DESCRIPTION + "]");
}
}
matcher.appendTail(replacedWikiText);
return replacedWikiText.toString();
}
@Transactional
public void resolveLinkText(Long currentAreaNumber, Map<String, WikiLink> links, String linkText) {
// Don't resolve twice
if (links.containsKey(linkText)) return;
Matcher wikiProtocolMatcher = Pattern.compile(REGEX_WIKI_PROTOCOL).matcher(linkText);
Matcher knownProtocolMatcher = Pattern.compile(REGEX_KNOWN_PROTOCOL).matcher(linkText);
WikiLink wikiLink;
// Check if its a common protocol
if (knownProtocolMatcher.find()) {
wikiLink = new WikiLink(false, true);
wikiLink.setUrl(linkText);
wikiLink.setDescription(linkText);
// Check if it is a wiki protocol
} else if (wikiProtocolMatcher.find()) {
// Find the node by PK
Node node = nodeDAO.findNode(Long.valueOf(wikiProtocolMatcher.group(1)));
if (node != null) {
wikiLink = new WikiLink(false, false);
wikiLink.setNode(node);
wikiLink.setDescription(node.getName());
} else {
// Can't do anything, [=>wiki://123] no longer exists
wikiLink = new WikiLink(true, false);
wikiLink.setUrl(BROKENLINK_URL);
wikiLink.setDescription(BROKENLINK_DESCRIPTION);
}
// It must be a stored clear text link, such as [=>Target Name] or [=>Area Name|Target Name]
// (This can happen if the string [foo=>bar] or [foo=>bar|baz] was stored in the database because the
// targets didn't exist at the time of saving)
} else {
// Try a WikiWord search in the current or named area
Node node = resolveCrossAreaLinkText(currentAreaNumber, linkText);
if (node!=null) {
wikiLink = new WikiLink(false, false);
wikiLink.setNode(node);
wikiLink.setDescription(node.getName());
// Indicate that caller should update the wiki text that contains this link
wikiLink.setRequiresUpdating(true);
} else {
/* TODO: Not sure we should actually implement this..., one of these things that the wiki "designers" got wrong
// OK, so it's not any recognized URL and we can't find a node with that wikiname
// Let's assume its a page name and render /Area/WikiLink (but encoded, so it gets transported fully)
// into the edit page when the user clicks on the link to create the document
try {
String encodedPagename = currentDirectory.getWikiname() + "/" + URLEncoder.encode(linkText, "UTF-8");
wikiLink = new WikiLink(null, true, encodedPagename, linkText);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // Java is so great...
}
*/
wikiLink = new WikiLink(true, false);
wikiLink.setUrl(BROKENLINK_URL);
wikiLink.setDescription(BROKENLINK_DESCRIPTION);
}
}
links.put(linkText, wikiLink);
}
private Node resolveCrossAreaLinkText(Long currentAreaNumber, String linkText) {
Matcher crossLinkMatcher = Pattern.compile(REGEX_WIKILINK_CROSSAREA).matcher(linkText);
if (crossLinkMatcher.find()) {
// Try to find the node in the referenced area
String areaName = crossLinkMatcher.group(1);
String nodeName = crossLinkMatcher.group(2);
Node crossLinkArea = nodeDAO.findArea(WikiUtil.convertToWikiName(areaName));
if ( crossLinkArea != null && (nodeName == null || nodeName.length() == 0) )
return crossLinkArea; // Support [=>This is an Area Link|] syntax
else if (crossLinkArea != null)
return nodeDAO.findNodeInArea(crossLinkArea.getAreaNumber(), WikiUtil.convertToWikiName(nodeName));
} else {
// Try the current area
return nodeDAO.findNodeInArea(currentAreaNumber, WikiUtil.convertToWikiName(linkText));
}
return null;
}
}
More information about the jboss-cvs-commits
mailing list