package org.jboss.seam.wiki.core.action;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.log.Log;
import org.jboss.seam.wiki.core.dao.WikiNodeDAO;
import org.jboss.seam.wiki.core.model.*;
import org.jboss.seam.wiki.core.wikitext.engine.WikiLinkResolver;
import org.jboss.seam.wiki.core.wikitext.engine.WikiLink;
import org.jboss.seam.wiki.util.WikiUtil;
import java.util.Map;
import java.util.Set;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A default implementation of <tt>WikiLinkResolver</tt>.
*
* @author Christian Bauer
*/
@Name("wikiLinkResolver")
@AutoCreate
public class DefaultWikiLinkResolver implements WikiLinkResolver {
@Logger static Log log;
// Render these strings whenever [=>wiki://123] needs to be resolved but can't
public static final String BROKENLINK_URL = "FileNotFound";
public static final String BROKENLINK_DESCRIPTION = "?BROKEN LINK?";
@In
private WikiNodeDAO wikiNodeDAO;
@In
Map<String, LinkProtocol> linkProtocolMap;
public String convertToWikiProtocol(Set<WikiFile> linkTargets, Long currentAreaNumber, String wikiText) {
if (wikiText == null) return null;
log.debug("converting wiki text links to wiki protocol for storage, current area: " + currentAreaNumber);
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);
if (linkText.matches(REGEX_KNOWN_PROTOCOL) || linkText.matches(REGEX_CUSTOM_PROTOCOL)) continue;
log.debug("converting to wiki protocol: " + linkText);
WikiFile file = null;
String fragment = null;
Matcher linkTextMatcher = getCrossAreaMatcher(linkText);
if (linkTextMatcher != null) {
log.debug("link to different area: " + linkTextMatcher.group(1));
file = resolve(currentAreaNumber, linkTextMatcher.group(1), linkTextMatcher.group(2));
fragment = linkTextMatcher.group(3);
} else {
log.debug("link to current area");
linkTextMatcher = getAreaMatcher(linkText);
if (linkTextMatcher != null) {
file = resolve(currentAreaNumber, null, linkTextMatcher.group(1));
fragment = linkTextMatcher.group(2);
}
}
log.debug("resolved file: " + file);
log.debug("resolved fragment: " + fragment);
if (file != null) {
if (fragment == null) fragment = "";
log.debug("updating wiki text with wiki protocol link: " + "wiki://" + file.getId() + ""+fragment);
matcher.appendReplacement(replacedWikiText, "[$1=>wiki://" + file.getId() + ""+fragment+"]");
linkTargets.add(file);
}
}
matcher.appendTail(replacedWikiText);
log.debug("completed converting wiki text links to wiki protocol, ready for storing");
return replacedWikiText.toString();
}
public String convertFromWikiProtocol(Long currentAreaNumber, String wikiText) {
if (wikiText == null) return null;
log.debug("converting wiki protocol to wiki text, current area: " + currentAreaNumber);
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()) {
String fileId = matcher.group(2);
String fragment = matcher.group(3);
log.debug("found file id: " + fileId);
log.debug("found fragment: " + fragment);
if (fragment == null) fragment = "";
// Find the node by PK
WikiFile file = wikiNodeDAO.findWikiFile(Long.valueOf(fileId));
// Node is in current area, just use its name
if (file != null && file.getAreaNumber().equals(currentAreaNumber)) {
matcher.appendReplacement(replacedWikiText, "[$1=>" + file.getName() + fragment +"]");
// Node is in different area, prepend the area name
} else if (file != null && !file.getAreaNumber().equals(currentAreaNumber)) {
WikiDirectory area = wikiNodeDAO.findArea(file.getAreaNumber());
matcher.appendReplacement(replacedWikiText, "[$1=>" + area.getName() + "|" + file.getName() + fragment +"]");
// Couldn't find it anymore, its a broken link
} else {
matcher.appendReplacement(replacedWikiText, "[$1=>" + BROKENLINK_DESCRIPTION + "]");
}
}
matcher.appendTail(replacedWikiText);
log.debug("completed converting wiki protocol to wiki text");
return replacedWikiText.toString();
}
public void resolveLinkText(Long currentAreaNumber, Map<String, WikiLink> links, String linkText) {
// Don't resolve twice
if (links.containsKey(linkText)) return;
log.debug("trying to resolve link text: " + linkText);
Matcher wikiProtocolMatcher = Pattern.compile(REGEX_WIKI_PROTOCOL).matcher(linkText.trim());
Matcher knownProtocolMatcher = Pattern.compile(REGEX_KNOWN_PROTOCOL).matcher(linkText.trim());
Matcher customProtocolMatcher = Pattern.compile(REGEX_CUSTOM_PROTOCOL).matcher(linkText.trim());
WikiLink wikiLink;
// Check if its a common protocol
if (knownProtocolMatcher.find()) {
wikiLink = new WikiLink(false, true);
wikiLink.setUrl(linkText);
wikiLink.setDescription(linkText);
log.debug("link resolved to known protocol: " + linkText);
// Check if it is a wiki protocol
} else if (wikiProtocolMatcher.find()) {
// Find the node by PK
WikiFile file = wikiNodeDAO.findWikiFile(Long.valueOf(wikiProtocolMatcher.group(1)));
String fragment = wikiProtocolMatcher.group(2);
if (file != null) {
wikiLink = new WikiLink(false, false);
wikiLink.setFile(file);
wikiLink.setFragment(fragment);
wikiLink.setDescription(file.getName());
log.debug("link text resolved to existing node: " + file + " and fragment: " + fragment);
} else {
// Can't do anything, [=>wiki://123] no longer exists
wikiLink = new WikiLink(true, false);
wikiLink.setUrl(BROKENLINK_URL);
wikiLink.setDescription(BROKENLINK_DESCRIPTION);
log.debug("link tet could not be resolved: " + linkText);
}
// Check if it is a custom protocol
} else if (customProtocolMatcher.find()) {
if (linkProtocolMap.containsKey(customProtocolMatcher.group(1))) {
LinkProtocol protocol = linkProtocolMap.get(customProtocolMatcher.group(1));
wikiLink = new WikiLink(false, true);
wikiLink.setUrl(protocol.getRealLink(customProtocolMatcher.group(2)));
wikiLink.setDescription(protocol.getPrefix() + "://" + customProtocolMatcher.group(2));
log.debug("link text resolved to custom protocol: " + linkText);
} else {
wikiLink = new WikiLink(true, false);
wikiLink.setUrl(BROKENLINK_URL);
wikiLink.setDescription(BROKENLINK_DESCRIPTION);
log.debug("link text resolved to non-existant custom protocol: " + linkText);
}
// 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
WikiFile file = null;
String fragment = null;
Matcher linkTextMatcher = getCrossAreaMatcher(linkText);
if (linkTextMatcher != null) {
file = resolve(currentAreaNumber, linkTextMatcher.group(1), linkTextMatcher.group(2));
fragment = linkTextMatcher.group(3);
} else {
linkTextMatcher = getAreaMatcher(linkText);
if (linkTextMatcher != null) {
file = resolve(currentAreaNumber, null, linkTextMatcher.group(1));
fragment = linkTextMatcher.group(2);
}
}
if (file!=null) {
wikiLink = new WikiLink(false, false);
wikiLink.setFile(file);
wikiLink.setFragment(fragment);
wikiLink.setDescription(file.getName());
// Indicate that caller should update the wiki text that contains this link
wikiLink.setRequiresUpdating(true);
log.debug("link text resolved (needs updating to wiki protocol): " + file + " and fragment: " + fragment);
} 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);
log.debug("could not resolve link: " + linkText);
}
}
links.put(linkText, wikiLink);
}
public Long resolveWikiDirectoryId(Long currentAreaNumber, String linkText) {
WikiFile f = resolveWikiFile(currentAreaNumber, linkText);
return f != null ? f.getParent().getId() : null;
}
public Long resolveWikiDocumentId(Long currentAreaNumber, String linkText) {
WikiFile f = resolveWikiFile(currentAreaNumber, linkText);
return f != null ? f.getId() : null;
}
private WikiFile resolveWikiFile(Long currentAreaNumber, String linkText) {
if (linkText == null || linkText.length() == 0) return null;
Map<String, WikiLink> resolvedLinks = new HashMap<String, WikiLink>();
resolveLinkText(currentAreaNumber, resolvedLinks, linkText);
WikiLink resolvedLink = resolvedLinks.get(linkText);
if (resolvedLink.isBroken() || resolvedLink.getFile().getId() == null) {
return null;
} else {
return resolvedLink.getFile();
}
}
private Matcher getCrossAreaMatcher(String linkText) {
Matcher matcher = Pattern.compile(REGEX_WIKILINK_CROSSAREA).matcher(linkText);
return matcher.find() ? matcher : null;
}
private Matcher getAreaMatcher(String linkText) {
Matcher matcher = Pattern.compile(REGEX_NODE_NAME_FRAGMENT).matcher(linkText);
return matcher.find() ? matcher : null;
}
private WikiFile resolve(Long currentAreaNumber, String areaName, String nodeName) {
log.debug("trying to resolve, current area " + currentAreaNumber + ", search in area '" + areaName + "' for node name: " + nodeName);
if (areaName != null) {
WikiNode crossLinkArea = wikiNodeDAO.findArea(WikiUtil.convertToWikiName(areaName));
if (crossLinkArea != null)
return wikiNodeDAO.findWikiFileInArea(crossLinkArea.getAreaNumber(), WikiUtil.convertToWikiName(nodeName));
}
return wikiNodeDAO.findWikiFileInArea(currentAreaNumber, WikiUtil.convertToWikiName(nodeName));
}
}