[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