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}