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}