    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.logging.log4j.LogManager;
    023import org.apache.logging.log4j.Logger;
    024import org.apache.wiki.api.core.Context;
    025import org.apache.wiki.api.core.Engine;
    026import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
    027import org.apache.wiki.util.FileUtil;
    028import org.apache.wiki.util.TextUtil;
    029
    030import java.io.BufferedReader;
    031import java.io.File;
    032import java.io.IOException;
    033import java.io.StringReader;
    034import java.nio.charset.Charset;
    035import java.nio.charset.StandardCharsets;
    036import java.util.Properties;
    037
    038/**
    039 * This DiffProvider allows external command line tools to be used to generate the diff.
    040 */
    041public class ExternalDiffProvider implements DiffProvider {
    042
    043    private static final Logger LOG = LogManager.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;
    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 final boolean m_traditionalColorization = true;
    067
    068    /**
    069     *  Creates a new ExternalDiffProvider.
    070     */
    071    public ExternalDiffProvider()
    072    {
    073    }
    074
    075    /**
    076     * @see org.apache.wiki.api.providers.WikiProvider#getProviderInfo()
    077     * {@inheritDoc}
    078     */
    079    @Override public String getProviderInfo()
    080    {
    081        return "ExternalDiffProvider";
    082    }
    083
    084    /**
    085     * {@inheritDoc}
    086     * @see org.apache.wiki.api.providers.WikiProvider#initialize(org.apache.wiki.api.core.Engine, java.util.Properties)
    087     */
    088    @Override
    089    public void initialize( final Engine engine, final Properties properties ) throws NoRequiredPropertyException, IOException {
    090        m_diffCommand = properties.getProperty( PROP_DIFFCOMMAND );
    091        if( m_diffCommand == null || m_diffCommand.trim().equals( "" ) ) {
    092            throw new NoRequiredPropertyException( "ExternalDiffProvider missing required property", PROP_DIFFCOMMAND );
    093        }
    094
    095        m_encoding = engine.getContentEncoding();
    096    }
    097
    098
    099    /**
    100     * Makes the diff by calling "diff" program.
    101     * {@inheritDoc}
    102     */
    103    @Override
    104    public String makeDiffHtml( final Context ctx, final String p1, final String p2 ) {
    105        File f1 = null;
    106        File f2 = null;
    107        String diff = null;
    108
    109        try {
    110            f1 = FileUtil.newTmpFile(p1, m_encoding);
    111            f2 = FileUtil.newTmpFile(p2, m_encoding);
    112
    113            String cmd = TextUtil.replaceString(m_diffCommand, "%s1", f1.getPath());
    114            cmd = TextUtil.replaceString(cmd, "%s2", f2.getPath());
    115
    116            final String output = FileUtil.runSimpleCommand(cmd, f1.getParent());
    117
    118            // FIXME: Should this rely on the system default encoding?
    119            final String rawWikiDiff = new String( output.getBytes( StandardCharsets.ISO_8859_1 ), m_encoding );
    120            final String htmlWikiDiff = TextUtil.replaceEntities( rawWikiDiff );
    121
    122            if (m_traditionalColorization) { //FIXME, see comment near declaration...
    123                diff = colorizeDiff( htmlWikiDiff );
    124            } else {
    125                diff = htmlWikiDiff;
    126            }
    127        } catch( final IOException e ) {
    128            LOG.error("Failed to do file diff", e );
    129        } catch( final InterruptedException e ) {
    130            LOG.error("Interrupted", e );
    131        } finally {
    132            if( f1 != null ) {
    133                f1.delete();
    134            }
    135            if( f2 != null ) {
    136                f2.delete();
    137            }
    138        }
    139
    140        return diff;
    141    }
    142
    143
    144    /**
    145     * Goes through output provided by a diff command and inserts HTML tags to
    146     * make the result more legible. Currently colors lines starting with a +
    147     * green, those starting with - reddish (hm, got to think of color blindness here...).
    148     */
    149    static String colorizeDiff( final String diffText ) throws IOException {
    150        if( diffText == null ) {
    151            return "Invalid diff - probably something wrong with server setup.";
    152        }
    153
    154        String line;
    155        String start;
    156        String stop;
    157
    158        final BufferedReader in = new BufferedReader( new StringReader( diffText ) );
    159        final StringBuilder out = new StringBuilder();
    160
    161        out.append("<table class=\"diff\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n");
    162        while( (line = in.readLine()) != null ) {
    163            stop = CSS_DIFF_CLOSE;
    164
    165            if( !line.isEmpty() ) {
    166                switch( line.charAt( 0 ) ) {
    167                    case DIFF_ADDED_SYMBOL:
    168                        start = CSS_DIFF_ADDED;
    169                        break;
    170                    case DIFF_REMOVED_SYMBOL:
    171                        start = CSS_DIFF_REMOVED;
    172                        break;
    173                    default:
    174                        start = CSS_DIFF_UNCHANGED;
    175                }
    176            } else {
    177                start = CSS_DIFF_UNCHANGED;
    178            }
    179
    180            out.append( start )
    181               .append( line.trim() )
    182               .append( stop )
    183               .append( "\n" );
    184        }
    185
    186        out.append( "</table>\n" );
    187        return out.toString();
    188    }
    189
    190}