Hivatkozás megjelenítése
A Confluence-ben az oldalak és blogok létrehozása után a megadott címből generálódik az elérési út, pl: az "Ekezet nelkuli szoveg"-hez a "/display/COM/Ekezet+nelkuli+szoveg" olvasható elérés. Ez egészen addig jól működik, amíg rövid szöveget és angol karaktereket (pontosabban ASCII-t) használunk. A magyar ékezeteknél az elérési út a fenti könnyen olvasható megoldástól eltérően nem a cím, hanem csak egy pageId alapján tölti be a hivatkozott oldalt, pl: az "Ékezetes szöveg"-nél "/pages/viewpage.action?pageId=123456". Ennek az oldal használata közben nincs különösebb jelentősége, de ha linkelnénk egy külső oldalra vagy e-mailben küldjük, akkor szebb és olvashatóbb, ha látszik a cím (és a space: COM) is. Publikus szervereknél a keresési találatokhoz szükséges indexelés is jobb lehet egy olvasható linkkel (pl: Google találatok).

Az ékezetek normalizálása
A Page és a BlogPost osztály is az AbstractPage-ből származik, aminek a "getUrlPath()" metódusa adja azt a hivatkozást, amit a Confluence oldalakon láthatunk. Magát az URL generálást az innen meghívott GeneralUtil.getPageUrl(AbstractPage page) metódusa végzi két egymástól eltérő módon:
- toDisplayUrl: ha a cím (title) megfelelő és az adott oldal a legkésőbbi, akkor a "szép" URL-t adja
- getIdBasedPageUrl: ha ez nem teljesük, akkor a pageId szerintit
A feltételeket az "isSafeTitleForUrl()"-ben találjuk, melyek szerint:
- nem lehet a cím üres és 150 karakternél hosszabb
- nem végződhet írásjellel
- nem tartalmazhat illegális karaktereket: '+', '?', '%', '&', '"', '/', '\\', ';'
- nem lehet benne nem ASCII karakter
A magyar ékezetek pageId szerinti kezelését ez utóbbi okozza. A problémát a cím normalizálásával oldhatjuk meg:
private static String decompose(String input) {
return Normalizer.normalize(input, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
}
Ékezetes oldal megjelenítése
A linkek beszédesé alakítása csak a feladat egyik része, mert ha így kattintunk rájuk, akkor nem tölti be a kívánt oldalt (PageNotFoundAction). Ennek oka, hogy a request-ek feldolgozásánál (PageAwareInterceptor) is két módon történik az adott oldal keresése: pageId vagy cím (+ space) alapján. A gondot az okozza, hogy az ékezetek nélküli linkből kivett cím (title) alapján nem lesz egyezés az adatbázisban az ékezetes címre. A legegyszerűbb megoldás, ha az ilyen normalizált URL-ek mellé betesszük a pageId-t is, így beszédes link mellett az oldal is egyértelműen azonosítható. Ennél szebb lenne, ha magát a DAO-t írnánk át, úgy hogy megtalálja az ékezetes rekordokat is (az ékezet nélküli címmel), de ezt majd a Confluence fejlesztők megoldják :)
GeneralUtil.java részlet
public static String getPageUrl(AbstractPage page) {
if (page == null || (page.getOriginalVersion() == null && page.getSpace() == null)) {
return "";
}
String title = page.getTitle();
// only use simple/nice page url if the page does not contain:
// - non-ASCII characters
// - '+' or '-' characters because these can be picked up by the insert and stripe through filters
// - double quotes (") because orion doesn't play nicely with them (CONF-1287)
// - ends in punctuation (CONFDEV-3995)
if (isSafeTitleForUrl(title) && page.isLatestVersion()) {
return toDisplayUrl(page);
} else if (isSafeTitleForNormalize(title) && page.isLatestVersion()) {
return toDisplayNormalizedUrl(page);
} else {
return getIdBasedPageUrl(page);
}
}
/**
* Get page URL that is id based (i.e. in the format /pages/viewpage.action?pageId=<pageId>)
* @param page the page to generate a url for
* @return page URL that is id based
*/
public static String getIdBasedPageUrl(AbstractPage page) {
if (page == null) {
return "";
}
return "/pages/viewpage.action?pageId=" + page.getId();
}
/**
* Is "title" something we can safely put in a /foo/bar/title URL? Or should we reference this thing by ID
* just to be safe?
* @param title The title to check
* @return True of the title can be put in a URL, false otherwise
*/
public static boolean isSafeTitleForUrl(String title) {
if (!isSafeTitleForNormalize(title)) {
return false;
}
for (int i = 0; i < title.length(); ++i) {
char c = title.charAt(i);
if (!isAscii(c))
return false;
}
return true;
}
private static boolean isSafeTitleForNormalize(String title) {
if (StringUtils.isEmpty(title) || title.length() >= 150) {
return false;
}
if (ENDS_WITH_PUNCTUATION.matcher(title).find()) {
return false;
}
if (".".equals(title)) {
return false;
}
for (int i = 0; i < title.length(); ++i) {
char c = title.charAt(i);
if (ILLEGAL_URL_TITLE_CHARS.contains(c)) {
return false;
}
}
return true;
}
/**
* This method is only ever called when we know that the page title consists of
* ASCII characters, so we only need to single-encode it, not double-encode.
* @param page The page to get the URL of
* @return The URL for the given page object
*/
private static String toDisplayUrl(AbstractPage page) {
return toDisplayUrl(page, false);
}
private static String toDisplayNormalizedUrl(AbstractPage page) {
return toDisplayUrl(page, true) + "?pageId=" + page.getId();
}
private static String toDisplayUrl(AbstractPage page, boolean normalize) {
StringBuilder displayUrl = new StringBuilder("/display/");
displayUrl.append(GeneralUtil.urlEncode(page.getSpace().getKey()));
displayUrl.append("/");
if ("blogpost".equals(page.getType())) {
//Why shouldn't a blog have any creation date? It happens in my case... Maybe a bug or so!
if (page.getCreationDate() != null) {
displayUrl.append(new SimpleDateFormat("yyyy/MM/dd").format(page.getCreationDate()));
displayUrl.append("/");
}
}
String title = normalize ? decompose(page.getTitle()) : page.getTitle();
displayUrl.append(urlEncode(title));
return displayUrl.toString();
}
private static String decompose(String input) {
return Normalizer.normalize(input, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
}
Tesztelés
TestGeneralUtil.java részlet
public void testNonAsciiPageUrl() {
final String spaceKey = "HUN";
final String pageTitle= "Ékezetes szöveg";
Space space = new Space();
space.setKey(spaceKey);
Page page = new Page();
page.setSpace(space);
page.setTitle(pageTitle);
assertEquals("/display/" + spaceKey + "/Ekezetes+szoveg?pageId=0", GeneralUtil.getPageUrl(page));
}
A végeredmény

A meglévő 2 megoldás mellé létrehoztunk egy harmadik típusú link generálást. Az így kialakított kompromisszumos megoldás (beszédes hivatkozás pageId-val kiegészítve):
/display/COM/Uj+szoveg+ekezettel?pageId=1081346
