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.api.core.Context;
024import org.apache.wiki.api.core.Engine;
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 the diff.
039 */
040public class ExternalDiffProvider implements DiffProvider {
041
042    private static final Logger log = Logger.getLogger(ExternalDiffProvider.class);
043
044    /**
045     * Determines the command to be used for 'diff'. This program must be able
046     * to output diffs in the unified format. For example 'diff -u %s1 %s2'.
047     */
048    public static final String PROP_DIFFCOMMAND    = "jspwiki.diffCommand";
049
050    private String m_diffCommand = null;
051    private Charset m_encoding;
052
053    private static final char DIFF_ADDED_SYMBOL    = '+';
054    private static final char DIFF_REMOVED_SYMBOL  = '-';
055
056    private static final String CSS_DIFF_ADDED     = "<tr><td bgcolor=\"#99FF99\" class=\"diffadd\">";
057    private static final String CSS_DIFF_REMOVED   = "<tr><td bgcolor=\"#FF9933\" class=\"diffrem\">";
058    private static final String CSS_DIFF_UNCHANGED = "<tr><td class=\"diff\">";
059    private static final String CSS_DIFF_CLOSE     = "</td></tr>";
060
061    //FIXME This could/should be a property for this provider, there is not guarentee that
062    //the external program generates a format suitible for the colorization code of the
063    //TraditionalDiffProvider, currently set to true for legacy compatibility.
064    //I don't think this 'feature' ever worked right, did it?...
065    private boolean m_traditionalColorization = true;
066
067    /**
068     *  Creates a new ExternalDiffProvider.
069     */
070    public ExternalDiffProvider()
071    {
072    }
073
074    /**
075     * @see org.apache.wiki.api.providers.WikiProvider#getProviderInfo()
076     * {@inheritDoc}
077     */
078    @Override public String getProviderInfo()
079    {
080        return "ExternalDiffProvider";
081    }
082
083    /**
084     * {@inheritDoc}
085     * @see org.apache.wiki.api.providers.WikiProvider#initialize(org.apache.wiki.api.core.Engine, java.util.Properties)
086     */
087    @Override
088    public void initialize( final Engine engine, final Properties properties ) throws NoRequiredPropertyException, IOException {
089        m_diffCommand = properties.getProperty( PROP_DIFFCOMMAND );
090        if( m_diffCommand == null || m_diffCommand.trim().equals( "" ) ) {
091            throw new NoRequiredPropertyException( "ExternalDiffProvider missing required property", PROP_DIFFCOMMAND );
092        }
093
094        m_encoding = engine.getContentEncoding();
095    }
096
097
098    /**
099     * Makes the diff by calling "diff" program.
100     * {@inheritDoc}
101     */
102    @Override
103    public String makeDiffHtml( final Context ctx, final String p1, final String p2 ) {
104        File f1 = null;
105        File f2 = null;
106        String diff = null;
107
108        try {
109            f1 = FileUtil.newTmpFile(p1, m_encoding);
110            f2 = FileUtil.newTmpFile(p2, m_encoding);
111
112            String cmd = TextUtil.replaceString(m_diffCommand, "%s1", f1.getPath());
113            cmd = TextUtil.replaceString(cmd, "%s2", f2.getPath());
114
115            final String output = FileUtil.runSimpleCommand(cmd, f1.getParent());
116
117            // FIXME: Should this rely on the system default encoding?
118            final String rawWikiDiff = new String( output.getBytes( StandardCharsets.ISO_8859_1 ), m_encoding );
119            final String htmlWikiDiff = TextUtil.replaceEntities( rawWikiDiff );
120
121            if (m_traditionalColorization) { //FIXME, see comment near declaration...
122                diff = colorizeDiff( htmlWikiDiff );
123            } else {
124                diff = htmlWikiDiff;
125            }
126        } catch( final IOException e ) {
127            log.error("Failed to do file diff", e );
128        } catch( final InterruptedException e ) {
129            log.error("Interrupted", e );
130        } finally {
131            if( f1 != null ) {
132                f1.delete();
133            }
134            if( f2 != null ) {
135                f2.delete();
136            }
137        }
138
139        return diff;
140    }
141
142
143    /**
144     * Goes through output provided by a diff command and inserts HTML tags to
145     * make the result more legible. Currently colors lines starting with a +
146     * green, those starting with - reddish (hm, got to think of color blindness here...).
147     */
148    static String colorizeDiff( final String diffText ) throws IOException {
149        if( diffText == null ) {
150            return "Invalid diff - probably something wrong with server setup.";
151        }
152
153        String line;
154        String start;
155        String stop;
156
157        final BufferedReader in = new BufferedReader( new StringReader( diffText ) );
158        final StringBuilder out = new StringBuilder();
159
160        out.append("<table class=\"diff\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n");
161        while( (line = in.readLine()) != null ) {
162            stop = CSS_DIFF_CLOSE;
163
164            if( line.length() > 0 ) {
165                switch( line.charAt( 0 ) ) {
166                    case DIFF_ADDED_SYMBOL:
167                        start = CSS_DIFF_ADDED;
168                        break;
169                    case DIFF_REMOVED_SYMBOL:
170                        start = CSS_DIFF_REMOVED;
171                        break;
172                    default:
173                        start = CSS_DIFF_UNCHANGED;
174                }
175            } else {
176                start = CSS_DIFF_UNCHANGED;
177            }
178
179            out.append( start )
180               .append( line.trim() )
181               .append( stop )
182               .append( "\n" );
183        }
184
185        out.append( "</table>\n" );
186        return out.toString();
187    }
188
189}