001/*
002    Licensed to the Apache Software Foundation (ASF) under one
003    or more contributor license agreements.  See the NOTICE file
004    distributed with this work for additional information
005    regarding copyright ownership.  The ASF licenses this file
006    to you under the Apache License, Version 2.0 (the
007    "License"); you may not use this file except in compliance
008    with the License.  You may obtain a copy of the License at
009
010       http://www.apache.org/licenses/LICENSE-2.0
011
012    Unless required by applicable law or agreed to in writing,
013    software distributed under the License is distributed on an
014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015    KIND, either express or implied.  See the License for the
016    specific language governing permissions and limitations
017    under the License.  
018*/
019
020package org.apache.wiki.diff;
021
022import org.apache.log4j.Logger;
023import org.apache.wiki.WikiContext;
024import org.apache.wiki.WikiEngine;
025import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
026import org.apache.wiki.util.FileUtil;
027import org.apache.wiki.util.TextUtil;
028
029import java.io.BufferedReader;
030import java.io.File;
031import java.io.IOException;
032import java.io.StringReader;
033import java.nio.charset.Charset;
034import java.nio.charset.StandardCharsets;
035import java.util.Properties;
036
037/**
038 * This DiffProvider allows external command line tools to be used to generate
039 * the diff.
040 */
041public class ExternalDiffProvider implements DiffProvider
042{
043    private static final Logger log = Logger.getLogger(ExternalDiffProvider.class);
044
045    /**
046     * Determines the command to be used for 'diff'. This program must be able
047     * to output diffs in the unified format. For example 'diff -u %s1 %s2'.
048     */
049    public static final String PROP_DIFFCOMMAND    = "jspwiki.diffCommand";
050
051    private String m_diffCommand = null;
052    private Charset m_encoding;
053
054    private static final char DIFF_ADDED_SYMBOL    = '+';
055    private static final char DIFF_REMOVED_SYMBOL  = '-';
056
057    private static final String CSS_DIFF_ADDED     = "<tr><td bgcolor=\"#99FF99\" class=\"diffadd\">";
058    private static final String CSS_DIFF_REMOVED   = "<tr><td bgcolor=\"#FF9933\" class=\"diffrem\">";
059    private static final String CSS_DIFF_UNCHANGED = "<tr><td class=\"diff\">";
060    private static final String CSS_DIFF_CLOSE     = "</td></tr>";
061
062    //FIXME This could/should be a property for this provider, there is not guarentee that
063    //the external program generates a format suitible for the colorization code of the
064    //TraditionalDiffProvider, currently set to true for legacy compatibility.
065    //I don't think this 'feature' ever worked right, did it?...
066    private boolean m_traditionalColorization = true;
067
068    /**
069     *  Creates a new ExternalDiffProvider.
070     */
071    public ExternalDiffProvider()
072    {
073    }
074
075    /**
076     * @see org.apache.wiki.WikiProvider#getProviderInfo()
077     * {@inheritDoc}
078     */
079    public String getProviderInfo()
080    {
081        return "ExternalDiffProvider";
082    }
083
084    /**
085     * {@inheritDoc}
086     * @see org.apache.wiki.WikiProvider#initialize(org.apache.wiki.WikiEngine, java.util.Properties)
087     */
088    public void initialize( WikiEngine engine, Properties properties )
089        throws NoRequiredPropertyException, IOException
090    {
091        m_diffCommand = properties.getProperty( PROP_DIFFCOMMAND );
092
093        if( (null == m_diffCommand) || (m_diffCommand.trim().equals( "" )) )
094        {
095            throw new NoRequiredPropertyException( "ExternalDiffProvider missing required property", PROP_DIFFCOMMAND );
096        }
097
098        m_encoding = engine.getContentEncoding();
099    }
100
101
102    /**
103     * Makes the diff by calling "diff" program.
104     * {@inheritDoc}
105     */
106    public String makeDiffHtml( WikiContext ctx, String p1, String p2 )
107    {
108        File f1 = null;
109        File f2 = null;
110        String diff = null;
111
112        try
113        {
114            f1 = FileUtil.newTmpFile(p1, m_encoding);
115            f2 = FileUtil.newTmpFile(p2, m_encoding);
116
117            String cmd = TextUtil.replaceString(m_diffCommand, "%s1", f1.getPath());
118            cmd = TextUtil.replaceString(cmd, "%s2", f2.getPath());
119
120            String output = FileUtil.runSimpleCommand(cmd, f1.getParent());
121
122            // FIXME: Should this rely on the system default encoding?
123            String rawWikiDiff = new String( output.getBytes( StandardCharsets.ISO_8859_1 ), m_encoding );
124
125            String htmlWikiDiff = TextUtil.replaceEntities( rawWikiDiff );
126
127            if (m_traditionalColorization) //FIXME, see comment near declaration...
128                diff = colorizeDiff(htmlWikiDiff);
129            else
130                diff = htmlWikiDiff;
131
132        } catch (IOException e) {
133            log.error("Failed to do file diff", e);
134        } catch (InterruptedException e) {
135            log.error("Interrupted", e);
136        } finally {
137            if( f1 != null ) {
138                f1.delete();
139            }
140            if( f2 != null ) {
141                f2.delete();
142            }
143        }
144
145        return diff;
146    }
147
148
149    /**
150     * Goes through output provided by a diff command and inserts HTML tags to
151     * make the result more legible. Currently colors lines starting with a +
152     * green, those starting with - reddish (hm, got to think of color blindness
153     * here...).
154     */
155    static String colorizeDiff(String diffText) throws IOException {
156        if( diffText == null ) {
157            return "Invalid diff - probably something wrong with server setup.";
158        }
159
160        String line = null;
161        String start = null;
162        String stop = null;
163
164        BufferedReader in = new BufferedReader( new StringReader( diffText ) );
165        StringBuilder out = new StringBuilder();
166
167        out.append("<table class=\"diff\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n");
168        while( (line = in.readLine()) != null )
169        {
170            stop = CSS_DIFF_CLOSE;
171
172            if( line.length() > 0 )
173            {
174                switch( line.charAt( 0 ) )
175                {
176                    case DIFF_ADDED_SYMBOL:
177                        start = CSS_DIFF_ADDED;
178                        break;
179                    case DIFF_REMOVED_SYMBOL:
180                        start = CSS_DIFF_REMOVED;
181                        break;
182                    default:
183                        start = CSS_DIFF_UNCHANGED;
184                }
185            }
186            else
187            {
188                start = CSS_DIFF_UNCHANGED;
189            }
190
191            out.append( start );
192            out.append( line.trim() );
193            out.append( stop + "\n" );
194        }
195
196        out.append( "</table>\n" );
197        return out.toString();
198    }
199}