001
014
015 package com.liferay.portlet.wiki.util;
016
017 import com.liferay.portal.kernel.configuration.Filter;
018 import com.liferay.portal.kernel.exception.PortalException;
019 import com.liferay.portal.kernel.exception.SystemException;
020 import com.liferay.portal.kernel.io.unsync.UnsyncStringReader;
021 import com.liferay.portal.kernel.log.Log;
022 import com.liferay.portal.kernel.log.LogFactoryUtil;
023 import com.liferay.portal.kernel.portlet.LiferayPortletURL;
024 import com.liferay.portal.kernel.search.Document;
025 import com.liferay.portal.kernel.search.Field;
026 import com.liferay.portal.kernel.search.Hits;
027 import com.liferay.portal.kernel.util.DiffHtmlUtil;
028 import com.liferay.portal.kernel.util.GetterUtil;
029 import com.liferay.portal.kernel.util.HtmlUtil;
030 import com.liferay.portal.kernel.util.HttpUtil;
031 import com.liferay.portal.kernel.util.InstancePool;
032 import com.liferay.portal.kernel.util.ListUtil;
033 import com.liferay.portal.kernel.util.OrderByComparator;
034 import com.liferay.portal.kernel.util.ParamUtil;
035 import com.liferay.portal.kernel.util.PropsKeys;
036 import com.liferay.portal.kernel.util.StringBundler;
037 import com.liferay.portal.kernel.util.StringPool;
038 import com.liferay.portal.kernel.util.StringUtil;
039 import com.liferay.portal.kernel.util.Validator;
040 import com.liferay.portal.security.permission.ActionKeys;
041 import com.liferay.portal.security.permission.PermissionChecker;
042 import com.liferay.portal.theme.ThemeDisplay;
043 import com.liferay.portal.util.PortalUtil;
044 import com.liferay.portal.util.PropsUtil;
045 import com.liferay.portal.util.PropsValues;
046 import com.liferay.portal.util.WebKeys;
047 import com.liferay.portlet.PortletURLUtil;
048 import com.liferay.portlet.documentlibrary.model.DLFileEntry;
049 import com.liferay.portlet.documentlibrary.service.DLFileEntryLocalServiceUtil;
050 import com.liferay.portlet.messageboards.model.MBMessage;
051 import com.liferay.portlet.messageboards.service.MBMessageLocalServiceUtil;
052 import com.liferay.portlet.wiki.PageContentException;
053 import com.liferay.portlet.wiki.WikiFormatException;
054 import com.liferay.portlet.wiki.engines.WikiEngine;
055 import com.liferay.portlet.wiki.model.WikiNode;
056 import com.liferay.portlet.wiki.model.WikiPage;
057 import com.liferay.portlet.wiki.model.WikiPageDisplay;
058 import com.liferay.portlet.wiki.service.WikiNodeLocalServiceUtil;
059 import com.liferay.portlet.wiki.service.WikiPageLocalServiceUtil;
060 import com.liferay.portlet.wiki.service.permission.WikiNodePermission;
061 import com.liferay.portlet.wiki.util.comparator.PageCreateDateComparator;
062 import com.liferay.portlet.wiki.util.comparator.PageTitleComparator;
063 import com.liferay.portlet.wiki.util.comparator.PageVersionComparator;
064 import com.liferay.util.ContentUtil;
065
066 import java.io.IOException;
067
068 import java.util.ArrayList;
069 import java.util.Arrays;
070 import java.util.Collections;
071 import java.util.HashSet;
072 import java.util.Iterator;
073 import java.util.List;
074 import java.util.Map;
075 import java.util.Set;
076 import java.util.concurrent.ConcurrentHashMap;
077 import java.util.regex.Matcher;
078 import java.util.regex.Pattern;
079
080 import javax.portlet.PortletPreferences;
081 import javax.portlet.PortletRequest;
082 import javax.portlet.PortletURL;
083 import javax.portlet.RenderRequest;
084 import javax.portlet.RenderResponse;
085
086
090 public class WikiUtil {
091
092 public static String convert(
093 WikiPage page, PortletURL viewPageURL, PortletURL editPageURL,
094 String attachmentURLPrefix)
095 throws PageContentException, WikiFormatException {
096
097 return _instance._convert(
098 page, viewPageURL, editPageURL, attachmentURLPrefix);
099 }
100
101 public static String diffHtml(
102 WikiPage sourcePage, WikiPage targetPage, PortletURL viewPageURL,
103 PortletURL editPageURL, String attachmentURLPrefix)
104 throws Exception {
105
106 String sourceContent = StringPool.BLANK;
107 String targetContent = StringPool.BLANK;
108
109 if (sourcePage != null) {
110 sourceContent = convert(
111 sourcePage, viewPageURL, editPageURL, attachmentURLPrefix);
112 }
113
114 if (targetPage != null) {
115 targetContent = convert(
116 targetPage, viewPageURL, editPageURL, attachmentURLPrefix);
117 }
118
119 return DiffHtmlUtil.diff(
120 new UnsyncStringReader(sourceContent),
121 new UnsyncStringReader(targetContent));
122 }
123
124 public static List<WikiPage> filterOrphans(List<WikiPage> pages)
125 throws PortalException {
126
127 List<Map<String, Boolean>> pageTitles =
128 new ArrayList<Map<String, Boolean>>();
129
130 for (WikiPage page : pages) {
131 pageTitles.add(WikiCacheUtil.getOutgoingLinks(page));
132 }
133
134 Set<WikiPage> notOrphans = new HashSet<WikiPage>();
135
136 for (WikiPage page : pages) {
137 for (Map<String, Boolean> pageTitle : pageTitles) {
138 String pageTitleLowerCase = page.getTitle();
139
140 pageTitleLowerCase = pageTitleLowerCase.toLowerCase();
141
142 if (pageTitle.get(pageTitleLowerCase) != null) {
143 notOrphans.add(page);
144
145 break;
146 }
147 }
148 }
149
150 List<WikiPage> orphans = new ArrayList<WikiPage>();
151
152 for (WikiPage page : pages) {
153 if (!notOrphans.contains(page)) {
154 orphans.add(page);
155 }
156 }
157
158 orphans = ListUtil.sort(orphans);
159
160 return orphans;
161 }
162
163 public static String getAttachmentURLPrefix(
164 String mainPath, long plid, long nodeId, String title) {
165
166 StringBundler sb = new StringBundler(8);
167
168 sb.append(mainPath);
169 sb.append("/wiki/get_page_attachment?p_l_id=");
170 sb.append(plid);
171 sb.append("&nodeId=");
172 sb.append(nodeId);
173 sb.append("&title=");
174 sb.append(HttpUtil.encodeURL(title));
175 sb.append("&fileName=");
176
177 return sb.toString();
178 }
179
180 public static String getEditPage(String format) {
181 return _instance._getEditPage(format);
182 }
183
184 public static String getEmailFromAddress(
185 PortletPreferences preferences, long companyId)
186 throws SystemException {
187
188 return PortalUtil.getEmailFromAddress(
189 preferences, companyId, PropsValues.WIKI_EMAIL_FROM_ADDRESS);
190 }
191
192 public static String getEmailFromName(
193 PortletPreferences preferences, long companyId)
194 throws SystemException {
195
196 return PortalUtil.getEmailFromName(
197 preferences, companyId, PropsValues.WIKI_EMAIL_FROM_NAME);
198 }
199
200 public static String getEmailPageAddedBody(PortletPreferences preferences) {
201 String emailPageAddedBody = preferences.getValue(
202 "emailPageAddedBody", StringPool.BLANK);
203
204 if (Validator.isNotNull(emailPageAddedBody)) {
205 return emailPageAddedBody;
206 }
207 else {
208 return ContentUtil.get(
209 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_BODY));
210 }
211 }
212
213 public static boolean getEmailPageAddedEnabled(
214 PortletPreferences preferences) {
215
216 String emailPageAddedEnabled = preferences.getValue(
217 "emailPageAddedEnabled", StringPool.BLANK);
218
219 if (Validator.isNotNull(emailPageAddedEnabled)) {
220 return GetterUtil.getBoolean(emailPageAddedEnabled);
221 }
222 else {
223 return GetterUtil.getBoolean(
224 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_ENABLED));
225 }
226 }
227
228 public static String getEmailPageAddedSignature(
229 PortletPreferences preferences) {
230
231 String emailPageAddedSignature = preferences.getValue(
232 "emailPageAddedSignature", StringPool.BLANK);
233
234 if (Validator.isNotNull(emailPageAddedSignature)) {
235 return emailPageAddedSignature;
236 }
237 else {
238 return ContentUtil.get(
239 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_SIGNATURE));
240 }
241 }
242
243 public static String getEmailPageAddedSubject(
244 PortletPreferences preferences) {
245
246 String emailPageAddedSubject = preferences.getValue(
247 "emailPageAddedSubject", StringPool.BLANK);
248
249 if (Validator.isNotNull(emailPageAddedSubject)) {
250 return emailPageAddedSubject;
251 }
252 else {
253 return ContentUtil.get(
254 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_ADDED_SUBJECT));
255 }
256 }
257
258 public static String getEmailPageUpdatedBody(
259 PortletPreferences preferences) {
260
261 String emailPageUpdatedBody = preferences.getValue(
262 "emailPageUpdatedBody", StringPool.BLANK);
263
264 if (Validator.isNotNull(emailPageUpdatedBody)) {
265 return emailPageUpdatedBody;
266 }
267 else {
268 return ContentUtil.get(
269 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_BODY));
270 }
271 }
272
273 public static boolean getEmailPageUpdatedEnabled(
274 PortletPreferences preferences) {
275
276 String emailPageUpdatedEnabled = preferences.getValue(
277 "emailPageUpdatedEnabled", StringPool.BLANK);
278
279 if (Validator.isNotNull(emailPageUpdatedEnabled)) {
280 return GetterUtil.getBoolean(emailPageUpdatedEnabled);
281 }
282 else {
283 return GetterUtil.getBoolean(
284 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_ENABLED));
285 }
286 }
287
288 public static String getEmailPageUpdatedSignature(
289 PortletPreferences preferences) {
290
291 String emailPageUpdatedSignature = preferences.getValue(
292 "emailPageUpdatedSignature", StringPool.BLANK);
293
294 if (Validator.isNotNull(emailPageUpdatedSignature)) {
295 return emailPageUpdatedSignature;
296 }
297 else {
298 return ContentUtil.get(
299 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_SIGNATURE));
300 }
301 }
302
303 public static String getEmailPageUpdatedSubject(
304 PortletPreferences preferences) {
305
306 String emailPageUpdatedSubject = preferences.getValue(
307 "emailPageUpdatedSubject", StringPool.BLANK);
308
309 if (Validator.isNotNull(emailPageUpdatedSubject)) {
310 return emailPageUpdatedSubject;
311 }
312 else {
313 return ContentUtil.get(
314 PropsUtil.get(PropsKeys.WIKI_EMAIL_PAGE_UPDATED_SUBJECT));
315 }
316 }
317
318 public static List<Object> getEntries(Hits hits) {
319 List<Object> entries = new ArrayList<Object>();
320
321 for (Document document : hits.getDocs()) {
322 String entryClassName = GetterUtil.getString(
323 document.get(Field.ENTRY_CLASS_NAME));
324 long entryClassPK = GetterUtil.getLong(
325 document.get(Field.ENTRY_CLASS_PK));
326
327 try {
328 Object obj = null;
329
330 if (entryClassName.equals(DLFileEntry.class.getName())) {
331 long classPK = GetterUtil.getLong(
332 document.get(Field.CLASS_PK));
333
334 WikiPageLocalServiceUtil.getPage(classPK);
335
336 obj = DLFileEntryLocalServiceUtil.getDLFileEntry(
337 entryClassPK);
338 }
339 else if (entryClassName.equals(MBMessage.class.getName())) {
340 long classPK = GetterUtil.getLong(
341 document.get(Field.CLASS_PK));
342
343 WikiPageLocalServiceUtil.getPage(classPK);
344
345 obj = MBMessageLocalServiceUtil.getMessage(entryClassPK);
346 }
347 else if (entryClassName.equals(WikiPage.class.getName())) {
348 obj = WikiPageLocalServiceUtil.getPage(entryClassPK);
349 }
350
351 entries.add(obj);
352 }
353 catch (Exception e) {
354 if (_log.isWarnEnabled()) {
355 _log.warn(
356 "Wiki search index is stale and contains entry " +
357 "{className=" + entryClassName + ", classPK=" +
358 entryClassPK + "}");
359 }
360 }
361 }
362
363 return entries;
364 }
365
366 public static WikiNode getFirstNode(PortletRequest portletRequest)
367 throws PortalException, SystemException {
368
369 ThemeDisplay themeDisplay = (ThemeDisplay)portletRequest.getAttribute(
370 WebKeys.THEME_DISPLAY);
371 long groupId = themeDisplay.getScopeGroupId();
372 PermissionChecker permissionChecker =
373 themeDisplay.getPermissionChecker();
374
375 List<WikiNode> nodes = WikiNodeLocalServiceUtil.getNodes(groupId);
376
377 PortletPreferences preferences = portletRequest.getPreferences();
378 String[] visibleNodeNames = StringUtil.split(
379 preferences.getValue("visibleNodes", null));
380 nodes = orderNodes(nodes, visibleNodeNames);
381
382 String[] hiddenNodes = StringUtil.split(
383 preferences.getValue("hiddenNodes", StringPool.BLANK));
384 Arrays.sort(hiddenNodes);
385
386 for (WikiNode node : nodes) {
387 if ((Arrays.binarySearch(hiddenNodes, node.getName()) < 0) &&
388 WikiNodePermission.contains(
389 permissionChecker, node, ActionKeys.VIEW)) {
390
391 return node;
392 }
393 }
394
395 return null;
396 }
397
398 public static String getFormattedContent(
399 RenderRequest renderRequest, RenderResponse renderResponse,
400 WikiPage wikiPage, PortletURL viewPageURL, PortletURL editPageURL,
401 String title, boolean preview)
402 throws Exception {
403
404 ThemeDisplay themeDisplay = (ThemeDisplay)renderRequest.getAttribute(
405 WebKeys.THEME_DISPLAY);
406
407 double version = ParamUtil.getDouble(renderRequest, "version");
408
409 PortletURL curViewPageURL = PortletURLUtil.clone(
410 viewPageURL, renderResponse);
411 PortletURL curEditPageURL = PortletURLUtil.clone(
412 editPageURL, renderResponse);
413
414 StringBundler sb = new StringBundler();
415
416 sb.append(themeDisplay.getPathMain());
417 sb.append("/wiki/get_page_attachment?p_l_id=");
418 sb.append(themeDisplay.getPlid());
419 sb.append("&nodeId=");
420 sb.append(wikiPage.getNodeId());
421 sb.append("&title=");
422 sb.append(HttpUtil.encodeURL(wikiPage.getTitle()));
423 sb.append("&fileName=");
424
425 String attachmentURLPrefix = sb.toString();
426
427 if (!preview && (version == 0)) {
428 WikiPageDisplay pageDisplay = WikiCacheUtil.getDisplay(
429 wikiPage.getNodeId(), title, curViewPageURL, curEditPageURL,
430 attachmentURLPrefix);
431
432 if (pageDisplay != null) {
433 return pageDisplay.getFormattedContent();
434 }
435 }
436
437 return convert(
438 wikiPage, curViewPageURL, curEditPageURL, attachmentURLPrefix);
439 }
440
441 public static String getHelpPage(String format) {
442 return _instance._getHelpPage(format);
443 }
444
445 public static String getHelpURL(String format) {
446 return _instance._getHelpURL(format);
447 }
448
449 public static Map<String, Boolean> getLinks(WikiPage page)
450 throws PageContentException {
451
452 return _instance._getLinks(page);
453 }
454
455 public static List<String> getNodeNames(List<WikiNode> nodes) {
456 List<String> nodeNames = new ArrayList<String>(nodes.size());
457
458 for (WikiNode node : nodes) {
459 nodeNames.add(node.getName());
460 }
461
462 return nodeNames;
463 }
464
465 public static List<WikiNode> getNodes(
466 List<WikiNode> nodes, String[] hiddenNodes,
467 PermissionChecker permissionChecker) {
468
469 nodes = ListUtil.copy(nodes);
470
471 Arrays.sort(hiddenNodes);
472
473 Iterator<WikiNode> itr = nodes.iterator();
474
475 while (itr.hasNext()) {
476 WikiNode node = itr.next();
477
478 if (!(Arrays.binarySearch(hiddenNodes, node.getName()) < 0) ||
479 !WikiNodePermission.contains(
480 permissionChecker, node, ActionKeys.VIEW)) {
481
482 itr.remove();
483 }
484 }
485
486 return nodes;
487 }
488
489 public static OrderByComparator getPageOrderByComparator(
490 String orderByCol, String orderByType) {
491
492 boolean orderByAsc = false;
493
494 if (orderByType.equals("asc")) {
495 orderByAsc = true;
496 }
497
498 OrderByComparator orderByComparator = null;
499
500 if (orderByCol.equals("modifiedDate")) {
501 orderByComparator = new PageCreateDateComparator(orderByAsc);
502 }
503 else if (orderByCol.equals("title")) {
504 orderByComparator = new PageTitleComparator(orderByAsc);
505 }
506 else if (orderByCol.equals("version")) {
507 orderByComparator = new PageVersionComparator(orderByAsc);
508 }
509
510 return orderByComparator;
511 }
512
513 public static List<WikiNode> orderNodes(
514 List<WikiNode> nodes, String[] visibleNodeNames) {
515
516 if ((visibleNodeNames == null) || (visibleNodeNames.length == 0)) {
517 return nodes;
518 }
519
520 nodes = ListUtil.copy(nodes);
521
522 List<WikiNode> orderedNodes = new ArrayList<WikiNode>(nodes.size());
523
524 for (String visibleNodeName : visibleNodeNames) {
525 for (WikiNode node : nodes) {
526 if (node.getName().equals(visibleNodeName)) {
527 orderedNodes.add(node);
528
529 nodes.remove(node);
530
531 break;
532 }
533 }
534 }
535
536 orderedNodes.addAll(nodes);
537
538 return orderedNodes;
539 }
540
541 public static String processContent(String content) {
542 content = content.replaceAll("</p>", "</p>\n");
543 content = content.replaceAll("</br>", "</br>\n");
544 content = content.replaceAll("</div>", "</div>\n");
545
546 return content;
547 }
548
549 public static boolean validate(long nodeId, String content, String format)
550 throws WikiFormatException {
551
552 return _instance._validate(nodeId, content, format);
553 }
554
555 private String _convert(
556 WikiPage page, PortletURL viewPageURL, PortletURL editPageURL,
557 String attachmentURLPrefix)
558 throws PageContentException, WikiFormatException {
559
560 LiferayPortletURL liferayViewPageURL = (LiferayPortletURL)viewPageURL;
561 LiferayPortletURL liferayEditPageURL = (LiferayPortletURL)editPageURL;
562
563 WikiEngine engine = _getEngine(page.getFormat());
564
565 String content = engine.convert(
566 page, viewPageURL, editPageURL, attachmentURLPrefix);
567
568 String editPageURLString = StringPool.BLANK;
569
570 if (editPageURL != null) {
571 liferayEditPageURL.setParameter("title", "__REPLACEMENT__", false);
572
573 editPageURLString = editPageURL.toString();
574
575 editPageURLString = StringUtil.replace(
576 editPageURLString, "__REPLACEMENT__", "$1");
577 }
578
579 Matcher matcher = _editPageURLPattern.matcher(content);
580
581 content = _convertURLs(editPageURLString, matcher);
582
583 String viewPageURLString = StringPool.BLANK;
584
585 if (viewPageURL != null) {
586 liferayViewPageURL.setParameter("title", "__REPLACEMENT__", false);
587
588 viewPageURLString = viewPageURL.toString();
589
590 viewPageURLString = StringUtil.replace(
591 viewPageURLString, "__REPLACEMENT__", "$1");
592 }
593
594 matcher = _viewPageURLPattern.matcher(content);
595
596 content = _convertURLs(viewPageURLString, matcher);
597
598 content = _replaceAttachments(
599 content, page.getTitle(), attachmentURLPrefix);
600
601 return content;
602 }
603
604 private String _convertURLs(String url, Matcher matcher) {
605 StringBuffer sb = new StringBuffer();
606
607 while (matcher.find()) {
608 String replacement = null;
609
610 if (matcher.groupCount() >= 1) {
611 String encodedTitle = HttpUtil.encodeURL(
612 HtmlUtil.unescape(matcher.group(1)));
613
614 replacement = url.replace("$1", encodedTitle);
615 }
616 else {
617 replacement = url;
618 }
619
620 matcher.appendReplacement(sb, replacement);
621 }
622
623 return matcher.appendTail(sb).toString();
624 }
625
626 private String _getEditPage(String format) {
627 return PropsUtil.get(
628 PropsKeys.WIKI_FORMATS_EDIT_PAGE, new Filter(format));
629 }
630
631 private WikiEngine _getEngine(String format) throws WikiFormatException {
632 WikiEngine engine = _engines.get(format);
633
634 if (engine != null) {
635 return engine;
636 }
637
638 synchronized (_engines) {
639 engine = _engines.get(format);
640
641 if (engine != null) {
642 return engine;
643 }
644
645 try {
646 String engineClass = PropsUtil.get(
647 PropsKeys.WIKI_FORMATS_ENGINE, new Filter(format));
648
649 if (engineClass == null) {
650 throw new WikiFormatException(format);
651 }
652
653 if (!InstancePool.contains(engineClass)) {
654 engine = (WikiEngine)InstancePool.get(engineClass);
655
656 engine.setMainConfiguration(
657 _readConfigurationFile(
658 PropsKeys.WIKI_FORMATS_CONFIGURATION_MAIN, format));
659
660 engine.setInterWikiConfiguration(
661 _readConfigurationFile(
662 PropsKeys.WIKI_FORMATS_CONFIGURATION_INTERWIKI,
663 format));
664 }
665 else {
666 engine = (WikiEngine)InstancePool.get(engineClass);
667 }
668
669 _engines.put(format, engine);
670
671 return engine;
672 }
673 catch (Exception e) {
674 throw new WikiFormatException(e);
675 }
676 }
677 }
678
679 private String _getHelpPage(String format) {
680 return PropsUtil.get(
681 PropsKeys.WIKI_FORMATS_HELP_PAGE, new Filter(format));
682 }
683
684 private String _getHelpURL(String format) {
685 return PropsUtil.get(
686 PropsKeys.WIKI_FORMATS_HELP_URL, new Filter(format));
687 }
688
689 private Map<String, Boolean> _getLinks(WikiPage page)
690 throws PageContentException {
691
692 try {
693 return _getEngine(page.getFormat()).getOutgoingLinks(page);
694 }
695 catch (WikiFormatException wfe) {
696 return Collections.emptyMap();
697 }
698 }
699
700 private String _readConfigurationFile(String propertyName, String format)
701 throws IOException {
702
703 ClassLoader classLoader = getClass().getClassLoader();
704
705 String configurationFile = PropsUtil.get(
706 propertyName, new Filter(format));
707
708 if (Validator.isNotNull(configurationFile)) {
709 return HttpUtil.URLtoString(
710 classLoader.getResource(configurationFile));
711 }
712 else {
713 return StringPool.BLANK;
714 }
715 }
716
717 private String _replaceAttachments(
718 String content, String title, String attachmentURLPrefix) {
719
720 content = StringUtil.replace(content, "[$WIKI_PAGE_NAME$]", title);
721
722 content = StringUtil.replace(
723 content, "[$ATTACHMENT_URL_PREFIX$]", attachmentURLPrefix);
724
725 return content;
726 }
727
728 private boolean _validate(long nodeId, String content, String format)
729 throws WikiFormatException {
730
731 return _getEngine(format).validate(nodeId, content);
732 }
733
734 private static Log _log = LogFactoryUtil.getLog(WikiUtil.class);
735
736 private static WikiUtil _instance = new WikiUtil();
737
738 private static Pattern _editPageURLPattern = Pattern.compile(
739 "\\[\\$BEGIN_PAGE_TITLE_EDIT\\$\\](.*?)" +
740 "\\[\\$END_PAGE_TITLE_EDIT\\$\\]");
741 private static Pattern _viewPageURLPattern = Pattern.compile(
742 "\\[\\$BEGIN_PAGE_TITLE\\$\\](.*?)\\[\\$END_PAGE_TITLE\\$\\]");
743
744 private Map<String, WikiEngine> _engines =
745 new ConcurrentHashMap<String, WikiEngine>();
746
747 }