larryi 01/08/15 22:24:14 Modified: src/share/org/apache/tomcat/modules/config JservConfig.java Log: Updated to have features similar to ApacheConfig. Revision Changes Path 1.3 +467 -199 jakarta-tomcat/src/share/org/apache/tomcat/modules/config/JservConfig.java Index: JservConfig.java =================================================================== RCS file: /home/cvs/jakarta-tomcat/src/share/org/apache/tomcat/modules/config/JservConfig.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- JservConfig.java 2001/07/19 20:23:34 1.2 +++ JservConfig.java 2001/08/16 05:24:14 1.3 @@ -61,53 +61,75 @@ import org.apache.tomcat.util.io.FileUtil; import org.apache.tomcat.util.log.*; import java.io.*; -import java.net.*; import java.util.*; // Used to find Ajp1? connector port import org.apache.tomcat.modules.server.Ajp12Interceptor; -import org.apache.tomcat.modules.server.Ajp13Interceptor; /** - - Generates automatic apache configurations based on + Generates automatic apache mod_jserv configurations based on the Tomcat server.xml settings and the war contexts initialized during startup. <p> - This config interceptor is enabled by inserting an ApacheConfig + This config interceptor is enabled by inserting a JservConfig element in the <b>\<ContextManager></b> tag body inside the server.xml file like so: <pre> * < ContextManager ... > * ... - * <<b>JServConfig</b> <i>options</i> /> + * <<b>JservConfig</b> <i>options</i> /> * ... * < /ContextManager > </pre> where <i>options</i> can include any of the following attributes: <ul> - <li><b>confighome</b> - default parent directory for the following paths. + <li><b>configHome</b> - default parent directory for the following paths. If not set, this defaults to TOMCAT_HOME. Ignored whenever any of the following paths is absolute. </li> - <li><b>jservconfig</b> - path to write apache jserv conf file to. If - not set, defaults to - "conf/jserv/tomcat-apache.conf".</li> - <li><b>modjserv</b> - path to Apache JServ plugin module file. If not - set, defaults to "modules/ApacheModuleJServ.dll" - on windows, "modules/Jserv.nlm" on netware, and - "libexec/mod_jserv.so" everywhere else.</li> - <li><b>jklog</b> - path to log file to be used by mod_jk.</li> + <li><b>jservConfig</b> - path to use for writing Apache mod_jserv conf file. If + not set, defaults to + "conf/auto/tomcat-apache.conf".</li> + <li><b>modJServ</b> - path to Apache mod_jserv plugin file. If not set, + defaults to "modules/ApacheModuleJserv.dll" on windows, + and "libexec/mod_jserv.so" everywhere else.</li> + <li><b>jservLog</b> - path to log file to be used by mod_jserv.</li> + <li><b>jservDebug</b> - Jserv Loglevel setting. May be debug, info, notice, + warn, error, crit, alert, or emerg. + If not set, defaults to debug.</li> + <li><b>forwardAll</b> - If true, forward all requests to Tomcat. This helps + insure that all the behavior configured in the web.xml + file functions correctly. If false, let Apache serve + static resources. The default is true. + Warning: When false, some configuration in + the web.xml may not be duplicated in Apache. + Review the tomcat-apache conf file to see what + configuration is actually being set in Apache.</li> + <li><b>noRoot</b> - If true, the root context is not mapped to + Tomcat. If false and forwardAll is true, all requests + to the root context are mapped to Tomcat. If false and + forwardAll is false, only JSP and servlets requests to + the root context are mapped to Tomcat. When false, + to correctly serve Tomcat's root context you must also + modify the DocumentRoot setting in Apache's httpd.conf + file to point to Tomcat's root context directory. + Otherwise some content, such as Apache's index.html, + will be served by Apache before mod_jserv gets a chance + to claim the request and pass it to Tomcat. + The default is true.</li> </ul> - <p> + <p> @author Costin Manolache + @author Larry Isaacs @author Mel Martinez - @version $Revision: 1.2 $ $Date: 2001/07/19 20:23:34 $ + @version $Revision: 1.3 $ $Date: 2001/08/16 05:24:14 $ */ public class JservConfig extends BaseInterceptor { /** default path to JServ .conf location */ - public static final String APACHE_CONFIG="conf/jserv/tomcat-apache.conf"; + public static final String APACHE_CONFIG="conf/auto/tomcat-apache.conf"; + /** default mod_jserv log file location */ + public static final String JSERV_LOG_LOCATION = "logs/mod_jserv.log"; /** default location of mod_jserv Apache plug-in. */ public static final String MOD_JSERV; public static final String AJPV12="ajpv12"; @@ -117,8 +139,6 @@ String os = System.getProperty("os.name").toLowerCase(); if(os.indexOf("windows")>=0){ MOD_JSERV = "modules/ApacheModuleJserv.dll"; - }else if(os.indexOf("netware")>=0){ - MOD_JSERV = "modules/Jserv.nlm"; }else{ MOD_JSERV = "libexec/mod_jserv.so"; } @@ -126,8 +146,19 @@ private File configHome = null; private File jservConfig = null; - private File workersConfig = null; private File modJserv = null; + private File jservLog = null; + + private String tomcatHome; + + private String jservDebug=null; + private boolean noRoot=true; + + // default is true until we can map all web.xml directives + // Or detect only portable directives were used. + boolean forwardAll=true; + + Hashtable NamedVirtualHosts=null; public JservConfig() { } @@ -156,49 +187,56 @@ } } + //-------------------- Properties -------------------- + + /** If false, we'll try to generate a config that will + * let apache serve static files. + * The default is true, forward all requests in a context + * to tomcat. + */ + public void setForwardAll( boolean b ) { + forwardAll=b; + } + + /** Special option - do not generate mappings for the ROOT + context. The default is true, and will not generate the mappings, + not redirecting all pages to tomcat (since /* matches everything). + This means that Apache's root remains intact but isn't completely + servlet/JSP enabled. If the ROOT webapp can be configured with + apache serving static files, there's no problem setting this + option to false. If not, then setting it true means Apache will + be out of picture for all requests. + */ + public void setNoRoot( boolean b ) { + noRoot=b; + } + /** set a path to the parent directory of the conf folder. That is, the parent directory - within which setJservConfig(), setJkConfig() - and setWorkerConfig() paths would be resolved against + within which setJkConfig() and other path + setters would be resolved against if relative. For example if ConfigHome is set to "/home/tomcat" - and JkConfig is set to "conf/mod_jk.conf" then the resulting - path returned from getJkConfig() would be: - "/home/tomcat/conf/mod_jk.conf".</p> + and JservConfig is set to "conf/tomcat-apache.conf" then the resulting + path used would be: + "/home/tomcat/conf/tomcat-apache.conf".</p> <p> - However, if JkConfig, JservConfig or WorkersConfig - are set to absolute paths, this attribute is ignored. + However, if JservConfig or other path + is set to an absolute path, this attribute is ignored. <p> If not set, execute() will set this to TOMCAT_HOME. <p> @param <b>dir</b> - path to a directory */ public void setConfigHome(String dir){ - setConfigHome(dir==null?null:new File(dir)); - } - - /** - set a path to the parent directory of the - conf folder. That is, the parent directory - within which setJservConfig(), setJkConfig() - and setWorkerConfig() paths would be resolved against - if relative. For example if ConfigHome is set to "/home/tomcat" - and JkConfig is set to "conf/mod_jk.conf" then the resulting - path returned from getJkConfig() would be: - "/home/tomcat/conf/mod_jk.conf".</p> - <p> - However, if JkConfig, JservConfig or WorkersConfig - are set to absolute paths, this attribute is ignored. - <p> - @param <b>dir</b> - path to a directory - */ - public void setConfigHome(File dir){ - if(!dir.isDirectory()){ + if( dir==null ) return; + File f=new File(dir); + if(!f.isDirectory()){ throw new IllegalArgumentException( - "ApacheConfig.setConfigHome(): "+ + "JservConfig.setConfigHome(): "+ "Configuration Home must be a directory! : "+dir); } - configHome = dir; + configHome = f; } /** @@ -226,44 +264,6 @@ } - - /** - return a File object pointing to the output file - in which to write the mod_jserv configuration. - If the path set using setJservConfig() was absolute, - then this simply returns that File object. - If the path set using setJservConfig() was relative - then this method will first try to resolve it - absolutely against the path returned from getConfigHome(). - If getConfigHome()==null, then instead the path - will be resolved absolutely against the current - directory (System.getProperty("user.dir")). - <p> - @return a File object. - */ - public File getJservConfig(){ - if(jservConfig==null){ - jservConfig = new File(APACHE_CONFIG); - } - File jservF = jservConfig; - if(!jservF.isAbsolute()){ - if(getConfigHome()!=null){ - jservF = new File( - getConfigHome(),jservF.getPath()); - }else{ //resolve against user.dir (implicit) - jservF = new File(jservF.getAbsolutePath()); - } - } - File parent = new File(jservF.getParent()); - if(!parent.exists()){ - if(!parent.mkdirs()){ - throw new RuntimeException( - "Unable to create path to config file :"+jservF); - } - } - return jservF; - } - /** set the path to the Jserv Apache Module @param <b>path</b> String path to a file @@ -281,132 +281,400 @@ } /** - returns the path to the apache module mod_jserv. - If the path set with setModJserv() was relative, this method - will try first to resolve it absolutely - against the return value of getConfigHome(). If that is null, then - it instead will resolve against the current user.dir. - If this file doesn't exist, the relative path is returned. - <p> - @return a File object with the path to the mod_jserv.so file. + set the path to the mod_jserv log file + @param <b>path</b> String path to a file */ - public File getModJserv(){ - if(modJserv==null){ - modJserv=new File(MOD_JSERV); - } - File jservF = modJserv; - if(!jservF.isAbsolute()){ - if(getConfigHome()!=null){ - jservF = new File(getConfigHome(),jservF.getPath()); - }else{//resolve against user.dir - jservF = new File(jservF.getAbsolutePath()); - } - if( !jservF.exists() ) - jservF = modJserv; + public void setJservLog(String path){ + jservLog= ( path==null?null:new File(path)); + } + + /** Set the verbosity level for mod_jserv. + ( use debug, error, etc. ) If not set, no log is written. + */ + public void setJservDebug( String level ) { + jservDebug=null; + } + + // -------------------- Initialize/guess defaults -------------------- + + /** Initialize defaults for properties that are not set + explicitely + */ + public void initProperties(ContextManager cm) { + tomcatHome = cm.getHome(); + File tomcatDir = new File(tomcatHome); + if(configHome==null){ + configHome=tomcatDir; } - return jservF; + + jservConfig=FileUtil.getConfigFile( jservConfig, configHome, APACHE_CONFIG); + + if( modJserv == null ) + modJserv=new File(MOD_JSERV); + else + modJserv=FileUtil.getConfigFile( modJserv, configHome, MOD_JSERV ); + jservLog=FileUtil.getConfigFile( jservLog, configHome, JSERV_LOG_LOCATION); } - + + + // -------------------- Generate config -------------------- + /** - executes the ApacheConfig interceptor. This method generates apache - configuration files for use with mod_jserv or mod_jk. If not + executes the JservConfig interceptor. This method generates apache + configuration files for use with mod_jserv. If not already set, this method will setConfigHome() to the value returned from <i>cm.getHome()</i>. <p> @param <b>cm</b> a ContextManager object. */ public void execute(ContextManager cm) throws TomcatException { - try { - String tomcatHome = cm.getHome(); - File tomcatDir = new File(tomcatHome); - - if(getConfigHome()==null){ - setConfigHome(tomcatDir); - } - - PrintWriter pw=new PrintWriter(new FileWriter(getJservConfig())); - log("Generating apache mod_jserv config = "+getJservConfig() ); - - //insert LoadModule calls: - pw.println("<IfModule !mod_jserv.c>"); - pw.println(" LoadModule jserv_module "+ - getModJserv().toString().replace('\\','/')); - pw.println("</IfModule>"); - - pw.println("ApJServManual on"); - pw.println("ApJServDefaultProtocol " + AJPV12); - pw.println("ApJServSecretKey DISABLED"); - pw.println("ApJServMountCopy on"); - pw.println("ApJServLogLevel notice"); - pw.println(); - - // Find Ajp1? connectors - int portInt=8007; - BaseInterceptor ci[]=cm.getContainer().getInterceptors(); - for( int i=0; i<ci.length; i++ ) { - Object con=ci[i]; - if( con instanceof Ajp12Interceptor ) { - Ajp12Interceptor tcpCon=(Ajp12Interceptor) con; - portInt=tcpCon.getPort(); - } - } - pw.println("ApJServDefaultPort " + portInt); - pw.println(); - - pw.println("AddType text/jsp .jsp"); - pw.println("AddHandler jserv-servlet .jsp"); - pw.println(); - - - // Set up contexts - // XXX deal with Virtual host configuration !!!! - Enumeration enum = cm.getContexts(); - while (enum.hasMoreElements()) { - Context context = (Context)enum.nextElement(); - String path = context.getPath(); - String vhost = context.getHost(); + try { + initProperties(cm); - if( vhost != null ) { - // Generate Apache VirtualHost section for this host - // You'll have to do it manually right now - // XXX - continue; - } - if( path.length() > 1) { + NamedVirtualHosts = new Hashtable(); - // It's not the root context - // assert path.startsWith( "/" ) + PrintWriter pw=new PrintWriter(new FileWriter(jservConfig)); + log("Generating apache mod_jserv config = "+jservConfig ); - // Calculate the absolute path of the document base - String docBase = context.getDocBase(); - if (!FileUtil.isAbsolute(docBase)){ - docBase = tomcatHome + "/" + docBase; - } - docBase = FileUtil.patch(docBase); - if (File.separatorChar == '\\') - docBase = docBase.replace('\\','/');// use separator preferred by Apache - - // All pages will be served by tomcat. - // So far nobody found a solution that can configure apache to - // match web.xml, until this happen we can't do too much. - - // In mod_jk/Ajp14 we'll provide special solution to redirect - // static pages to apache, and avoid overhead - pw.println("ApJServMount " + path + " " + path); - - } else { - // the root context - // XXX use a non-conflicting name - pw.println("ApJServMount / /ROOT"); - } + // generate header + generateJservHead(pw,cm); - }//end while(enum) + // Set up contexts + // XXX deal with Virtual host configuration !!!! + Enumeration enum = cm.getContexts(); + while (enum.hasMoreElements()) { + Context context = (Context)enum.nextElement(); + if( forwardAll ) + generateStupidMappings( context, pw ); + else + generateContextMappings( context, pw ); + } - pw.close(); - } catch( Exception ex ) { + pw.close(); + } catch( Exception ex ) { Log loghelper = Log.getLog("tc_log", this); - loghelper.log("Error generating automatic apache configuration", ex); - } + loghelper.log("Error generating automatic apache configuration", ex); + } }//end execute() + + // -------------------- Config sections -------------------- + + /** Generate the loadModule and general options + */ + private boolean generateJservHead(PrintWriter pw, ContextManager cm) + throws TomcatException + { + //insert LoadModule calls: + pw.println("<IfModule !mod_jserv.c>"); + pw.println(" LoadModule jserv_module "+ + modJserv.toString().replace('\\','/')); + pw.println("</IfModule>"); + + pw.println("ApJServManual on"); + pw.println("ApJServDefaultProtocol " + AJPV12); + pw.println("ApJServSecretKey DISABLED"); + pw.println("ApJServMountCopy on"); + pw.println("ApJServLogLevel notice"); + pw.println(); + + // Find Ajp12 connector + int portInt=8007; + BaseInterceptor ci[]=cm.getContainer().getInterceptors(); + for( int i=0; i<ci.length; i++ ) { + Object con=ci[i]; + if( con instanceof Ajp12Interceptor ) { + Ajp12Interceptor tcpCon=(Ajp12Interceptor) con; + portInt=tcpCon.getPort(); + } + } + pw.println("ApJServDefaultPort " + portInt); + pw.println(); + return true; + } + + // -------------------- Forward all mode -------------------- + String indent=""; -}//end class ApacheConfig + /** Forward all requests for a context to tomcat. + The default. + */ + private void generateStupidMappings(Context context, + PrintWriter pw ) + { + String ctxPath = context.getPath(); + String vhost = context.getHost(); + String nPath=("".equals(ctxPath)) ? "/" : ctxPath; + + if( noRoot && "".equals(ctxPath) ) { + log("Ignoring root context in forward-all mode "); + return; + } + if( vhost != null ) { + String vhostip = getVirtualHostAddress(vhost, + context.getHostAddress()); + generateNameVirtualHost(pw, vhostip); + pw.println("<VirtualHost " + vhostip + ">"); + pw.println(" ServerName " + vhost ); + Enumeration aliases=context.getHostAliases(); + if( aliases.hasMoreElements() ) { + pw.print(" ServerAlias " ); + while( aliases.hasMoreElements() ) { + pw.print( (String)aliases.nextElement() + " " ); + } + pw.println(); + } + indent=" "; + } + + pw.println(indent + "ApJServMount " + nPath + " " + nPath ); + if( "".equals(ctxPath) ) + pw.println(indent + "ApJServMount " + nPath + "* " + nPath ); + else + pw.println(indent + "ApJServMount " + nPath + "/* " + nPath ); + if( vhost != null ) { + pw.println("</VirtualHost>"); + pw.println(); + indent=""; + } + } + + private void generateNameVirtualHost( PrintWriter pw, String ip ) { + if( !NamedVirtualHosts.containsKey(ip) ) { + pw.println("NameVirtualHost " + ip + ""); + NamedVirtualHosts.put(ip,ip); + } + } + + + // -------------------- Apache serves static mode -------------------- + // This is not going to work for all apps. We fall back to stupid mode. + + private void generateContextMappings(Context context, PrintWriter pw ) + { + String ctxPath = context.getPath(); + String vhost = context.getHost(); + + if( noRoot && "".equals(ctxPath) ) { + log("Ignoring root context in non-forward-all mode "); + return; + } + pw.println(); + pw.println("#################### " + + ((vhost!=null ) ? vhost + ":" : "" ) + + (("".equals(ctxPath)) ? "/" : ctxPath ) + + " ####################" ); + pw.println(); + if( vhost != null ) { + String vhostip = getVirtualHostAddress(vhost, + context.getHostAddress()); + generateNameVirtualHost(pw, vhostip); + pw.println("<VirtualHost " + vhostip + ">"); + pw.println(" ServerName " + vhost ); + Enumeration aliases=context.getHostAliases(); + if( aliases.hasMoreElements() ) { + pw.print(" ServerAlias " ); + while( aliases.hasMoreElements() ) { + pw.print( (String)aliases.nextElement() + " " ); + } + pw.println(); + } + indent=" "; + } + // Dynamic /servet pages go to Tomcat + + generateStaticMappings( context, pw ); + + // InvokerInterceptor - it doesn't have a container, + // but it's implemented using a special module. + + // XXX we need to better collect all mappings + addMapping( ctxPath + "/servlet/*", ctxPath, pw ); + + Enumeration servletMaps=context.getContainers(); + while( servletMaps.hasMoreElements() ) { + Container ct=(Container)servletMaps.nextElement(); + addMapping( context, ct , pw ); + } + + // There is a big problem with this one - it is + // equivalent with JkMount path/*... + // The good news - there is a container with exactly this + // map ( the real path that is used by form auth ), so no need + // for this one + //mod_jk.println("JkMount " + path + "/*j_security_check " + + // jkProto); + //mod_jk.println(); + + // XXX ErrorDocument + // Security and filter mappings + + if( vhost != null ) { + pw.println("</VirtualHost>"); + indent=""; + } + } + + // -------------------- Config Utils -------------------- + + protected boolean addMapping( Context ctx, Container ct, + PrintWriter pw ) + { + int type=ct.getMapType(); + String ctPath=ct.getPath(); + String ctxPath=ctx.getPath(); + + if( type==Container.EXTENSION_MAP ) { + if( ctPath.length() < 3 ) return false; + String ext=ctPath.substring( 2 ); + return addExtensionMapping( ctxPath, ext , pw ); + } + String fullPath=null; + if( ctPath.startsWith("/" )) + fullPath=ctxPath+ ctPath; + else + fullPath=ctxPath + "/" + ctPath; + return addMapping( fullPath, ctxPath, pw); + } + + /** Add an Apache extension mapping. + */ + protected boolean addExtensionMapping( String ctxPath, String ext, + PrintWriter pw ) + { + if( debug > 0 ) + log( "Adding extension map for " + ctxPath + "/*." + ext ); + pw.println(indent + "AddHandler jserv-servlet ." + ext); + if ( "jsp".equals(ext) ) { + pw.println(indent + "# Forward non-cookie session requests"); + pw.println(indent + "<LocationMatch \"" + ctxPath + "/.*;jsessionid=.*\">"); + pw.println(indent + " SetHandler jserv-servlet"); + pw.println(indent + "</LocationMatch>"); + } + return true; + } + + /** Add a fulling specified Appache mapping. + */ + protected boolean addMapping( String fullPath, String app, PrintWriter pw ) { + if( debug > 0 ) + log( "Adding map for " + fullPath ); + pw.println(indent + "ApJServMount " + fullPath + " " + app ); + return true; + } + + + private void generateWelcomeFiles(Context context, PrintWriter pw ) { + String wf[]=context.getWelcomeFiles(); + if( wf==null || wf.length == 0 ) + return; + pw.print(indent + " DirectoryIndex "); + for( int i=0; i<wf.length ; i++ ) { + pw.print( wf[i] + " " ); + } + pw.println(); + } + + /** Mappings for static content. XXX need to add welcome files, + * mime mappings ( all will be handled by Mime and Static modules of + * apache ). + */ + private void generateStaticMappings(Context context, PrintWriter pw ) { + String ctxPath = context.getPath(); + + // Calculate the absolute path of the document base + String docBase = getApacheDocBase(context); + + if( !"".equals(ctxPath) ) { + // Static files will be served by Apache + pw.println(indent + "# Static files "); + pw.println(indent + "Alias " + ctxPath + " \"" + docBase + "\""); + pw.println(); + } else { + // For root context, ask user to update DocumentRoot setting. + // Using "Alias / " interferes with the Alias for other contexts. + pw.println(indent + + "# To correctly serve the Tomcat's root context, DocumentRoot must"); + pw.println(indent + + "# must be set to: \"" + docBase + "\""); + } + pw.println(indent + "<Directory \"" + docBase + "\">"); + pw.println(indent + " Options Indexes FollowSymLinks"); + + generateWelcomeFiles(context, pw); + + // XXX XXX Here goes the Mime types and welcome files !!!!!!!! + pw.println(indent + "</Directory>"); + pw.println(); + + + // Deny serving any files from WEB-INF + pw.println(); + pw.println(indent + + "# Deny direct access to WEB-INF and META-INF"); + pw.println(indent + "#"); + pw.println(indent + "<Location \"" + ctxPath + "/WEB-INF/*\">"); + pw.println(indent + " AllowOverride None"); + pw.println(indent + " deny from all"); + pw.println(indent + "</Location>"); + // Deny serving any files from META-INF + pw.println(); + pw.println(indent + "<Location \"" + ctxPath + "/META-INF/*\">"); + pw.println(indent + " AllowOverride None"); + pw.println(indent + " deny from all"); + pw.println(indent + "</Location>"); + if (File.separatorChar == '\\') { + pw.println(indent + "#"); + pw.println(indent + + "# Use Directory too. On Windows, Location doesn't" + + " work unless case matches"); + pw.println(indent + "#"); + pw.println(indent + + "<Directory \"" + docBase + "/WEB-INF/\">"); + pw.println(indent + " AllowOverride None"); + pw.println(indent + " deny from all"); + pw.println(indent + "</Directory>"); + pw.println(); + pw.println(indent + + "<Directory \"" + docBase + "/META-INF/\">"); + pw.println(indent + " AllowOverride None"); + pw.println(indent + " deny from all"); + pw.println(indent + "</Directory>"); + } + pw.println(); + } + + // -------------------- Utils -------------------- + + private String getAbsoluteDocBase(Context context) + { + // Calculate the absolute path of the document base + String docBase = context.getDocBase(); + if (!FileUtil.isAbsolute(docBase)){ + docBase = tomcatHome + "/" + docBase; + } + docBase = FileUtil.patch(docBase); + return docBase; + } + + private String getApacheDocBase(Context context) + { + // Calculate the absolute path of the document base + String docBase = getAbsoluteDocBase(context); + if (File.separatorChar == '\\') { + // use separator preferred by Apache + docBase = docBase.replace('\\','/'); + } + return docBase; + } + + private String getVirtualHostAddress(String vhost, String vhostip) { + if( vhostip == null ) { + if ( vhost != null && vhost.length() > 0 && Character.isDigit(vhost.charAt(0)) ) + vhostip=vhost; + else + vhostip="*"; + } + return vhostip; + } + +}//end class JservConfig