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}