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