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