001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.tools.sourceformatter;
016    
017    import com.liferay.portal.kernel.util.PropertiesUtil;
018    import com.liferay.portal.kernel.util.PropsKeys;
019    import com.liferay.portal.kernel.util.StringBundler;
020    import com.liferay.portal.kernel.util.StringPool;
021    import com.liferay.portal.kernel.util.StringUtil;
022    import com.liferay.portal.kernel.util.Validator;
023    import com.liferay.portal.kernel.xml.Document;
024    import com.liferay.portal.kernel.xml.DocumentException;
025    import com.liferay.portal.kernel.xml.Element;
026    import com.liferay.portal.tools.ComparableRoute;
027    import com.liferay.util.ContentUtil;
028    
029    import java.io.File;
030    import java.io.IOException;
031    
032    import java.util.ArrayList;
033    import java.util.Arrays;
034    import java.util.Collections;
035    import java.util.List;
036    import java.util.Map;
037    import java.util.Properties;
038    import java.util.Set;
039    import java.util.TreeSet;
040    import java.util.regex.Matcher;
041    import java.util.regex.Pattern;
042    
043    /**
044     * @author Hugo Huijser
045     */
046    public class XMLSourceProcessor extends BaseSourceProcessor {
047    
048            public static String formatXML(String content) {
049                    String newContent = StringUtil.replace(content, "\"/>\n", "\" />\n");
050    
051                    while (true) {
052                            Matcher matcher = _commentPattern1.matcher(newContent);
053    
054                            if (matcher.find()) {
055                                    newContent = StringUtil.replaceFirst(
056                                            newContent, ">\n", ">\n\n", matcher.start());
057    
058                                    continue;
059                            }
060    
061                            matcher = _commentPattern2.matcher(newContent);
062    
063                            if (!matcher.find()) {
064                                    break;
065                            }
066    
067                            newContent = StringUtil.replaceFirst(
068                                    newContent, "-->\n", "-->\n\n", matcher.start());
069                    }
070    
071                    return newContent;
072            }
073    
074            protected String fixAntXMLProjectName(String fileName, String content) {
075                    int x = 0;
076    
077                    if (fileName.endsWith("-ext/build.xml")) {
078                            if (fileName.startsWith("ext/")) {
079                                    x = 4;
080                            }
081                    }
082                    else if (fileName.endsWith("-hook/build.xml")) {
083                            if (fileName.startsWith("hooks/")) {
084                                    x = 6;
085                            }
086                    }
087                    else if (fileName.endsWith("-layouttpl/build.xml")) {
088                            if (fileName.startsWith("layouttpl/")) {
089                                    x = 10;
090                            }
091                    }
092                    else if (fileName.endsWith("-portlet/build.xml")) {
093                            if (fileName.startsWith("portlets/")) {
094                                    x = 9;
095                            }
096                    }
097                    else if (fileName.endsWith("-theme/build.xml")) {
098                            if (fileName.startsWith("themes/")) {
099                                    x = 7;
100                            }
101                    }
102                    else if (fileName.endsWith("-web/build.xml") &&
103                                     !fileName.endsWith("/ext-web/build.xml")) {
104    
105                            if (fileName.startsWith("webs/")) {
106                                    x = 5;
107                            }
108                    }
109                    else {
110                            return content;
111                    }
112    
113                    int y = fileName.indexOf("/", x);
114    
115                    String correctProjectElementText =
116                            "<project name=\"" + fileName.substring(x, y) + "\"";
117    
118                    if (!content.contains(correctProjectElementText)) {
119                            x = content.indexOf("<project name=\"");
120    
121                            y = content.indexOf("\"", x) + 1;
122                            y = content.indexOf("\"", y) + 1;
123    
124                            content =
125                                    content.substring(0, x) + correctProjectElementText +
126                                            content.substring(y);
127    
128                            processErrorMessage(
129                                    fileName, fileName + " has an incorrect project name");
130                    }
131    
132                    return content;
133            }
134    
135            @Override
136            protected void format() throws Exception {
137                    String[] excludes = new String[] {
138                            "**\\.idea\\**", "**\\bin\\**", "**\\classes\\**"
139                    };
140                    String[] includes = new String[] {"**\\*.xml"};
141    
142                    Properties exclusions = getExclusionsProperties(
143                            "source_formatter_xml_exclusions.properties");
144    
145                    List<String> fileNames = getFileNames(excludes, includes);
146    
147                    for (String fileName : fileNames) {
148                            File file = new File(BASEDIR + fileName);
149    
150                            fileName = StringUtil.replace(
151                                    fileName, StringPool.BACK_SLASH, StringPool.SLASH);
152    
153                            if ((exclusions != null) &&
154                                    (exclusions.getProperty(fileName) != null)) {
155    
156                                    continue;
157                            }
158    
159                            String content = fileUtil.read(file);
160    
161                            String newContent = content;
162    
163                            if (!fileName.contains("/build")) {
164                                    newContent = trimContent(newContent, false);
165                            }
166    
167                            if (fileName.contains("/build") && !fileName.contains("/tools/")) {
168                                    newContent = formatAntXML(fileName, newContent);
169                            }
170                            else if (fileName.endsWith("structures.xml")) {
171                                    newContent = formatDDLStructuresXML(newContent);
172                            }
173                            else if (fileName.endsWith("routes.xml")) {
174                                    newContent = formatFriendlyURLRoutesXML(fileName, newContent);
175                            }
176                            else if ((portalSource &&
177                                              fileName.endsWith("/portlet-custom.xml")) ||
178                                             (!portalSource && fileName.endsWith("/portlet.xml"))) {
179    
180                                    newContent = formatPortletXML(newContent);
181                            }
182                            else if (portalSource && fileName.endsWith("/service.xml")) {
183                                    formatServiceXML(fileName, newContent);
184                            }
185                            else if (portalSource && fileName.endsWith("/struts-config.xml")) {
186                                    formatStrutsConfigXML(fileName, content);
187                            }
188                            else if (portalSource && fileName.endsWith("/tiles-defs.xml")) {
189                                    formatTilesDefsXML(fileName, content);
190                            }
191                            else if (portalSource && fileName.endsWith("WEB-INF/web.xml") ||
192                                             !portalSource && fileName.endsWith("/web.xml")) {
193    
194                                    newContent = formatWebXML(fileName, content);
195                            }
196    
197                            newContent = formatXML(newContent);
198    
199                            if (isAutoFix() && (newContent != null) &&
200                                    !content.equals(newContent)) {
201    
202                                    fileUtil.write(file, newContent);
203    
204                                    sourceFormatterHelper.printError(fileName, file);
205                            }
206                    }
207            }
208    
209            protected String formatAntXML(String fileName, String content)
210                    throws DocumentException, IOException {
211    
212                    String newContent = trimContent(content, true);
213    
214                    newContent = fixAntXMLProjectName(fileName, newContent);
215    
216                    Document document = saxReaderUtil.read(newContent);
217    
218                    Element rootElement = document.getRootElement();
219    
220                    String previousName = StringPool.BLANK;
221    
222                    List<Element> targetElements = rootElement.elements("target");
223    
224                    for (Element targetElement : targetElements) {
225                            String name = targetElement.attributeValue("name");
226    
227                            if (name.equals("Test")) {
228                                    name = name.toLowerCase();
229                            }
230    
231                            if (name.compareTo(previousName) < -1) {
232                                    processErrorMessage(
233                                            fileName, fileName + " has an unordered target " + name);
234    
235                                    break;
236                            }
237    
238                            previousName = name;
239                    }
240    
241                    return newContent;
242            }
243    
244            protected String formatDDLStructuresXML(String content)
245                    throws DocumentException, IOException {
246    
247                    Document document = saxReaderUtil.read(content);
248    
249                    Element rootElement = document.getRootElement();
250    
251                    rootElement.sortAttributes(true);
252    
253                    rootElement.sortElementsByChildElement("structure", "name");
254    
255                    List<Element> structureElements = rootElement.elements("structure");
256    
257                    for (Element structureElement : structureElements) {
258                            Element structureRootElement = structureElement.element("root");
259    
260                            structureRootElement.sortElementsByAttribute(
261                                    "dynamic-element", "name");
262    
263                            List<Element> dynamicElementElements =
264                                    structureRootElement.elements("dynamic-element");
265    
266                            for (Element dynamicElementElement : dynamicElementElements) {
267                                    Element metaDataElement = dynamicElementElement.element(
268                                            "meta-data");
269    
270                                    metaDataElement.sortElementsByAttribute("entry", "name");
271                            }
272                    }
273    
274                    return document.formattedString();
275            }
276    
277            protected String formatFriendlyURLRoutesXML(String fileName, String content)
278                    throws DocumentException, IOException {
279    
280                    Properties friendlyUrlRoutesSortExclusions = getExclusionsProperties(
281                            "source_formatter_friendly_url_routes_sort_exclusions.properties");
282    
283                    String excluded = null;
284    
285                    if (friendlyUrlRoutesSortExclusions != null) {
286                            excluded = friendlyUrlRoutesSortExclusions.getProperty(fileName);
287                    }
288    
289                    if (excluded != null) {
290                            return content;
291                    }
292    
293                    Document document = saxReaderUtil.read(content);
294    
295                    Element rootElement = document.getRootElement();
296    
297                    List<ComparableRoute> comparableRoutes =
298                            new ArrayList<ComparableRoute>();
299    
300                    for (Element routeElement : rootElement.elements("route")) {
301                            String pattern = routeElement.elementText("pattern");
302    
303                            ComparableRoute comparableRoute = new ComparableRoute(pattern);
304    
305                            for (Element generatedParameterElement :
306                                            routeElement.elements("generated-parameter")) {
307    
308                                    String name = generatedParameterElement.attributeValue("name");
309                                    String value = generatedParameterElement.getText();
310    
311                                    comparableRoute.addGeneratedParameter(name, value);
312                            }
313    
314                            for (Element ignoredParameterElement :
315                                            routeElement.elements("ignored-parameter")) {
316    
317                                    String name = ignoredParameterElement.attributeValue("name");
318    
319                                    comparableRoute.addIgnoredParameter(name);
320                            }
321    
322                            for (Element implicitParameterElement :
323                                            routeElement.elements("implicit-parameter")) {
324    
325                                    String name = implicitParameterElement.attributeValue("name");
326                                    String value = implicitParameterElement.getText();
327    
328                                    comparableRoute.addImplicitParameter(name, value);
329                            }
330    
331                            for (Element overriddenParameterElement :
332                                            routeElement.elements("overridden-parameter")) {
333    
334                                    String name = overriddenParameterElement.attributeValue("name");
335                                    String value = overriddenParameterElement.getText();
336    
337                                    comparableRoute.addOverriddenParameter(name, value);
338                            }
339    
340                            comparableRoutes.add(comparableRoute);
341                    }
342    
343                    Collections.sort(comparableRoutes);
344    
345                    StringBundler sb = new StringBundler();
346    
347                    sb.append("<?xml version=\"1.0\"?>\n");
348                    sb.append("<!DOCTYPE routes PUBLIC \"-//Liferay//DTD Friendly URL ");
349                    sb.append("Routes ");
350                    sb.append(mainReleaseVersion);
351                    sb.append("//EN\" \"http://www.liferay.com/dtd/");
352                    sb.append("liferay-friendly-url-routes_");
353                    sb.append(
354                            StringUtil.replace(
355                                    mainReleaseVersion, StringPool.PERIOD, StringPool.UNDERLINE));
356                    sb.append(".dtd\">\n\n<routes>\n");
357    
358                    for (ComparableRoute comparableRoute : comparableRoutes) {
359                            sb.append("\t<route>\n");
360                            sb.append("\t\t<pattern>");
361                            sb.append(comparableRoute.getPattern());
362                            sb.append("</pattern>\n");
363    
364                            Map<String, String> generatedParameters =
365                                    comparableRoute.getGeneratedParameters();
366    
367                            for (Map.Entry<String, String> entry :
368                                            generatedParameters.entrySet()) {
369    
370                                    sb.append("\t\t<generated-parameter name=\"");
371                                    sb.append(entry.getKey());
372                                    sb.append("\">");
373                                    sb.append(entry.getValue());
374                                    sb.append("</generated-parameter>\n");
375                            }
376    
377                            Set<String> ignoredParameters =
378                                    comparableRoute.getIgnoredParameters();
379    
380                            for (String entry : ignoredParameters) {
381                                    sb.append("\t\t<ignored-parameter name=\"");
382                                    sb.append(entry);
383                                    sb.append("\" />\n");
384                            }
385    
386                            Map<String, String> implicitParameters =
387                                    comparableRoute.getImplicitParameters();
388    
389                            for (Map.Entry<String, String> entry :
390                                            implicitParameters.entrySet()) {
391    
392                                    sb.append("\t\t<implicit-parameter name=\"");
393                                    sb.append(entry.getKey());
394                                    sb.append("\">");
395                                    sb.append(entry.getValue());
396                                    sb.append("</implicit-parameter>\n");
397                            }
398    
399                            Map<String, String> overriddenParameters =
400                                    comparableRoute.getOverriddenParameters();
401    
402                            for (Map.Entry<String, String> entry :
403                                            overriddenParameters.entrySet()) {
404    
405                                    sb.append("\t\t<overridden-parameter name=\"");
406                                    sb.append(entry.getKey());
407                                    sb.append("\">");
408                                    sb.append(entry.getValue());
409                                    sb.append("</overridden-parameter>\n");
410                            }
411    
412                            sb.append("\t</route>\n");
413                    }
414    
415                    sb.append("</routes>");
416    
417                    return sb.toString();
418            }
419    
420            protected String formatPortletXML(String content)
421                    throws DocumentException, IOException {
422    
423                    Document document = saxReaderUtil.read(content);
424    
425                    Element rootElement = document.getRootElement();
426    
427                    rootElement.sortAttributes(true);
428    
429                    List<Element> portletElements = rootElement.elements("portlet");
430    
431                    for (Element portletElement : portletElements) {
432                            portletElement.sortElementsByChildElement("init-param", "name");
433    
434                            Element portletPreferencesElement = portletElement.element(
435                                    "portlet-preferences");
436    
437                            if (portletPreferencesElement != null) {
438                                    portletPreferencesElement.sortElementsByChildElement(
439                                            "preference", "name");
440                            }
441                    }
442    
443                    return document.formattedString();
444            }
445    
446            protected void formatServiceXML(String fileName, String content)
447                    throws DocumentException {
448    
449                    Document document = saxReaderUtil.read(content);
450    
451                    Element rootElement = document.getRootElement();
452    
453                    List<Element> entityElements = rootElement.elements("entity");
454    
455                    String previousEntityName = StringPool.BLANK;
456    
457                    for (Element entityElement : entityElements) {
458                            String entityName = entityElement.attributeValue("name");
459    
460                            if (Validator.isNotNull(previousEntityName) &&
461                                    (previousEntityName.compareToIgnoreCase(entityName) > 0)) {
462    
463                                    processErrorMessage(
464                                            fileName, "sort: " + fileName + " " + entityName);
465                            }
466    
467                            String previousReferenceEntity = StringPool.BLANK;
468                            String previousReferencePackagePath = StringPool.BLANK;
469    
470                            List<Element> referenceElements = entityElement.elements(
471                                    "reference");
472    
473                            for (Element referenceElement : referenceElements) {
474                                    String referenceEntity = referenceElement.attributeValue(
475                                            "entity");
476                                    String referencePackagePath = referenceElement.attributeValue(
477                                            "package-path");
478    
479                                    if (Validator.isNotNull(previousReferencePackagePath)) {
480                                            if ((previousReferencePackagePath.compareToIgnoreCase(
481                                                            referencePackagePath) > 0) ||
482                                                    (previousReferencePackagePath.equals(
483                                                            referencePackagePath) &&
484                                                     (previousReferenceEntity.compareToIgnoreCase(
485                                                             referenceEntity) > 0))) {
486    
487                                                    processErrorMessage(
488                                                            fileName,
489                                                            "sort: " + fileName + " " + referencePackagePath);
490                                            }
491                                    }
492    
493                                    previousReferenceEntity = referenceEntity;
494                                    previousReferencePackagePath = referencePackagePath;
495                            }
496    
497                            previousEntityName = entityName;
498                    }
499    
500                    Element exceptionsElement = rootElement.element("exceptions");
501    
502                    if (exceptionsElement == null) {
503                            return;
504                    }
505    
506                    List<Element> exceptionElements = exceptionsElement.elements(
507                            "exception");
508    
509                    String previousException = StringPool.BLANK;
510    
511                    for (Element exceptionElement : exceptionElements) {
512                            String exception = exceptionElement.getStringValue();
513    
514                            if (Validator.isNotNull(previousException) &&
515                                    (previousException.compareToIgnoreCase(exception) > 0)) {
516    
517                                    processErrorMessage(
518                                            fileName, "sort: " + fileName + " " + exception);
519                            }
520    
521                            previousException = exception;
522                    }
523            }
524    
525            protected void formatStrutsConfigXML(String fileName, String content)
526                    throws DocumentException {
527    
528                    Document document = saxReaderUtil.read(content);
529    
530                    Element rootElement = document.getRootElement();
531    
532                    Element actionMappingsElement = rootElement.element("action-mappings");
533    
534                    List<Element> actionElements = actionMappingsElement.elements("action");
535    
536                    String previousPath = StringPool.BLANK;
537    
538                    for (Element actionElement : actionElements) {
539                            String path = actionElement.attributeValue("path");
540    
541                            if (Validator.isNotNull(previousPath) &&
542                                    (previousPath.compareTo(path) > 0) &&
543                                    (!previousPath.startsWith("/portal/") ||
544                                     path.startsWith("/portal/"))) {
545    
546                                    processErrorMessage(fileName, "sort: " + fileName + " " + path);
547                            }
548    
549                            previousPath = path;
550                    }
551            }
552    
553            protected void formatTilesDefsXML(String fileName, String content)
554                    throws DocumentException {
555    
556                    Document document = saxReaderUtil.read(content);
557    
558                    Element rootElement = document.getRootElement();
559    
560                    List<Element> definitionElements = rootElement.elements("definition");
561    
562                    String previousName = StringPool.BLANK;
563    
564                    for (Element definitionElement : definitionElements) {
565                            String name = definitionElement.attributeValue("name");
566    
567                            if (Validator.isNotNull(previousName) &&
568                                    (previousName.compareTo(name) > 0) &&
569                                    !previousName.equals("portlet")) {
570    
571                                    processErrorMessage(fileName, "sort: " + fileName + " " + name);
572    
573                            }
574    
575                            previousName = name;
576                    }
577            }
578    
579            protected String formatWebXML(String fileName, String content)
580                    throws IOException {
581    
582                    if (!portalSource) {
583                            String webXML = ContentUtil.get(
584                                    "com/liferay/portal/deploy/dependencies/web.xml");
585    
586                            if (content.equals(webXML)) {
587                                    processErrorMessage(fileName, fileName);
588                            }
589    
590                            return content;
591                    }
592    
593                    Properties properties = new Properties();
594    
595                    String propertiesContent = fileUtil.read(
596                            BASEDIR + "portal-impl/src/portal.properties");
597    
598                    PropertiesUtil.load(properties, propertiesContent);
599    
600                    String[] locales = StringUtil.split(
601                            properties.getProperty(PropsKeys.LOCALES));
602    
603                    Arrays.sort(locales);
604    
605                    Set<String> urlPatterns = new TreeSet<String>();
606    
607                    for (String locale : locales) {
608                            int pos = locale.indexOf(StringPool.UNDERLINE);
609    
610                            String languageCode = locale.substring(0, pos);
611    
612                            urlPatterns.add(languageCode);
613                            urlPatterns.add(locale);
614                    }
615    
616                    StringBundler sb = new StringBundler();
617    
618                    for (String urlPattern : urlPatterns) {
619                            sb.append("\t<servlet-mapping>\n");
620                            sb.append("\t\t<servlet-name>I18n Servlet</servlet-name>\n");
621                            sb.append("\t\t<url-pattern>/");
622                            sb.append(urlPattern);
623                            sb.append("/*</url-pattern>\n");
624                            sb.append("\t</servlet-mapping>\n");
625                    }
626    
627                    int x = content.indexOf("<servlet-mapping>");
628    
629                    x = content.indexOf("<servlet-name>I18n Servlet</servlet-name>", x);
630    
631                    x = content.lastIndexOf("<servlet-mapping>", x) - 1;
632    
633                    int y = content.lastIndexOf(
634                            "<servlet-name>I18n Servlet</servlet-name>");
635    
636                    y = content.indexOf("</servlet-mapping>", y) + 19;
637    
638                    String newContent =
639                            content.substring(0, x) + sb.toString() + content.substring(y);
640    
641                    x = newContent.indexOf("<security-constraint>");
642    
643                    x = newContent.indexOf(
644                            "<web-resource-name>/c/portal/protected</web-resource-name>", x);
645    
646                    x = newContent.indexOf("<url-pattern>", x) - 3;
647    
648                    y = newContent.indexOf("<http-method>", x);
649    
650                    y = newContent.lastIndexOf("</url-pattern>", y) + 15;
651    
652                    sb = new StringBundler();
653    
654                    sb.append("\t\t\t<url-pattern>/c/portal/protected</url-pattern>\n");
655    
656                    for (String urlPattern : urlPatterns) {
657                            sb.append("\t\t\t<url-pattern>/");
658                            sb.append(urlPattern);
659                            sb.append("/c/portal/protected</url-pattern>\n");
660                    }
661    
662                    return newContent.substring(0, x) + sb.toString() +
663                            newContent.substring(y);
664            }
665    
666            private static Pattern _commentPattern1 = Pattern.compile(
667                    ">\n\t+<!--[\n ]");
668            private static Pattern _commentPattern2 = Pattern.compile(
669                    "[\t ]-->\n[\t<]");
670    
671    }