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