<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Mark S. Kolich</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/" />
    <link rel="self" type="application/atom+xml" href="http://mark.koli.ch/atom.xml" />
    <id>tag:,2008-10-25:/1</id>
    <updated>2010-08-23T16:11:23Z</updated>
    <subtitle>Engineer, Entrepreneur, Consultant</subtitle>
    <generator uri="http://www.sixapart.com/movabletype/">Movable Type 4.21-en</generator>

<entry>
    <title>Integrating Ant and Google&apos;s Closure Compiler</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/08/integrating-ant-and-googles-closure-compiler.html?rss" />
    <id>tag:mark.koli.ch,2010://1.240</id>

    <published>2010-08-21T18:45:00Z</published>
    <updated>2010-08-23T16:11:23Z</updated>

    <summary><![CDATA[With a little spare time on my hands, I dug into Google's Closure Compiler, an open-source tool that minifies and optimizes JavaScript for better performance.&nbsp; Unlike other JavaScript compression tools like JSMin, Packer or the YUI compressor, Google's Closure Compiler...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="ant" label="ant" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="google" label="google" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="java" label="java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="javascript" label="javascript" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[With a little spare time on my hands, I dug into <a href="http://code.google.com/closure/compiler/">Google's Closure Compiler</a>, an open-source tool that minifies and optimizes JavaScript for better performance.&nbsp; Unlike other JavaScript compression tools like <a href="http://www.crockford.com/javascript/jsmin.html">JSMin</a>, <a href="http://dean.edwards.name/packer/">Packer</a> or the <a href="http://developer.yahoo.com/yui/compressor/">YUI compressor</a>, Google's Closure Compiler converts your decent JavaScript into "better" JavaScript.&nbsp; It does so by searching for and pointing out obvious JavaScript pitfalls, deleting dead code, and optimizing what's left to make your web-applications more efficient.&nbsp; This all sounds great, so I gave it a whirl and eventually integrated it into my <a href="http://ant.apache.org/">Ant build</a> for a new project.&nbsp; Heck, I <a href="http://code.google.com/p/closure-compiler/issues/detail?id=221">even found a bug</a> and <a href="http://code.google.com/p/closure-compiler/issues/detail?id=220">suggested an enhancement</a>.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">1 - $jQuery Users be Warned</span><br /><br />Before you run off and have a field day with the Closure Compiler, you should know that optimizing jQuery with the <a href="http://code.google.com/closure/compiler/docs/api-tutorial3.html">compiler's ADVANCED_OPTIMIZATIONS</a> mode failed miserably.&nbsp; Even with a proper <a href="http://code.google.com/closure/compiler/docs/api-tutorial3.html#externs">JS externs</a> file, I could not get jQuery, or my JavaScript that uses jQuery, to compile under ADVANCED_OPTIMIZATIONS mode.&nbsp; Ok, actually it compiled fine, but when I tried to run this JavaScript in a browser: welcome to JavaScript Error City, population one.<br /><br />Eventually, I gave up and reverted back to SIMPLE_OPTIMIZATIONS mode instead. &nbsp;This works much better. &nbsp;If you know something I don't about compiling jQuery in advanced mode, please let me know.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">2 - Get 'er done</span><br /><br />First things first, you'll need to <a href="http://code.google.com/p/closure-compiler/downloads/list">download the Closure Compiler JAR</a> and integrate it into your Ant build file by defining a new Ant task:<br /><br /><pre class="prettyprint">&lt;property name="jscompjar"<br />  location="${lib.dir}/google-closure-compiler.jar" /&gt;<br /><br />&lt;taskdef name="jscomp"<br />  classname="com.google.javascript.jscomp.ant.CompileTask"<br />  classpath="${jscompjar}"/&gt;<br /></pre><br />Yay, now you have a &lt;jscomp&gt; Ant task.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">3 - Concatenate</span><br /><br />Next, you'll need Ant to concatenate each of your JavaScript files into a single file.&nbsp; Technically, you don't have to do this, but I found it much easier and I like having all of my JavaScript compressed into a single resource:<br /><br /><pre class="prettyprint">&lt;concat destfile="${build.dir}/js/project.js"&gt;<br />  &lt;filelist dir="${source.js.dir}"&gt;<br />    &lt;file name="underscore-1.0.4.min.js" /&gt;<br />    &lt;file name="jquery-1.4.2.min.js" /&gt;<br />    &lt;file name="jquery-ui-1.8.4.min.js" /&gt;<br />    &lt;file name="project.util.js" /&gt;<br />    &lt;file name="project.js" /&gt;<br />  &lt;/filelist&gt;<br />&lt;/concat&gt;</pre><br />Note that I'm using a <a href="http://ant.apache.org/manual/Types/filelist.html">FileList</a> instead of a <a href="http://ant.apache.org/manual/Types/fileset.html">FileSet</a>. &nbsp;This is because the order in which the resources are concatenated is important; they should be concatenated in the order in which they are required, and using a FileList ensures proper order.&nbsp; More on that <a href="http://mark.koli.ch/2009/12/a-better-way-to-include-javascript-and-css-in-your-web-apps-break-proxy-caching-too.html">here</a>.<br /><br />Also, note that if you eventually plan on compiling your JavaScript with ADVANCED_OPTIMIZATIONS enabled, you'll need to concatenate your JavaScript files together anyways.&nbsp; Further, using a single JavaScript resource across your entire site has obvious performance advantages.&nbsp; HTTP operations are usually expensive (<a href="http://about.digg.com/blog/technical-strategy-mdiggcom">especially on mobile devices</a>), so having a web-browser download one large JavaScript file with everything in it is usually better than having it download multiple smaller files.&nbsp; Of course, you <a href="http://blog.getify.com/2009/11/labjs-why-not-just-concat">could even take it a step further and use a tool like LABjs</a> to load your JavaScript dynamically, as needed. <br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">4 - Compile</span><br /><br />Ok, now that your JavaScript files have been concatenated into a single resource, let's use the &lt;jscomp&gt; Ant task we defined earlier to compile it:<br /><br /><pre class="prettyprint">&lt;jscomp compilationLevel="simple" warning="quiet"<br />  debug="false" output="${release.js.dir}/project.min.js"&gt;<br />  &lt;sources dir="${build.dir}/js"&gt;<br />    &lt;file name="project.js" /&gt;<br />  &lt;/sources&gt;<br />&lt;/jscomp&gt;<br /></pre><br />Assuming your code compiled cleanly, you'll have a Closure Compiler optimized JavaScript file sitting around ready for deployment!<br /><br />Enjoy.<br />]]>
        
    </content>
</entry>

<entry>
    <title>Spring 3 and Spring Security: Setting your Own Custom /j_spring_security_check Filter Processes URL</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/07/spring-3-and-spring-security-setting-your-own-custom-j-spring-security-check-filter-processes-url.html?rss" />
    <id>tag:mark.koli.ch,2010://1.239</id>

    <published>2010-07-25T02:20:00Z</published>
    <updated>2010-07-25T06:07:24Z</updated>

    <summary><![CDATA[While working on a new personal project, I decided to pick up and dig into Spring 3 MVC and Spring Security.&nbsp; I've touched both of these technologies here and there in a number of other projects, but this new opportunity...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="java" label="java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="spring" label="spring" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="springsecurity" label="spring-security" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[While working on a new personal project, I decided to pick up and dig into <a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/htmlsingle/spring-framework-reference.html">Spring 3 MVC</a> and <a href="http://static.springsource.org/spring-security/site/index.html">Spring Security</a>.&nbsp; I've touched both of these technologies here and there in a number of other projects, but this new opportunity has really opened the door for a deep dive into Spring.&nbsp; I know the Ruby fanatics out there would say I'm crazy, but once I got all of the awful XML configuration mess out of the way, I'm really enjoying Spring's stability and reliability.&nbsp; I know Ruby and Rails is hip and what not, but Spring 3 has really made an impression on me.&nbsp; It's hard to beat such a mature enterprise framework.<br /><br />I setup a few Spring 3 controllers, and integrated Spring Security into my web-app.&nbsp; All went great and so I added a simple form-based login to my Spring Security XML configuration.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">Problem: Overriding UsernamePasswordAuthenticationFilter</span><br /><br />When setting up a form-based login via a default Spring Security &lt;http:security&gt; configuration, Spring auto generates and configures a <a href="http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.html">UsernamePasswordAuthenticationFilter</a> bean.&nbsp; This filter, by default, responds to the URL /j_spring_security_check when processing a login POST from your web-form.&nbsp; First, I want to override Spring Security's default login process URL to /login instead of /j_spring_security_check.&nbsp; Second, I've configured a Spring 3 controller to display my login web-form when a user visits /login.<br /><br />That said, here's the underlying problem with Spring Security's default <a href="http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.html">UsernamePasswordAuthenticationFilter</a>: I want it to accept and process POST's to /login, but a GET or any HTTP method to /login should be forwarded to the next filter in the chain.&nbsp; Not surprisingly, you cannot do this with Spring Security's default <a href="http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.html">UsernamePasswordAuthenticationFilter</a> because it does not @Override the doFilter() method of <a href="http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html">AbstractAuthenticationProcessingFilter</a>.&nbsp; In short, there's no way to get and check the incoming HTTP request method and re-route it using the default <a href="http://static.springsource.org/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/UsernamePasswordAuthenticationFilter.html">UsernamePasswordAuthenticationFilter</a>.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">Solution: Write your own Spring Security Authentication Filter</span><br /><br />If you want a Spring controller to process GET requests to /login, but Spring Security to intercept and process a POST to /login, then you'll need to write your own Spring Security authentication filter.&nbsp; Here's the idea:<br /><br /><pre class="prettyprint">public class MyFilter extends AbstractAuthenticationProcessingFilter {<br /><br />  private static final String DEFAULT_FILTER_PROCESSES_URL = "/login";<br />  private static final String POST = "POST";<br /><br />  public MyFilter () {<br />    super(DEFAULT_FILTER_PROCESSES_URL);<br />  }<br /><br />  @Override<br />  public Authentication attemptAuthentication(HttpServletRequest request,<br />    HttpServletResponse response) throws AuthenticationException,<br />    IOException, ServletException {<br />    // You'll need to fill in the gaps here.  See the source of<br />    // UsernamePasswordAuthenticationFilter for a working implementation<br />    // you can leverage.<br />  }<br /><br />  @Override<br />  public void doFilter(ServletRequest req, ServletResponse res,<br />    FilterChain chain) throws IOException, ServletException {<br />    final HttpServletRequest request = (HttpServletRequest) req;<br />    final HttpServletResponse response = (HttpServletResponse) res;<br />    if(request.getMethod().equals(POST)) {<br />      // If the incoming request is a POST, then we send it up<br />      // to the AbstractAuthenticationProcessingFilter.<br />      super.doFilter(request, response, chain);<br />    } else {<br />      // If it's a GET, we ignore this request and send it<br />      // to the next filter in the chain.  In this case, that<br />      // pretty much means the request will hit the /login<br />      // controller which will process the request to show the<br />      // login page.<br />      chain.doFilter(request, response);<br />    }<br />  }<br /><br />}<br /></pre><br />Note the good stuff inside of <b>doFilter()</b>.&nbsp; If the incoming request method is a POST, then we send it up to our AbstractAuthenticationProcessingFilter to actually process the login.&nbsp; If it's a GET, or any other HTTP request method for that matter, we simply send it to the next filter in the chain.<br /><br />Finally, remember that you'll need to define your own <b>FORM_LOGIN_FILTER</b> inside of your &lt;security:http&gt; Spring Security XML configuration to override the default /j_spring_security_check URL:<br /><br /><pre class="prettyprint">&lt;security:http auto-config="false" use-expressions="true"<br />  entry-point-ref="LoginUrlAuthenticationEntryPoint"&gt;<br />  &lt;security:custom-filter position="FORM_LOGIN_FILTER" ref="MyFilter" /&gt;<br />&lt;/security:http&gt;<br /><br />&lt;bean id="LoginUrlAuthenticationEntryPoint"<br />  class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"&gt;<br />  &lt;property name="loginFormUrl" value="/login" /&gt;<br />&lt;/bean&gt;</pre><br />Enjoy!]]>
        
    </content>
</entry>

<entry>
    <title>Extended Thoughts on Custom Tiny URL Engines</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/05/extended-thoughts-on-custom-tiny-url-engines.html?rss" />
    <id>tag:mark.koli.ch,2010://1.237</id>

    <published>2010-05-31T23:35:00Z</published>
    <updated>2010-06-01T02:07:19Z</updated>

    <summary><![CDATA[A blog reader recently contacted me via email and asked, "hey, how does your koli.ch tiny URL thing work?"&nbsp; Well, I would be happy to explain.&nbsp; As previously discussed here, and here, I'm not using Apache's mod_rewrite RewriteMap engine.&nbsp; No,...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="apache" label="apache" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="base36" label="base36" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="http" label="http" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="php" label="php" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[A blog reader recently contacted me via email and asked, "hey, how does your koli.ch tiny URL thing work?"&nbsp; Well, I would be happy to explain.&nbsp; As previously discussed <a href="http://mark.koli.ch/2009/03/howto-make-your-own-tiny-url-shortener-service.html">here</a>, and <a href="http://mark.koli.ch/2009/03/update-howto-make-your-own-tiny-url-alias-service-tinyurl-bitly-twurlcc-etc-with-apache-rewritemap.html">here</a>, I'm not using <a href="http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html#rewritemap">Apache's mod_rewrite RewriteMap</a> engine.&nbsp; No, koli.ch tiny URL's are using an interesting combination of Apache's mod_rewrite RewriteRule directive and <a href="https://onyx.koli.ch/">Onyx</a>, which is basically <a href="http://mark.koli.ch/2009/10/onyx-my-custom-solution-to-the-digital-clutter-problem.html">PHP talking to a MySQL database</a>.<br /><br />So, let's follow <a href="http://koli.ch/x3dh">http://koli.ch/x3dh</a> through the redirection process.&nbsp; The "3dh" you see on the end of each URL is a <a href="http://mark.koli.ch/2009/10/base36-encoding-for-tiny-urls-with-php.html">base-36 encoded number</a>, that maps to a unique bookmark resource stored inside of <a href="https://onyx.koli.ch/">Onyx</a>.<br /><br /><span style="font-size: 1.1em; font-weight: bold;">Step 1 - koli.ch to mark.koli.ch</span><br /><br />First, you may have noticed that http://koli.ch always redirects to http://mark.koli.ch by default.&nbsp; This is accomplished using Apache's mod_rewrite in my virtual host configuration ...<br /><br /><pre class="prettyprint">RewriteCond %{HTTP_HOST} !^mark\.koli\.ch [NC]<br />RewriteRule ^/(.*)$ http://mark.koli.ch/$1 [R=301,L]</pre><br />You'll notice that the Perl compatible regular expression in the RewriteRule directive captures the URL-path content after the first slash, allowing me to gracefully forward URL's like <a href="http://koli.ch/about-mark.html">http://koli.ch/about-mark.html</a> to <a href="http://mark.koli.ch/about-mark.html">http://mark.koli.ch/about-mark.html</a>.&nbsp; Consequently, tiny URL's like http://koli.ch/x3dh are forwarded to http://mark.koli.ch/x3dh.&nbsp; And, this redirect is a <a href="http://en.wikipedia.org/wiki/HTTP_301">301 Moved Permanently</a> instead of a default 302 Found redirect.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">Step 2 - mark.koli.ch to onyx.koli.ch</span><br /><br />Next, in the same virtual host configuration, I use mod_rewrite's RewriteRule directive to match URL-paths that represent a tiny URL.&nbsp; As far as <a href="https://onyx.koli.ch/">Onyx</a> is concerned, all of my tiny URL's all start with "x" and contain at least one, but no more than ten, word characters following the "x" ...<br /><br /><pre class="prettyprint">RewriteCond %{REQUEST_URI} !\.(htm?l)$ [NC]<br />RewriteRule ^/x(\w{1,10})$ https://onyx.koli.ch/x$1 [L]</pre><br />Notice the regular expression group, I'm only capturing the word-characters following the "x" in the request URI, then appending them onto <a href="https://onyx.koli.ch/x">https://onyx.koli.ch/x</a> before handing it off to Onyx.&nbsp; Note that I have not specified the redirect response code in the RewriteRule options.&nbsp; In this case, Apache will <a href="http://en.wikipedia.org/wiki/HTTP_302">302 Found</a> redirect to Onyx, which tells search engines and other bots that this redirect is much less permanent than a <a href="http://en.wikipedia.org/wiki/HTTP_301">301 Moved Permanently</a>.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">Step 3 - onyx.koli.ch to ?</span><br /><br />Once the browser hits <a href="https://onyx.koli.ch/x">https://onyx.koli.ch/x</a>, I use RewriteRule one last time to call a redirection controller I wrote in PHP which actually takes the tiny URL, sanitizes the request URI then looks it up in a MySQL database to retrieve the final URL to redirect to.&nbsp; This is part of Onyx.&nbsp; Here's the pseudo code ... <br /><br /><pre class="prettyprint">&lt;?php<br /><br />$uri = (isset($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : null;<br />if(empty($uri)) {<br />  throw new InternalErrorException();<br />}<br /><br />// Strip the leading / from the front of the URI<br />$uri = preg_replace("/^\//","",$uri);<br />@list(, $resource) = preg_split("/[\/\?]/", $uri, -1, PREG_SPLIT_NO_EMPTY);<br />if(!empty($resource)) {<br />  $resource = preg_replace("/\D/","",$resource);<br />} else {<br />  throw new BadRequestException();<br />}<br /><br />// ... do some MySQL things here, look up the $resource in the DB.<br />$url = ... some URL from the database<br /><br />// Redirect the user to their destination, via an HTTP 302 Found<br />header(HTTP_302_FOUND);<br /><br />// Unset the Pragma HTTP response header; to avoid<br />// redirection issues on IE.<br />header(PRAGMA.": ");<br />header(CACHE_CONTROL.": no-store, no-cache");<br /><br />header(LOCATION_HEADER.': '.$url);<br /><br />?&gt;<br /></pre><br />That's it.&nbsp; Cheers.]]>
        
    </content>
</entry>

<entry>
    <title>MySQL Triggers and SUPER Privileges: &quot;Access denied; you need the SUPER privilege for this operation.&quot;</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/05/mysql-triggers-and-super-privileges-access-denied-you-need-the-super-privilege-for-this-operation.html?rss" />
    <id>tag:mark.koli.ch,2010://1.236</id>

    <published>2010-05-27T19:20:00Z</published>
    <updated>2010-05-27T19:33:41Z</updated>

    <summary><![CDATA[I just discovered that dealing with MySQL triggers, in many instances, is quite painful.&nbsp; For example, here's a trigger that deletes a bunch of rows in a table on every INSERT:delimiter |CREATE TRIGGER delete_expired_tweets AFTER INSERT ON tweets FOR EACH...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="mysql" label="mysql" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="security" label="security" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[I just discovered that dealing with MySQL triggers, in many instances, is quite painful.&nbsp; For example, here's a trigger that deletes a bunch of rows in a table on every INSERT:<br /><br /><pre class="prettyprint">delimiter |<br />CREATE TRIGGER delete_expired_tweets AFTER INSERT ON tweets<br />  FOR EACH ROW BEGIN<br />    DELETE FROM tweets WHERE DATEDIFF(NOW(), created_at) &gt; 365;<br />  END;<br />|<br /><br />delimiter ;<br /></pre><br />Ok, let's load this trigger into MySQL:<br /><br /><pre class="prettyprint">#/&gt; mysql -h myhost -u normaluser -p mydatabase<br />Enter password: **********<br />Welcome to the MySQL monitor.  Commands end with ; or \g.<br />Your MySQL connection id is 10<br />Server version: 5.0.41-community-nt MySQL Community Edition (GPL)<br /><br />Type 'help;' or '\h' for help. Type '\c' to clear the buffer.<br /><br />mysql&gt; source trigger.sql<br />ERROR 1227 (42000): Access denied; you need the SUPER privilege<br />    for this operation</pre><br />Access denied?&nbsp; I dug into it, and confirmed that <strong>you can only add triggers if your user account has the SUPER privilege enabled</strong>.&nbsp; You're probably thinking, "No kidding Sherlock, that's what the error message says."&nbsp; Yes, I know that's what the error message says.&nbsp; But here's the problem.&nbsp; Normal database users created using "GRANT ALL PRIVILEGES ON database.* TO..." will <strong>not have the SUPER privilege</strong> assigned to them by default.&nbsp; As <a href="http://dev.mysql.com/doc/refman/5.0/en/privileges-provided.html#priv_super">described here</a>, the SUPER privilege in MySQL let's the account do some things that normal database users, in most environments, should not be able to do (like kill database threads, modify global system variables, etc.).&nbsp; As a result, it's a very bad idea to grant the SUPER privilege to normal database users, even if they just need the SUPER privilege to load a trigger.&nbsp; You know better than that!<br /><br />Even worse, suppose you GRANT SUPER PRIVILEGES to a single user, on a single database.&nbsp; Well, that still won't be enough to load a trigger.&nbsp; Unfortunately, loading triggers requires SUPER PRIVILEGES at the global level (e.g., GRANT SUPER PRIVILEGES ON *.*).&nbsp; Again, it's a very bad ideal to grant normal database users the SUPER privilege.<br /><br />So how exactly am I supposed to load this trigger?&nbsp; Well as far as I can tell, assuming I refuse to give myself SUPER PRIVILEGES for the reasons I just explained, I have two options:<br /><br /><ol><li>Don't use triggers, and find another way to cleanup rows in my table.</li><li>Log into the database as root/admin and load the trigger on behalf of the normal user.&nbsp; If I wasn't the owner of this database server, this would probably involve asking my database administrator to load the trigger for me.<br /></li></ol><br />This is just one of many common annoyances with MySQL.&nbsp; Sucks, I know.<br />]]>
        
    </content>
</entry>

<entry>
    <title>OAuth and the Twitter API: Generate a one-time access token and token secret</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/05/oauth-and-the-twitter-api-generate-a-one-time-access-token-and-token-secret.html?rss" />
    <id>tag:mark.koli.ch,2010://1.235</id>

    <published>2010-05-23T19:05:00Z</published>
    <updated>2010-05-23T19:10:50Z</updated>

    <summary><![CDATA[You may have heard that Twitter plans to stop supporting HTTP Basic Authentication on June 30, 2010.&nbsp; This means that starting on June 30th, to use Twitter's API, your application must support OAuth.&nbsp; OAuth is a nice step up from...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="api" label="api" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="java" label="java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="php" label="php" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="twitter" label="twitter" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[You may have heard that <a href="http://groups.google.com/group/twitter-api-announce/browse_thread/thread/f71eb68600996af8?pli=1">Twitter plans to stop supporting HTTP Basic Authentication on June 30, 2010</a>.&nbsp; This means that starting on June 30th, to use <a href="http://apiwiki.twitter.com/Twitter-API-Documentation">Twitter's API</a>, your application must support <a href="http://oauth.net/">OAuth</a>.&nbsp; OAuth is a nice step up from basic authentication but it makes developing web or desktop applications that communicate with Twitter, slightly more painful.&nbsp; Well, painful isn't the right word, but you definitely have to jump through more hoops to get things to work.&nbsp; Gone are the days of simply sending a username and password to the API.<br /><br />In response to this change, Twitter API proxy services like <a href="http://www.supertweet.net/">SuperTweet</a> have popped up.&nbsp; Turns out, if you know what you're doing with OAuth, SuperTweet and other API proxy services are entirely unnecessary, not to mention unsafe.&nbsp; You're better off upgrading your applications to use OAuth the right way, instead of making them rely on potentially insecure third-party proxy services.&nbsp; And again, it's not difficult, just a bit annoying.<br /><br /><span style="font-size: 1.1em; font-weight: bold;">Scenario</span><br /><br />You're a developer, and you need to write some code that pulls in Tweets from one or more users.&nbsp; Maybe you also need to pull down a list of followers for each of these users.&nbsp; Not surprisingly, it's entirely unreasonable to ask each of them to authenticate your application using OAuth.&nbsp; You just want to write code that pulls down their public timeline, followers, etc. avoiding the whole OAuth dance with each user, every time.<br /><br /><span style="font-size: 1.1em; font-weight: bold;">Solution</span><br /><br />Register a new application on Twitter.&nbsp; Then, dig into your application control panel and find your new "single access token" and "single access token secret" for the application you just registered.<br /><br />As <a href="http://dev.twitter.com/pages/oauth_single_token">described here</a>, "Twitter offers the ability for you to retrieve a single access token (complete with oauth_token_secret) from application detail pages found in your application control panel.&nbsp; This is ideal for applications migrating to OAuth with single-user use cases ... By using a single access token, you don't need to implement the entire OAuth token acquisition dance. Instead, you can pick up from the point where you are working with an access token to make signed requests for Twitter resources."<br /><br />This token and token secret is as close as you'll get to a 
username/password equivalent in OAuth.&nbsp; In other words, once you have this one-time token and token secret for your application, you can issue signed OAuth requests against the Twitter API just like you would with a basic username and password.&nbsp; If you want to think about it this way, the token is like your username and the token secret is like your password.&nbsp; Don't share them.&nbsp; Once you have these credentials, you can pull in Tweets for any public user, get their followers, read the public timeline, etc.<br /><br />Here are <a href="http://dev.twitter.com/pages/oauth_single_token">several examples in a number of popular languages showing how you can use this one-time token and token secret</a> in your project.<br /><br />Yay for OAuth.]]>
        
    </content>
</entry>

<entry>
    <title>Formatting a Java Date into a Specific TimeZone and Conversion Between TimeZone&apos;s</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/05/formatting-a-java-date-into-a-specific-timezone.html?rss" />
    <id>tag:mark.koli.ch,2010://1.234</id>

    <published>2010-05-15T18:51:00Z</published>
    <updated>2010-05-15T20:33:22Z</updated>

    <summary><![CDATA[Date objects in Java, and probably most other robust languages, simply represent a snapshot of a point in time.&nbsp; In other words, java.util.Date knows nothing about the time zone you're referring to when instantiating or manipulating a Date object.&nbsp; Fact...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="code" label="code" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="howto" label="howto" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="java" label="java" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[Date objects in Java, and probably most other robust languages, simply represent a snapshot of a point in time.&nbsp; In other words, <a href="http://java.sun.com/javase/6/docs/api/java/util/Date.html">java.util.Date</a> knows nothing about the time zone you're referring to when instantiating or manipulating a Date object.&nbsp; Fact is, java.util.Date does not have to care about your time zone, because internally a Date is really nothing more than a count of the number of milliseconds since the standard base time known as "<a href="http://en.wikipedia.org/wiki/Unix_time">the epoch</a>", namely January 1, 1970, 00:00:00 GMT.<br /><br />If it helps, think about it this way: X milliseconds since the epoch is X milliseconds since the epoch in the US-Pacific time zone, X milliseconds since the epoch in GMT-0 (London), X milliseconds since the epoch in India, etc.&nbsp; In short, when it's 1273947282085 milliseconds since the epoch, it's 1273947282085 milliseconds since the epoch everywhere in the world at the same time regardless of what time zone you're sitting in.&nbsp; And since Java's util.Date is simply a snapshot of the number of milliseconds at a specific point in time, you can see why Date doesn't care about your time zone.&nbsp; It's irrelevant.<br /><br />But Mark, <b>how do I convert a java.util.Date into a different time zone</b>?&nbsp; You can't, and that question makes no sense.&nbsp; That's like asking me to "take a picture of the sound."&nbsp; Here's some <a href="http://en.wikipedia.org/wiki/Shit#Shortening_of_bullshit">B.S.</a> code that you should not use, but I've put it here for illustrative purposes:<br /><br /><pre class="prettyprint">// Do NOT use this, it does nothing and makes no sense.<br />public static final Date convertIntoTimeZone(Date date, TimeZone tz) {<br />  final Calendar cal = Calendar.getInstance();<br />  cal.setTime(date);<br />  cal.setTimeZone(tz);<br />  return cal.getTime();<br />}</pre><br />You can't convert a Date into a different time zone, but you can use Java's handy <a href="http://java.sun.com/javase/6/docs/api/java/text/DateFormat.html">DateFormat</a> class to format a Date into the time zone of your choice.&nbsp; To put it differently, let Date do its thing.&nbsp; Then, <b>when you're ready to display or print out a String representation of Date, that's when you tell DateFormat what time zone you want it in</b>.&nbsp; So, here's some code that makes sense, and actually works:<br /><br /><pre class="prettyprint">final Date currentTime = new Date();<br /><br />final SimpleDateFormat sdf =<br />        new SimpleDateFormat("EEE, MMM d, yyyy hh:mm:ss a z");<br /><br />// Give it to me in US-Pacific time.<br />sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));<br />System.out.println("LA time: " + sdf.format(currentTime));<br /><br />// Give it to me in GMT-0 time.<br />sdf.setTimeZone(TimeZone.getTimeZone("GMT"));<br />System.out.println("GMT time: " + sdf.format(currentTime));<br /><br />// Or maybe Zagreb local time.<br />sdf.setTimeZone(TimeZone.getTimeZone("Europe/Zagreb"));<br />System.out.println("Zagreb time: " + sdf.format(currentTime));<br /><br />// Even 10 hours and 10 minutes ahead of GMT<br />sdf.setTimeZone(TimeZone.getTimeZone("GMT+0010"));<br />System.out.println("10/10 ahead time: " + sdf.format(currentTime));<br /></pre><br />Yay for Dates and time zones!&nbsp; Enjoy.]]>
        
    </content>
</entry>

<entry>
    <title>HTTP Digest Access Authentication using MD5 and HttpClient 4</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/05/http-digest-access-authentication-with-md5-and-httpclient-4.html?rss" />
    <id>tag:mark.koli.ch,2010://1.233</id>

    <published>2010-05-04T20:30:00Z</published>
    <updated>2010-05-04T20:53:58Z</updated>

    <summary><![CDATA[Dealing with HTTP's Digest authentication mechanism isn't too bad once you have the basic building blocks in place.&nbsp; Luckily HttpClient 4 can automatically solve many types of authentication challenges for you, if used correctly.&nbsp; Using HttpClient 4, I built an...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="code" label="code" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="http" label="http" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="httpclient" label="httpclient" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="java" label="java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="md5" label="md5" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="soap" label="soap" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[Dealing with HTTP's Digest authentication mechanism isn't too bad once you have the basic building blocks in place.&nbsp; Luckily <a href="http://hc.apache.org/httpcomponents-client/index.html">HttpClient 4</a> can automatically solve many types of authentication challenges for you, if used correctly.&nbsp; Using <a href="http://hc.apache.org/httpcomponents-client/index.html">HttpClient 4</a>, I built an app that authenticates against a SOAP based web-service requiring WWW-Authenticate Digest authentication.&nbsp; In a nutshell, the fundamental principal behind HTTP Digest authentication is simple:<br /><br /><ul><li>The client asks for a page that requires authentication.<br /></li><li>The server responds with an HTTP 401 response code, providing the authentication realm and a randomly-generated, single-use value called a "nonce".&nbsp; The authentication "challenge" itself is encapsulated inside of the WWW-Authenticate HTTP response header.<br /></li><li>The client "solves" the authentication challenge and a solution is sent back to the web-server via the HTTP Authorization header on a subsequent request.&nbsp; The solution usually contains some type of MD5 hashed mess of your username, password, and "nonce".<br /></li><li>Assuming the solution is acceptable the server responds with a successful type response, usually an HTTP 200 OK.<br /></li></ul><br />Here's a sample with a bit of pseudo code mixed in (so, you get the idea):<br /><br /><pre class="prettyprint">// A org.apache.http.impl.auth.DigestScheme instance is<br />// what will process the challenge from the web-server<br />final DigestScheme md5Auth = new DigestScheme();<br /><br />// This should return an HTTP 401 Unauthorized with<br />// a challenge to solve.<br />final HttpResponse authResponse =<br />        doPost(url, postBody, contentType);<br /><br />// Validate that we got an HTTP 401 back<br />if(authResponse.getStatusLine().getStatusCode() ==<br />      HttpStatus.SC_UNAUTHORIZED) {<br />  if(authResponse.containsHeader("WWW-Authenticate")) {<br />    // Get the challenge.<br />    final Header challenge =<br />            authResponse.getHeaders("WWW-Authenticate")[0];<br />    // Solve it.<br />    md5Auth.processChallenge(challenge);<br />    // Generate a solution Authentication header using your<br />    // username and password.<br />    final Header solution = md5Auth.authenticate(<br />            new UsernamePasswordCredentials(username, password),<br />            new BasicHttpRequest(HttpPost.METHOD_NAME,<br />                  new URL(url).getPath()));<br />    // Do another POST, but this time include the solution<br />    // Authentication header as generated by HttpClient.<br />    final HttpResponse goodResponse =<br />            doPost(url, postBody, contentType, solution);<br />    // ... do something useful with goodResponse, which assuming<br />    // your credentials were valid, should contain the data you<br />    // requested.<br />  } else {<br />    throw new Error("Web-service responded with Http 401, " +<br />      "but didn't send us a usable WWW-Authenticate header.");<br />  }<br />} else {<br />  throw new Error("Didn't get an Http 401 " +<br />      "like we were expecting.");<br />}</pre><br />Enjoy.<br />]]>
        
    </content>
</entry>

<entry>
    <title>Pingdom Incorrectly Reports Page Load Time</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/04/pingdom-incorrectly-reports-page-load-time.html?rss" />
    <id>tag:mark.koli.ch,2010://1.232</id>

    <published>2010-04-24T20:00:00Z</published>
    <updated>2010-04-24T20:03:16Z</updated>

    <summary><![CDATA[I recently discovered that Pingdom, in many instances, does not correctly report page-load time.&nbsp; As it turns out, when you ask Pingdom to tell you how fast your site loads it will incorrectly download all images and other resources referenced...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="commonsense" label="commonsense" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="compression" label="compression" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="css" label="css" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[I recently discovered that <a href="http://www.pingdom.com/">Pingdom</a>, in many instances, does not correctly report page-load time.&nbsp; As it turns out, when you ask Pingdom to tell you how fast your site loads it will incorrectly download all images and other resources referenced in your CSS, regardless of whether they are actually used on your site or not.&nbsp; Of course this isn't ideal, because Pingdom will claim the page you're testing takes longer to load than it really does!&nbsp; By comparison, smart web-browsers do not blindly download every image referenced in the CSS, on every page.&nbsp; Instead, they download the resources as needed.<br /><br />For folks who really care about site optimization and performance, 
this is a big deal.&nbsp; Especially if you're hired to help a client optimize the page-load time of their web-site, but services like Pingdom lie and report junk.<br /><br />Here's an example; imagine this is your homepage:<br /><br /><pre class="prettyprint">&lt;html&gt;<br />&lt;head&gt;<br /> &lt;title&gt;My Homepage&lt;/title&gt;<br /> &lt;style type="text/css"&gt;<br />  #header {<br />    background: black url(/header.png) no-repeat top left;<br />  }<br />  #homepage {<br />    background: white url(/homepage.png) no-repeat top left;<br />  }<br />  #aboutus {<br />    background: white url(/about-us.png) no-repeat top left;<br />  }<br />  #contact {<br />    background: white url(/contact.png) no-repeat top left;<br />  }<br />  #footer {<br />    background: black url(/footer.png) no-repeat top left;<br />  }<br /> &lt;/style&gt;<br />&lt;/head&gt;<br /> &lt;body&gt;<br />  &lt;div id="header"&gt;header&lt;/div&gt;<br />  &lt;div id="homepage"&gt;content&lt;/div&gt;<br />  &lt;div id="footer"&gt;footer&lt;/div&gt;<br /> &lt;/body&gt;<br />&lt;/html&gt;<br /></pre><br />Ok, so you've got a header and a footer that will appear on every page.&nbsp; In addition, you've got a few other CSS selectors for the homepage, for your "about us" page, and for your "contact us" page.&nbsp; In a real web-browser, when you visit this page the browser will load the following images as referenced in your CSS:<br /><br /><ul><li>header.png</li><li>homepage.png</li><li>footer.png</li></ul>However, when you test this same page using Pingdom, Pingdom will claim your site is loading the following resources:<br /><br /><ul><li>header.png</li><li>homepage.png</li><li>about-us.png</li><li>contact.png</li><li>footer.png</li></ul>Wait a second, that's not accurate at all!&nbsp; On this hypothetical homepage, I'm definitely not using #aboutus or #contact anywhere, so why should the resources associated with these selectors be factored into my page load time?&nbsp; In reality, they shouldn't be because that's not how real web-browsers work.<br /><br />To further validate my findings, I used the Rackspace Cloud and allocated a new machine with a running web-server.&nbsp; I loaded this hypothetical homepage HTML onto it, and asked Pingdom to tell me how fast it loads:<br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2010/04/24/pingdom-lies-snap1.jpg"><img alt="pingdom-lies-snap1.jpg" src="http://mark.koli.ch/assets_c/2010/04/pingdom-lies-snap1-thumb-400x202.jpg" class="mt-image-none" style="" height="202" width="400" /></a></span><br /><br />Wrong!&nbsp; I'm not using aboutus.png or contactus.png anywhere on the homepage, so why should Pingdom factor the load time of those images into my test results?&nbsp; In contrast, using Firefox and <a href="http://code.google.com/p/httpfox/">HttpFox</a>, I verified that real web-browsers only load the resources they need to properly render the page:<br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2010/04/24/pingdom-lies-browser-is-better.jpg"><img alt="pingdom-lies-browser-is-better.jpg" src="http://mark.koli.ch/assets_c/2010/04/pingdom-lies-browser-is-better-thumb-400x200.jpg" class="mt-image-none" style="" height="200" width="400" /></a></span><br /><br />Here you can see the correct resources are loaded in Firefox: header.png, homepage.png, and footer.png.<br /><br />Bottom line, you really shouldn't use Pingdom and other external performance analysis tools as "bibles" for improving your page-load time.&nbsp; Clearly they're only tools to help you identify obvious problems, and are definitely not a complete solution.&nbsp; For more accurate results, I would suggest using tools like <a href="http://developer.yahoo.com/yslow/">YSlow</a> and <a href="http://code.google.com/speed/page-speed/">Google's Page Speed</a>.<br /><br />Cheers.<br />]]>
        
    </content>
</entry>

<entry>
    <title>Understanding Java&apos;s CountDownLatch and CyclicBarrier</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/04/understanding-javas-countdownlatch.html?rss" />
    <id>tag:mark.koli.ch,2010://1.231</id>

    <published>2010-04-09T18:20:00Z</published>
    <updated>2010-04-14T02:39:33Z</updated>

    <summary><![CDATA[While working on some nifty multi-threaded Java recently, a colleague pointed me to a few really useful Java classes: CountDownLatch and CyclicBarrier.&nbsp; My code was quite typical, a parent worker thread spawns a bunch of children to do real work,...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="code" label="code" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="java" label="java" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[<span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2010/04/09/countdownlatch-demo-screenshot.png"><img alt="countdownlatch-demo-screenshot.png" src="http://mark.koli.ch/assets_c/2010/04/countdownlatch-demo-screenshot-thumb-200x208.png" class="mt-image-left" style="margin: 0pt 20px 20px 0pt; float: left;" height="208" width="200" /></a></span>While working on some nifty multi-threaded Java recently, a colleague pointed me to a few really useful Java classes: <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/CountDownLatch.html">CountDownLatch</a> and <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/CyclicBarrier.html">CyclicBarrier</a>.&nbsp; My code was quite typical, a parent worker thread spawns a bunch of children to do real work, and needs to wait for the children to finish before continuing.&nbsp; The catch though, is that the child worker threads may or may not finish successfully, and in all likelihood will finish at different times.&nbsp; Even so, the parent thread must wait until all of its children have finished because the parent can only make forward progress once the children are complete.&nbsp; I <a href="http://mark.koli.ch/2010/04/09/kolich.com-countdownlatch-swing-example.zip">whipped up a little demo</a> (screen shot left) that spawns five worker threads which update a JProgressBar at a random interval.&nbsp; The demo finishes once each progress bar hits 100%.]]>
        <![CDATA[<br /><span style="font-size: 1.1em; font-weight: bold;">1 - CountDownLatch</span><br /><br />Meet <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/CountDownLatch.html">CountDownLatch</a>.&nbsp; As described <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/CountDownLatch.html">in the Java 6 API docs</a>, a CountDownLatch is "a synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes."&nbsp; In other words, the developer says new CountDownLatch(N) which waits for N threads to finish before the latch is "released" allowing the calling thread to make forward progress.&nbsp; Couldn't be more perfect here.&nbsp; To make my life a little easier, I wrote a few wrapper classes that encapsulate a CountDownLatch which allow me to easily synchronize on a List&lt;BaseWorker&gt;, a list of worker threads:<br /><br /><ul><li><a href="http://mark.koli.ch/2010/04/09/ThreadRunner.java">ThreadRunner.java</a> -- A class that accepts a List&lt;BaseWorker&gt; (a List of BaseWorker's), creates a new CountDownLatch(list.size()), starts each BasedWorker then allows the developer to await() on the runner for all BaseWorker's to finish.<br /><br /></li><li><a href="http://mark.koli.ch/2010/04/09/BaseWorker.java">BaseWorker.java</a> -- An abstract class that represents each worker thread, and defines a set of methods each BaseWorker must implement to be used with a ThreadRunner.<br /></li></ul><br />So, using these wrappers, let's create a new worker:<br /><br /><pre class="prettyprint">public class MyWorker extends BaseWorker {<br /><br />  private int someField_;<br /><br />  public MyWorker(int worker) {<br />    super();<br />    someField_ = worker;<br />    // ...<br />  }<br /><br />  @Override<br />  public void myRun() throws Exception {<br />    // ...<br />  }<br /><br />  @Override<br />  public String getWorkerName() {<br />    return String.format("%s #%s",<br />        getClass().getSimpleName(), someField_);<br />  }<br /><br />}<br /></pre><br />Now let's setup a new ThreadRunner that will take a bunch of BaseWorker's, start them, then wait for all to finish:<br /><br /><pre class="prettyprint">public class MyRunner {<br /><br />  private static final List&lt;BaseWorker&gt; workers__;<br />  static {<br />    workers__ = new ArrayList&lt;BaseWorker&gt;();<br />    workers__.add(new MyWorker(1));<br />    workers__.add(new MyWorker(2));<br />    workers__.add(new MyWorker(3));<br />  }<br /><br />  public static void main(String[] args) {<br />    final ThreadRunner runner = new ThreadRunner(workers__);<br />    // Start all of the threads in this runner.<br />    runner.start();<br />    // Wait for all of the threads to finish.<br />    runner.await();<br />    // Did all of our workers complete without error?<br />    if(runner.wasSuccessful()) {<br />      System.out.println("All workers finished cleanly.");<br />    } else {<br />      System.out.println("Not all workers finished cleanly.");<br />    }<br />  }<br /><br />}<br /></pre><br />In this example, I built a List of BaseWorker's, gave the list to the ThreadRunner and asked the runner to start them.&nbsp; Upon calling runner.await(), the ThreadRunner blocks waiting for all of the workers to finish.&nbsp; Note that my concept of "finish" here means either successfully, or unsuccessfully (an Exception or Error case).&nbsp; Subsequently, I call runner.wasSuccessful() to check if all of the workers finished cleanly, basically asking the runner did all of your workers finish without throwing any Exception's or Error's?<br /><br />If you're interested, you can <a href="http://mark.koli.ch/2010/04/09/kolich.com-countdownlatch-swing-example.zip">download my complete ThreadRunner demo/example</a> that further demonstrates the usage of these wrapper classes using Swing and several JProgressBar's.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">2 - CyclicBarrier</span><br /><br />A <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/CyclicBarrier.html">CyclicBarrier</a> is similar to that of a CountDownLatch, except that a <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/CyclicBarrier.html">CyclicBarrier</a> is "a synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point."&nbsp; Like a CountDownLatch, a CyclicBarrier can be used to synchronize a number of threads.&nbsp; But instead of exiting upon completion, theads using a CyclicBarrier await() for all other threads in the pool to finish.&nbsp; Here's a usage example of a CyclicBarrier built around my BaseWorker class:<br /><br /><pre class="prettyprint">public class MyCyclicWorker extends BaseWorker {<br /><br />  private CyclicBarrier barrier_;<br /><br />  public MyWorker(CyclicBarrier barrier) {<br />    super();<br />    barrier_ = barrier;<br />    // ...<br />  }<br /><br />  @Override<br />  public void myRun() throws Exception {<br />    // ...<br />    // Wait here for all other threads<br />    // in the CyclicBarrier to finish.<br />    barrier_.await();<br />  }<br /><br />  @Override<br />  public String getWorkerName() {<br />    return getClass().getSimpleName();<br />  }<br /><br />} <br /></pre><br />Here's the class that starts up a bunch of these MyCyclicWorkers, then runs a single "cleanup" thread once all of the workers are done:<br /><br /><pre class="prettyprint">public class CyclicExample {<br /><br />  private static final int CYCLIC_THREADS = 5;<br /><br />  public static void main(String[] args) {<br />    final CyclicBarrier barrier =<br />            new CyclicBarrier(CYCLIC_THREADS,<br />              new Runnable() {<br />                @Override<br />                public void run() {<br />                  // Cleanup thread, or completion thread.<br />                  // Called when all of the worker threads<br />                  // are finished.<br />                  // ...<br />                }<br />              });<br />    for(int i=0; i &lt; CYCLIC_THREADS; ++i) {<br />      new MyCyclicWorker(barrier).start();<br />    }<br />    // ...<br />  }<br /><br />}<br /></pre><br />Enjoy!<br />]]>
    </content>
</entry>

<entry>
    <title>HOWTO: Ignore Files and Directories in Subversion (SVN)</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/04/howto-ignore-files-and-directories-in-subversion-svn.html?rss" />
    <id>tag:mark.koli.ch,2010://1.230</id>

    <published>2010-04-08T16:21:10Z</published>
    <updated>2010-04-08T16:51:54Z</updated>

    <summary><![CDATA[Subversion is fun.&nbsp; Especially when it tries to commit intermediate build files and other junk you want to keep out of your SVN repository.&nbsp; In my environment, Ant is configured to place intermediate build files into my build/ directory.&nbsp; The...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="howto" label="howto" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="svn" label="svn" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="tips" label="tips" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[Subversion is fun.&nbsp; Especially when it tries to commit intermediate build files and other junk you want to keep out of <a href="http://mark.koli.ch/2010/03/howto-setting-up-your-own-svn-server-using-apache-and-mod-dav-svn.html">your SVN repository</a>.&nbsp; In my environment, <a href="http://ant.apache.org/">Ant</a> is configured to place intermediate build files into my build/ directory.&nbsp; The final product is then packaged up and placed into my dist/ directory.&nbsp; So, I need SVN to ignore everything inside of my build/ directory since the contents of this folder are going to change on every build.<br /><br />Meet <b>svn propedit svn:ignore</b> ...<br /><br /><pre class="prettyprint">#/&gt; svn propedit svn:ignore build<br />#/&gt; svn -m "ignoring build" commit build<br /></pre><br />When you run <b>svn propedit</b>, your local text editor will open (usually vi) which will allow you to specify a series of file names to ignore under build/.&nbsp; If you want to ignore all files and folders, just enter a single * wildcard character, save, and commit:<br /><br /><pre class="prettyprint">*</pre><br />Or, maybe you want to ignore just .class files:<br /><br /><pre class="prettyprint">*.class</pre><br />Of course, if you want to use another editor like emacs to set your ignore properties, you can do so by exporting SVN_EDITOR before running <b>svn propedit</b>:<br /><br /><pre class="prettyprint">#/&gt; export SVN_EDITOR=emacs</pre><br />Enjoy!]]>
        
    </content>
</entry>

<entry>
    <title>HOWTO: Scheduling Cron Style Timers and Jobs with Spring and Quartz</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/03/howto-scheduling-cron-style-timers-and-jobs-with-spring-and-quartz.html?rss" />
    <id>tag:mark.koli.ch,2010://1.229</id>

    <published>2010-03-27T17:25:00Z</published>
    <updated>2010-03-27T17:31:04Z</updated>

    <summary><![CDATA[Spring Timer's are great if you need a job or task to run on a fixed interval.&nbsp; For example, if you need a bean to wake up and run itself every 30-minutes.&nbsp; But what if you need finer control over...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="code" label="code" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="cron" label="cron" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="howto" label="howto" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="java" label="java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="quartz" label="quartz" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="spring" label="spring" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[<a href="http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/scheduling/timer/TimerFactoryBean.html">Spring Timer's are great</a> if you need a job or task to run on a fixed interval.&nbsp; For example, if you need a bean to wake up and run itself every 30-minutes.&nbsp; But what if you need finer control over the scheduling of a timer?&nbsp; Say you need a job to run every morning at 3AM, regardless of when the JVM is started.&nbsp; Or, say you need a bean to run "every half hour between the hours of 8 AM and 10 AM on the 5th and 20th of every month."&nbsp; You won't get that type of granular control with a <a href="http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/scheduling/timer/TimerFactoryBean.html">straight-up Spring Timer</a>.<br /><br />Meet <a href="http://www.quartz-scheduler.org/download/index.html">Quartz</a>, a full-featured, open source job scheduling service that can be integrated with, or used along side virtually any Java EE or Java SE application.&nbsp; Similar to a native <a href="http://en.wikipedia.org/wiki/Cron">UNIX Cron job</a>, Quartz lets you define <a href="http://www.quartz-scheduler.org/docs/api/org/quartz/CronExpression.html">powerful CronExpression's</a> that give you very precise control over when a job or task is started and on what interval.<br /><br />Setting up Quartz with your Spring powered web-application is easy.]]>
        <![CDATA[<br /><span style="font-size: 1.1em; font-weight: bold;">1 - Download Quartz</span><br /><br />The <a href="http://www.quartz-scheduler.org/download/index.html">latest Quartz libraries are available here</a>.&nbsp; Download them, and add to your build-path and build-files accordingly.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">2 - Add Beans to Your applicationContext.xml</span><br /><br />In your <b>applicationContext.xml</b>, add the necessary beans:<br /><br /><pre class="prettyprint">&lt;!-- this is the bean we're going to run on a schedule<br />      with Quartz; we'll call it UpdaterOnScheduleExecutor.<br />      The class is com.kolich.app.beans.UpdaterBean --&gt;<br />&lt;bean id="UpdaterOnScheduleExecutor"<br />  class="com.kolich.app.beans.UpdaterBean"<br />  init-method="init"<br />  depends-on="AnotherBean"&gt;<br />  &lt;property name="someProperty" value="/WEB-INF/something" /&gt;<br />&lt;/bean&gt;<br /><br />&lt;!-- quartz stuff below --&gt;<br /><br />&lt;!-- UpdaterOnScheduleJobDetail defines a bean that points to<br />       UpdaterOnScheduleExecutor and says to call its execute()<br />       method when the trigger is fired --&gt;<br />&lt;bean id="UpdaterOnScheduleJobDetail"<br />  class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"&gt;<br />  &lt;property name="targetObject" ref="UpdaterOnScheduleExecutor"/&gt;<br />  &lt;!-- the method to call inside of<br />        com.kolich.app.beans.UpdaterBean --&gt;<br />  &lt;property name="targetMethod" value="execute"/&gt;<br />&lt;/bean&gt;<br /><br />&lt;!-- here's where we use the Cron like scheduling expression<br />      to define when the bean is run. --&gt;<br />&lt;bean id="UpdaterOnScheduleJob"<br />  class="org.springframework.scheduling.quartz.CronTriggerBean"&gt;<br />  &lt;property name="jobDetail" ref="UpdaterOnScheduleJobDetail" /&gt;<br />  &lt;!-- run every morning at 3AM --&gt;<br />  &lt;property name="cronExpression" value="0 0 3 * * ?" /&gt;<br />&lt;/bean&gt;<br /><br />&lt;!-- the scheduler factory is what does the work to setup<br />       the triggers --&gt;<br />&lt;bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean"&gt;<br />  &lt;property name="triggers"&gt;<br />      &lt;list&gt;<br />          &lt;ref bean="UpdaterOnScheduleJob" /&gt;<br />      &lt;/list&gt;<br />  &lt;/property&gt;<br />&lt;/bean&gt;<br /></pre><br />Note that you can add as many jobs/triggers as desired to the <a href="http://static.springsource.org/spring/docs/2.5.6/api/org/springframework/scheduling/quartz/SchedulerFactoryBean.html">Quartz SchedulerFactoryBean</a>.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">3 - Sample Cron Expressions</span><br /><br />Here are some other sample <b>cronExpression's</b> you might enjoy (I borrowed these <a href="http://www.quartz-scheduler.org/docs/tutorial/TutorialLesson06.html">from the Quartz trigger tutorial</a>) ...<br /><br />Fires every five minutes:<br /><br /><pre class="prettyprint">"0 0/5 * * * ?"<br /></pre><br />Fires every 5 minutes, at 10 seconds after the minute:<br /><br /><pre class="prettyprint">"10 0/5 * * * ?"<br /></pre><br />Fires at 10:30, 11:30, 12:30, and 13:30, on every Wednesday and Friday:<br /><br /><pre class="prettyprint">"0 30 10-13 ? * WED,FRI"<br /></pre><br />Fires every half hour between the hours of 8 am and 10 am on the 5th and 20th of every month:<br /><br /><pre class="prettyprint">"0 0/30 8-9 5,20 * ?"<br /></pre><br />Enjoy!<br />]]>
    </content>
</entry>

<entry>
    <title>Fixing Blown Thermal Fuse in Krups KM7000 Coffee Maker; Triangle Head/Bit Tamper Proof Screws Too!</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/03/fixing-broken-krups-km7000-coffee-maker-triangle-head-tamper-proof-screws-too.html?rss" />
    <id>tag:mark.koli.ch,2010://1.228</id>

    <published>2010-03-27T00:50:00Z</published>
    <updated>2010-04-28T04:34:16Z</updated>

    <summary><![CDATA[In 2008, my girlfriend bought me a Krups KM7000 Grind-and-Brew Coffee Maker for Christmas.&nbsp; I love it, it grinds and brews an incredible pot of high quality coffee for me each morning.&nbsp; This past weekend (about 15-months since I got...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="ee" label="ee" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="fuse" label="fuse" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="howto" label="howto" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="krups" label="krups" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="whatcouldpossiblygowrong" label="whatcouldpossiblygowrong" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[<span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2010/03/23/on-the-operating-table.jpg"><img alt="on-the-operating-table.jpg" src="http://mark.koli.ch/assets_c/2010/03/on-the-operating-table-thumb-200x149.jpg" class="mt-image-left" style="float: left; margin: 0pt 20px 20px 0pt;" height="149" width="200" /></a></span>In 2008, my girlfriend <a href="http://mark.koli.ch/2008/12/review-krups-km-7000-grind-and-brew-coffeemaker.html">bought me a Krups KM7000 Grind-and-Brew Coffee Maker for Christmas</a>.&nbsp; I love it, it grinds and brews an incredible pot of high quality coffee for me each morning.&nbsp; This past weekend (about 15-months since I got it), my KM7000 unexpectedly stopped brewing mid-cycle.&nbsp; And to make matters worse, the one-year manufacturers warranty period already expired.&nbsp; Well, I wasn't about to let this one get away, so I decided to open it up and give it a look-see.&nbsp; After all, shouldn't a high-end coffee maker last a little longer than just over a year?<br /><br />With a little effort and some engineering know-how, I found the troublemaker part and repaired my coffee maker.&nbsp; My KM7000 is as good as new.&nbsp; Here's how I did it ...<br />]]>
        <![CDATA[<br /><span style="font-size: 1.1em; font-weight: bold;">1 - Symptoms</span><br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="Thumbnail image for krups-km7000.jpg" src="http://mark.koli.ch/assets_c/2010/03/krups-km7000-thumb-200x245.jpg" class="mt-image-right" style="float: right; margin: 0pt 0pt 20px 20px;" height="245" width="200" /></span>The symptoms of my failing KM7000 were pretty straightforward.&nbsp; Basically everything on the machine worked great, except the hot plate would not get hot nor would the water pump from the reservoir into the brew-pot.&nbsp; The grinder worked perfectly, the LCD display was completely functional, but the machine just wouldn't get hot and wouldn't brew any coffee.&nbsp; Further, when I started a brew-cycle, I could hear a little "click" inside the machine (this is normal) but the coffee maker just sat there doing nothing.<br /><br />After doing a little research online, I discovered that this is a <a href="http://www.jimmysu.net/component/content/article/1-latest/971-oh-the-coffeemaker-is-broken-again">very common problem with the KM7000</a>.&nbsp; Apparently, this unit is well known for dying just past the one-year warranty period.&nbsp; Interesting.&nbsp; Time to open this puppy up for a little surgery.<br /><br /><b>NOTE: My KM7000 is out of warranty, and could not be replaced.&nbsp; If you have a KM7000 that's still under warranty, and it's exhibiting the symptoms I described above, you're better off calling Krups for a new replacement unit.&nbsp; If you open your KM7000, and it's still under warranty, I can say with much certainty that Krups won't give you a free replacement.&nbsp; Better not risk it.</b><br /><b><br />DISCLAIMER: If you attempt to self-fix your KM7000 coffee maker, you should know what you're doing.&nbsp; If you follow this blog post and destroy your KM7000 or set your house on fire, you should know that I'm in no way responsible.&nbsp; If you don't feel comfortable doing this by yourself, find a friend or family member who is more mechanically or electrically inclined to help you.</b><br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">2 - Triangle Bit Tamper Proof Screws</span><br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="triangle-inset.gif" src="http://mark.koli.ch/2010/03/23/triangle-inset.gif" class="mt-image-left" style="float: left; margin: 0pt 20px 5px 0pt;" height="77" width="90" /></span>I took the coffee maker out to my workbench, and flipped it over (making sure that I poured out all of the water, and emptied the coffee bean bin).&nbsp; Using a standard Phillips head screwdriver, I removed all but one screw from the underside of the unit.&nbsp; As it turned out, I discovered that I could not remove one of the screws because it had a non-standard tamper-proof, triangle shaped inset.&nbsp; Note that the head was round, but the inset/bit was literally an equilateral triangle.&nbsp; In other words, this was no standard screw.&nbsp; Krups obviously put this screw in place to prevent people like me from trying to open the unit.&nbsp; Screw that!&nbsp; I tried to loosen this screw using a tiny flat-head screwdriver, being careful not to strip the head.&nbsp; Unfortunately, it was too tight and I gave up fearing that I might strip the head making it impossible for me to remove the screw altogether.<br /><br />First shot, I called a few local hardware stores looking for a triangle shaped replaceable bit or screwdriver, but they never heard of this type of tamper-proof screw.&nbsp; I checked online, and found a few places that sell triangle shaped machine bits, but I wasn't about to pay a few bucks plus $10 USD shipping for a screwdriver bit I'll really only use once.&nbsp; Plus, the online stores I found had 4 sizes of triangular bits so I really had no idea what size I should order, and didn't want to risk wasting my money.&nbsp; For the record, I found <a href="http://www.mcmaster.com/#triangle-screwdriver-bits/=6ce2zm">these bits at McMaster-Carr online</a>:<br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="triangle-bit.gif" src="http://mark.koli.ch/2010/03/23/triangle-bit.gif" class="mt-image-none" style="" height="60" width="367" /></span><br /><br />If you're curious, here's a close up shot of a standard Phillips head (left) and triangular bit tamper-proof screw (right):<br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2010/03/23/phillips-and-triangle-tamper-proof-screw.jpg"><img alt="phillips-and-triangle-tamper-proof-screw.jpg" src="http://mark.koli.ch/assets_c/2010/03/phillips-and-triangle-tamper-proof-screw-thumb-200x151.jpg" class="mt-image-none" style="" height="151" width="200" /></a></span><br /><br />In the end, my solution to this problem involved an old flat-head screwdriver, a vice and metal 
file.&nbsp; I snagged an old beat-up flat-head screwdriver, locked it into my
 vice, and filed the tip into a triangular point.&nbsp; About 10-minutes 
later, with a little trial and error, I had myself a pretty sweet 
triangular screwdriver bit that could easily handle these ridiculous 
tamper-proof screws!<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">3 - Inside the Machine</span><br /><br />Once I removed all of the screws from the underside of the unit, the hard plastic bottom slid right out.&nbsp; Note that there are more "hidden" screws under the little rubber feet; this wasn't immediately obvious to me.&nbsp; You'll have to pop the rubber plugs out of their sockets to reveal about 5-6 more screws.<br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2010/03/23/inside-the-machine.jpg"><img alt="inside-the-machine.jpg" src="http://mark.koli.ch/assets_c/2010/03/inside-the-machine-thumb-200x149.jpg" class="mt-image-left" style="float: left; margin: 0pt 20px 20px 0pt;" height="149" width="200" /></a></span>The internals of the KM7000 seem pretty basic.&nbsp; There's a length of flexible orange tubing that connects the water reservoir to the heating coil (and the hot plate!), then upwards to the brew-pot.&nbsp; A tiny circuit board connects the LCD display to the rest of the unit.&nbsp; So, as I see it, here's how the machine brews coffee.&nbsp; Water from the reservoir flows through the orange tubing into the heating ring, connected to the hot-plate with a little thermal adhesive.&nbsp; From there, the heating element heats the water which forces it upwards through the rest of the orange tubing and into the brew-pot.&nbsp; Remember, the symptoms of my problem were that the hot-plate would not get hot, nor would the machine brew any coffee.&nbsp; Well, the heating coil boils the water as it passes through the tube which propels it upwards into the brew-pot.&nbsp; And, because the heating coil is connected to the underside of the hot plate, the coil warms the hot-plate while it boils the water!&nbsp; In other words, if the machine isn't boiling any water then the hot plate ain't gonna be hot, and vice versa.&nbsp; So clearly, the problem was with the heating coil itself or the <b>circuit that powers the heating coil</b>.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">4 - Problematic Thermal Fuses</span><br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2010/03/23/dual-inline-thermal-fuses.jpg"><img alt="dual-inline-thermal-fuses.jpg" src="http://mark.koli.ch/assets_c/2010/03/dual-inline-thermal-fuses-thumb-200x149.jpg" class="mt-image-left" style="float: left; margin: 0pt 20px 20px 0pt;" height="149" width="200" /></a></span>With a little research, I found <a href="http://www.fixya.com/support/t3315232-krups_km7000_grinds_but_brew_warming">this page</a> and <a href="http://www.jimmysu.net/component/content/article/1-latest/971-oh-the-coffeemaker-is-broken-again">this great blog post</a> describing the same problem, and a do-it-yourself fix.&nbsp; Turns out there are dual inline one-shot thermal fuses on the circuit that connects the heating coil to the rest of the unit.&nbsp; Both are <b>Therm-o-disc G4 series Microtemp axial leaded fuses</b>; one is rated up to 216C (~420F), the other is rated to 240C (~464F).&nbsp; The specs and other details are clearly printed on the fuses themselves.&nbsp; Based on what others reported elsewhere, and the symptoms of my failing KM7000, these fuses were highly suspect.<br /><br />At first, I had difficulty discovering the location of the thermal fuses.&nbsp; As it turns out, they are assembled together in series, hidden under a white plastic sheath on the circuit leading to/from the heating element.&nbsp; Gently side the white sheath away to expose the fuses.<br /><br />Using a multimeter, I checked the resistance across both fuses to see which one had bit the dust.&nbsp; Theoretically speaking, the resistance across an open circuit should be infinite, so my multimeter helped me pinpoint the problematic fuse.&nbsp; Turns out, the 240C fuse had blown wide open; but the 216C fuse was perfectly in tact.&nbsp; One would expect that if the unit really was overheating because of an electrical malfunction, the 216C fuse would have blown first given that it's built to trigger at a lower temperature.&nbsp; In any event, I strongly agree with <a href="http://www.jimmysu.net/component/content/article/1-latest/971-oh-the-coffeemaker-is-broken-again">Jimmy Su, that these fuses probably aren't made all that well and eventually fail under normal operating conditions</a>.&nbsp; One would think that a high-end appliance manufacturer wouldn't cheap-out on basic components.&nbsp; Especially on a one-shot fuse; if it blows, a $150 coffee maker is rendered useless by a $1 part.<br /><br />To further prove to myself that the 240C thermal fuse was the source of the problem, I used a pair of alligator clips to bypass the fuse all together.&nbsp; With alligator clips in place on my workbench, I turned the machine on and sure enough the heating coil powered right up, and got hot very quickly.&nbsp; So, the 240C thermal fuse was definitely the problem!<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">5 - Replacement Thermal Fuses</span><br /><br />Looking for a few replacement fuses, Jimmy kindly pointed me to <b>goodmans.net</b> where I ordered <a href="http://www.goodmans.net/get_item_th-tf216c_thermal-fuse-216-degrees-celsius.htm">three 216C</a> and <a href="http://www.goodmans.net/get_item_th-tf240c_thermal-fuse-240-degrees-celsius.htm">three 240C</a> thermal fuses.&nbsp; I ordered three of each just in case I ruined one or two during the repair or needed more to fix a another blown fuse down the road.&nbsp; In total, the six replacement fuses plus expedited shipping came out to just over 14-bucks.&nbsp; Not bad at all.&nbsp; You might ask why didn't I just drive down to the local Radio Shack?&nbsp; Well, smart guy, turns out Radio Shack does not carry these type of specialty fuses.&nbsp; I'm sure I could have hunted something down at a local electronics parts supplier, but I had no interest in wasting my time.&nbsp; In any event, the exact manufacturer part numbers of the replacement fuses I ordered online are as follows:<br /><br /><ul><li>Therm-O-Disc G4A01216C (Microtemp G4 Series, Axial Leaded)<br /></li><li>Therm-O-Disc G4A01240C (Microtemp G4 Series, Axial Leaded)<br /></li></ul><br />Once my replacement fuses arrived, I grabbed the closest soldering gun and got down to business.&nbsp; Even though I didn't need to, I opted to replace both fuses.&nbsp; I felt like that's a safer bet than replacing one, only to find out I need to open up the unit again a month later to replace the other fuse.&nbsp; Note that if you <b>opt to solder in the replacement fuses, you should remember to heat sink the fuses so the heat of the soldering gun doesn't accidentally trip one</b>.&nbsp; Placing a few alligator clips on each end of the fuse works nicely by helping to direct some heat away from the fuse itself.<br /><br />I opted to use a few simple butt-connectors between the fuses, and the main line:<br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2010/03/26/km7000-replacement-fuses-butt-connectors.JPG"><img alt="km7000-replacement-fuses-butt-connectors.JPG" src="http://mark.koli.ch/assets_c/2010/03/km7000-replacement-fuses-butt-connectors-thumb-200x149.jpg" class="mt-image-none" style="" height="149" width="200" /></a></span><br /><br />With both fuses replaced, I verified each with a multimeter and carefully reassembled my KM7000.&nbsp; I dusted it off, brought it back into my kitchen, plugged it in, and brewed up one heck of a great tasting pot of coffee.<br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2010/03/26/km7000-fixed-victory-is-mine.JPG"><img alt="km7000-fixed-victory-is-mine.JPG" src="http://mark.koli.ch/assets_c/2010/03/km7000-fixed-victory-is-mine-thumb-200x149.jpg" class="mt-image-none" style="" height="149" width="200" /></a></span><br /><br />Can you smell that?&nbsp; That's the smell of delicious do-it-yourself satisfaction.<br /><br />Cheers!<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">6 - Additional Resources</span><br /><br />Here are some additional resources you might find helpful.<br /><br /><ul><li><a href="https://onyx.koli.ch/x3bn">https://onyx.koli.ch/x3bn</a> -- Complete set of photos from my repair procedure, including more pictures of those lame triangle bit tamper-proof screws.</li><li><a href="https://onyx.koli.ch/x3c2">https://onyx.koli.ch/x3c2</a> -- Fantastic blog post from Jimmy Su which helped me discover the problem with my KM7000.</li><li><a href="https://onyx.koli.ch/x14m">https://onyx.koli.ch/x14m</a> -- My archived digital copy of the Krups KM7000 User-Manual.</li><li><a href="https://onyx.koli.ch/x3c3">https://onyx.koli.ch/x3c3</a> -- A PDF spec of the G4 fuse from Therm-O-Disc, the manufacturer of the thermal fuses used in the KM7000.</li></ul><div><br /></div><div><i><b>Very special thanks to <a href="http://www.jimmysu.net/component/content/article/1-latest/971-oh-the-coffeemaker-is-broken-again">Jimmy Su</a> for all of his help and advice along the way.&nbsp; Thanks, Jimmy!</b></i><br /><i><b><br />Randy M. has confirmed that these instructions also work to fix a broken Krups KM7005.&nbsp; Thanks, Randy!</b></i><br /></div>]]>
    </content>
</entry>

<entry>
    <title>HOWTO: Setting Up Your Own SVN Server (Using Apache and mod_dav_svn)</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/03/howto-setting-up-your-own-svn-server-using-apache-and-mod-dav-svn.html?rss" />
    <id>tag:mark.koli.ch,2010://1.227</id>

    <published>2010-03-16T21:30:00Z</published>
    <updated>2010-04-07T15:11:40Z</updated>

    <summary><![CDATA[Setting up your own SVN source control server is surprisingly easy.&nbsp; At home, I recently setup an SVN server in a CentOS 5.4 virtual machine with Apache 2.2 and mod_dav_svn.&nbsp; With a little work, I had a secure and fully...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="apache" label="apache" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="howto" label="howto" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="http" label="http" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="svn" label="svn" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[Setting up your own SVN source control server is surprisingly easy.&nbsp; At home, I recently setup an SVN server in a CentOS 5.4 virtual machine with Apache 2.2 and <a href="http://summersoft.fay.ar.us/pub/subversion/Book/re58.html">mod_dav_svn</a>.&nbsp; With a little work, I had a secure and fully functional SVN server up and running in about 20 minutes.<br /><br />Note that this HOWTO is specific to CentOS/RHEL/Fedora.&nbsp; The location of configuration files, and other tools, might be different depending on your Linux distro.&nbsp; For the most part though, everything should be pretty similar and you should be able to figure it out.]]>
        <![CDATA[<br /><span style="font-size: 1.1em; font-weight: bold;">1 - Install Apache, Subversion, mod_dav_svn and mod_ssl</span><br /><br />On CentOS, installing the Apache web-server, Subversion, and the Apache mod_dav_svn and mod_ssl modules are a snap with yum:<br /><br /><pre class="prettyprint">#(root)/&gt; yum -y install httpd subversion mod_dav_svn mod_ssl openssl</pre><br />If you're on Ubuntu you can probably install the required packages using <strong>apt-get install</strong>.&nbsp; Note that you need to install <b>mod_ssl</b> if you plan on securing your SVN server with HTTPS.&nbsp; If you don't care about HTTPS, then you can ignore <b>mod_ssl</b> and skip <b>Step #6 below</b>.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">2 - Create your SVN Root Directory Structure</span><br /><br />On my SVN server, I created a new SVN root at <strong>/svn</strong>.  From here on out, all of my SVN repositories will live under <strong>/svn/repos</strong>.<br /><br /><pre class="prettyprint">#(root)/&gt; mkdir -p /svn/repos<br />#(root)/&gt; chown -R apache:apache /svn</pre><br />Once done, you're ready to create your first SVN repository.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">3 - Create your First Repository</span><br /><br />Using the <strong>svnadmin</strong> command, create a repository under <strong>/svn/repos</strong>.  For the sake of this example, the repository I'm creating is named "myproject".  Of course, you can name your own repository whatever you'd like.&nbsp; Oh, and you can create as many repositories as you'd like under <b>/svn</b>.<br /><br /><pre class="prettyprint">#(root)/&gt; cd /svn/repos<br />#(root)/svn/repos&gt; svnadmin create --fs-type fsfs myproject<br />#(root)/svn/repos&gt; chown -R apache:apache myproject<br />#(root)/svn/repos&gt; chmod -R g+w myproject<br />#(root)/svn/repos&gt; chmod g+s myproject/db</pre><br />You'll notice that the <b>svnadmin</b> command created a new directory named myproject/.&nbsp; If you look inside myproject/ you'll see a bunch of SVN repository data and configuration files.<br /><br /><pre class="prettyprint">#(root)/svn/repos&gt; ll myproject<br />total 28<br />drwxrwxr-x 2 apache apache 4096 Mar 13 11:49 conf<br />drwxrwxr-x 2 apache apache 4096 Mar 13 12:01 dav<br />drwxrwsr-x 5 apache apache 4096 Mar 13 12:23 db<br />-r--rw-r-- 1 apache apache 2    Mar 13 11:49 format<br />drwxrwxr-x 2 apache apache 4096 Mar 13 11:49 hooks<br />drwxrwxr-x 2 apache apache 4096 Mar 13 11:49 locks<br />-rw-rw-r-- 1 apache apache 229  Mar 13 11:49 README.txt</pre><br />Great, looks like our new SVN repository was setup correctly!<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">4 - Configure mod_dav_svn</span><br /><br />Now that you have an SVN repository setup and ready to go, let's configure Apache and the <strong>mod_dav_svn</strong> module.&nbsp; Open <strong>/etc/httpd/conf.d/subversion.conf</strong> in your favorite text editor, and tweak the configuration to match your installation.&nbsp; My <b>subversion.conf</b> file looks like this:<br /><br /><pre class="prettyprint">LoadModule dav_svn_module     modules/mod_dav_svn.so<br />LoadModule authz_svn_module   modules/mod_authz_svn.so<br /><br />&lt;Location /svn&gt;<br /><br />   DAV svn<br />   SVNParentPath /svn/repos<br /><br />   # Require SSL connection for password protection.<br />   SSLRequireSSL<br /><br />   AuthType Basic<br />   AuthName "Marks SVN Server"<br />   AuthUserFile /svn/repos/users<br />   Require valid-user<br /><br />&lt;/Location&gt;</pre><br />First, note that when you install <b>mod_dav_svn</b> using yum, the installation process will create a standard cookie cutter template <strong>/etc/httpd/conf.d/subversion.conf</strong> for you.&nbsp; This template has a <a href="http://httpd.apache.org/docs/2.2/mod/core.html#limitexcept">LimitExcept</a> directive in it, and a few other things.&nbsp; For security, I think it's best to require a user to authenticate before they are able to issue any request.&nbsp; Hence, why I removed the LimitExcept directive and did my own thing.&nbsp; If you want your SVN server to be read-only for anonymous users, and read-write for authenticated users, then my <b>subversion.conf</b> file is not for you.&nbsp; My <strong>subversion.conf</strong> file shown above allows no anonymous access; all users must authenticate (enter a valid username and password) before they can do anything with the SVN server.<br /><br />Second, note that I have enabled the <strong>SSLRequireSSL</strong> directive.&nbsp; This triggers <b>mod_dav_svn</b> to reject all non-HTTPS requests.&nbsp; This ensures that any communication between the server and my SVN client will be sent via HTTPS; usernames, passwords, and source code will be reasonably secured.&nbsp; I'll show you how to setup HTTPS here in a moment.&nbsp; Note that if you don't want to enable HTTPS on your SVN server, then you can comment out or remove the <b>SSLRequireSSL</b> line in your <b>subversion.conf</b> configuration file.<br /><br />Finally, note that my <strong>AuthUserFile is /svn/repos/users</strong>.&nbsp; This is a standard Apache <strong>htpasswd</strong> file that we'll create in the next step.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">5 - Create your SVN Users File</span><br /><br />Create your SVN users file using the <strong>htpasswd</strong> command.&nbsp; This is the file that stores a list of usernames and passwords declaring who is allowed to access your SVN server.<br /><br /><pre class="prettyprint">#(root)/&gt; htpasswd -c /svn/repos/users mark</pre><br />Replace "mark" above with your desired username.&nbsp; Repeat this command for however many users you need to add access.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">6 - Configure mod_ssl and Setup HTTPS</span><br /><br />If you have decided to make your SVN sever an HTTPS only server, we'll need to setup Apache's HTTPS configuration.&nbsp; This involves tweaking <strong>/etc/httpd/conf.d/ssl.conf</strong> and creating a new self-signed SSL certificate.&nbsp; If you're interested, I wrote a <a href="http://mark.koli.ch/2009/03/howto-generate-your-own-self-signed-ssl-certificates-for-https-apache.html">blog post a while back explaining how to create your own self-signed SSL certificate here</a>.&nbsp; However, for your convenience, I've included the same set of instructions below.&nbsp; Note that my SVN server is named <strong>svn.kolich.local</strong>.&nbsp; Yours will probably be different.&nbsp; Whatever it is, make sure that you enter the correct server name when openssl prompts you for a "Common Name" in your certificate.&nbsp; The "Common Name" in your SSL certificate should match the fully qualified name of your SVN server.<br /><br />Note that if you have an SSL certificate signed by a legitimate Certificate Authority (Network Solutions, Verisign, Thawte) you shouldn't need to generate a new SSL key and self-signed certificate.&nbsp; You can simply use the one issued to you by your CA.<br /><br />First, create a new SSL key with the <strong>openssl</strong> command:<br /><br /><pre class="prettyprint">#(root)/&gt; mkdir /etc/httpd/ssl<br />#(root)/&gt; cd /etc/httpd/ssl<br />#(root)/etc/httpd/ssl&gt; openssl genrsa 4096 &gt; svn.kolich.local.key</pre><br />Now that you have a private key, create a self-signed certificate:<br /><br /><pre class="prettyprint">#/etc/httpd/ssl&gt; openssl req -new -key svn.kolich.local.key -x509 \<br />     -days 1095 -out svn.kolich.local.crt<br /><br />You are about to be asked to enter information that will be incorporated<br />into your certificate request.<br />What you are about to enter is what is called a Distinguished Name or a DN.<br />There are quite a few fields but you can leave some blank<br />For some fields there will be a default value,<br />If you enter '.', the field will be left blank.<br />-----<br />Country Name (2 letter code) [GB]:US<br />State or Province Name (full name) [Berkshire]:California<br />Locality Name (eg, city) [Newbury]:My Town<br />Organization Name (eg, company) [My Company Ltd]:Mark Kolich<br />Organizational Unit Name (eg, section) []:<br />Common Name (eg, your name or your server's hostname) []:svn.kolich.local<br />Email Address []:</pre><br />Finally, edit <strong>/etc/httpd/conf.d/ssl.conf</strong> to point to your newly generated SSL key and SSL certificate.&nbsp; This involves updating the <strong>SSLCertificateFile</strong> and <strong>SSLCertificateKeyFile</strong> directives accordingly:<br /><br /><pre class="prettyprint">##<br />## SSL Virtual Host Context<br />##<br /><br />&lt;VirtualHost _default_:443&gt;<br /> ...<br /> SSLCertificateFile /etc/httpd/ssl/svn.kolich.local.crt<br /> SSLCertificateKeyFile /etc/httpd/ssl/svn.kolich.local.key<br /> ...<br />&lt;/VirtualHost&gt;</pre><br />Note that you should not place your SSL private key and certificate in a location accessible by the web-server.&nbsp; Usually placing them under <strong>/etc/httpd</strong> is sufficient.&nbsp; It would be less desirable and quite insecure to place them under <strong>/var/www/html</strong> for example.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">7 - Configure HTTP to HTTPS Redirection</span><br /><br />If you've bothered to setup HTTPS in the previous step, you probably want Apache to gracefully redirect clients from HTTP to HTTPS.&nbsp; If you don't automatically redirect, and you have <strong>SSLRequireSSL</strong> enabled in your <strong>subversion.conf</strong> file, when clients try to communicate with your SVN server via HTTP they'll see a <a href="http://en.wikipedia.org/wiki/HTTP_403">403 Forbidden</a> error.&nbsp; Instead, let's <a href="http://en.wikipedia.org/wiki/HTTP_301">301 Moved Permanently</a> redirect them to HTTPS.&nbsp; Open <strong>/etc/httpd/conf/httpd.conf</strong> in your favorite text editor, jump to the bottom of the file, and edit your <a href="http://httpd.apache.org/docs/2.2/mod/core.html#virtualhost">VirtualHost</a> configuration.&nbsp; Mine is as follows:<br /><br /><pre class="prettyprint">NameVirtualHost *:80<br /><br />&lt;VirtualHost *:80&gt;<br /><br />  ServerAdmin example@example.com<br />  DocumentRoot /var/www/html<br />  ServerName svn.kolich.local<br />  ServerAlias svn<br /><br />  RewriteEngine On<br />  RewriteCond %{HTTPS} !=on<br />  RewriteRule ^/(.*)$ https://svn.kolich.local/$1 [R=301,L]<br /><br />  ErrorLog logs/svn.kolich.local-error_log<br />  CustomLog logs/svn.kolich.local-access_log common<br /><br />&lt;/VirtualHost&gt;</pre><br />Save it, and you're done.&nbsp; Now when a client tries to communicate with my SVN server via HTTP, it'll see a 301 Moved Permanently redirect to HTTPS.&nbsp; If my SVN client is smart enough, it will gracefully follow this redirect to HTTPS, and all is well.&nbsp; Of course, you'll need to change the HTTPS URL shown above in the <a href="http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html#rewriterule">RewriteRule directive</a> to match your server hostname (your SVN server is not svn.kolich.local).<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">8 - Start Apache, and Enjoy</span><br /><br />That's it!&nbsp; Start Apache and checkout your new repository.<br /><br /><pre class="prettyprint">#(root)/&gt; /etc/init.d/httpd start</pre><br />On another machine, try to checkout the repository:<br /><br /><pre class="prettyprint">#(mark@fiji)~&gt; svn co http://svn.kolich.local/svn/myproject<br />svn: PROPFIND request failed on '/svn/myproject'<br />svn: PROPFIND of '/svn/myproject': 301 Moved Permanently (http://svn.kolich.local)</pre><br />Yep, HTTP to HTTPS redirection is working as expected.&nbsp; Unfortunately my SVN client isn't smart enough to follow the redirect on its own.&nbsp; Oh well, change that repository URL to HTTPS, and try again:<br /><br /><pre class="prettyprint">#(mark@fiji)~&gt; svn co https://svn.kolich.local/svn/myproject<br />Error validating server certificate for 'https://svn.kolich.local:443':<br /> - The certificate is not issued by a trusted authority. Use the<br />   fingerprint to validate the certificate manually!<br />Certificate information:<br /> - Hostname: svn.kolich.local<br /> - Valid: from Mar 15 20:17:38 2010 GMT until Mar 14 20:17:38 2013 GMT<br /> - Issuer: Mark Kolich, California, US<br /> - Fingerprint: ff:ee:b6:9c:d8:d7:78:3b:ce:9e:09:dd:4a:99:93:11:3e:12:07:85<br />(R)eject, accept (t)emporarily or accept (p)ermanently? p<br />Authentication realm: &lt;https://svn.kolich.local:443&gt; Marks SVN Server<br />Password for 'mark': ...<br />A    myproject<br />Checked out revision 0.</pre><br />It worked!&nbsp; Note that the "Error validating server certificate" warning is because I'm using a self-signed SSL certificate.&nbsp; When SVN asks if you want to accept the certificate, if you permanently accept it you will not be prompted about this again.&nbsp; If you use an SSL certificate issued by a real Certificate Authority like Network Solutions, Verisign, or Thawte, you shouldn't see this warning.<br /><br />We're all set!&nbsp; Time to start hackin'!<br /><br />Cheers.<br />]]>
    </content>
</entry>

<entry>
    <title>HOWTO: Setting Up Your Own Local DNS Server</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/03/howto-setting-up-your-own-local-dns-server.html?rss" />
    <id>tag:mark.koli.ch,2010://1.226</id>

    <published>2010-03-16T04:10:00Z</published>
    <updated>2010-04-08T15:36:17Z</updated>

    <summary><![CDATA[This weekend I setup my own SVN source control server running inside of a CentOS 5.4 virtual machine (fun project, another blog post on this to come soon here's my how to setup your own SVN server post).&nbsp; Once my...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="dns" label="dns" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="howto" label="howto" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="named" label="named" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[This weekend I setup my own <a href="http://subversion.apache.org/">SVN source control server</a> running inside of a CentOS 5.4 virtual machine (fun project, <strike>another blog post on this to come soon</strike> <a href="http://mark.koli.ch/2010/03/howto-setting-up-your-own-svn-server-using-apache-and-mod-dav-svn.html">here's my how to setup your own SVN server post</a>).&nbsp; Once my new SVN server was setup and ready to roll, I tried checking out one of the repositories.&nbsp; From the nearest command line, I started typing "svn co http://192.168.1." ... damn, what was the IP-address of my local SVN server again?&nbsp; I just picked an IP-address about 15 minutes ago during my CentOS install, but already forgot it!&nbsp; Ok, time to setup a decent local DNS server for my home network.&nbsp; Since 2006, I've been limping along manually editing my /etc/hosts files on multiple machines and memorizing the IP-addresses of critical devices on my network.&nbsp; Looking back, this was just plain silly.]]>
        <![CDATA[<br /><span style="font-size: 1.1em; font-weight: bold;">1 - My Network Topology</span><br /><br />My network topology, shown in the diagram below, isn't too complicated.  In fact, it's probably quite standard for an average geek.  I'm not very good at making pretty charts and graphics, so don't laugh at my artistic abilities.  In any event, I've got your typical wireless router and firewall appliance connected to an <a href="http://www.procurve.com/products/switches/HP_ProCurve_Switch_1800_Series/overview.htm">HP Procurve 1800-8G Gigabit switch</a> which fans out the bandwidth from there.  Generally speaking, most traffic on my home network is local so everything sits behind the HP Procurve Gigabit switch.  I'm more concerned about the internal network speed between devices, instead of device to the outside world.  And of course, all of my devices sit behind NAT, which the router handles for me.<br /><br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2010/03/15/kolich.com-network-topology.png"><img alt="kolich.com-network-topology.png" src="http://mark.koli.ch/assets_c/2010/03/kolich.com-network-topology-thumb-415x309.png" class="mt-image-none" style="" height="309" width="415" /></a></span><br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">2 - The Problem</span><br /><br />The DNS for my publicly visible domain names are hosted by my registrar, <a href="http://www.networksolutions.com/">Network Solutions</a>.&nbsp; I'm not planning on hosting my own DNS for these services anytime soon, so I just needed something to help me out around the house.&nbsp; In other words, I just need a local DNS server so I can resolve addresses internally on my network.&nbsp; I was getting really tired of typing "ping 192.168.1.102" when I could have been typing "ping somebox" this whole time.&nbsp; In addition to resolving locally, if the DNS server receives a request to resolve a name it doesn't know about, I want it to forward the request to my favorite DNS provider, <a href="http://www.opendns.com/">OpenDNS</a>.&nbsp; So if my DNS server receives a request for "somebox", and "somebox" is a local device on my network, it should resolve to 192.168.1.whatever.&nbsp; However, if it receives a request to resolve google.com, or some other domain it knows nothing about, it should forward the request to OpenDNS and cache the response if necessary.<br /><br />I should also add that every device with a name on my local network, has a hostname that ends with "<b>kolich.local</b>" (this is my new DNS zone).&nbsp; This is so I can distinguish internal devices from servers, and services, running externally under kolich.com, koli.ch, or another domain.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">3 - Installing the DNS Server</span><br /><br />I cloned another CentOS Linux virtual machine, booted it up, and installed <a href="http://www.isc.org/software/bind">Bind</a> (an open source DNS server usually called "named"):<br /><br /><pre class="prettyprint">#(dns)/&gt; yum -y install bind</pre><br />As of 3/15/10 CentOS 5.4 ships with Bind 9.3.6, which should be perfectly stable for what I need.&nbsp; This new virtual machine is now my own private internal DNS server, not visible to the outside world.&nbsp; Once installed, I used <b>chkconfig</b> to start <b>named</b> on system startup:<br /><br /><pre class="prettyprint">#(dns)/&gt; /sbin/chkconfig --level 35 named on</pre><br />You should note that <b>named</b> on CentOS, and probably other newer Linux distros, runs inside of a chroot'ed environment which I have no interest in explaining here.&nbsp; If you want to read more about chroot, check out <a href="http://en.wikipedia.org/wiki/Chroot">this decent overview of it on Wikipedia</a>.&nbsp; For the sake of this blog post, you probably don't have to care about how <b>named</b> runs under <b>chroot</b>.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">4 - Configuring the DNS Server</span><br /><br />Once installed, I tweaked my <b>/etc/named.conf</b> file so it looks something like this:<br /><br /><pre class="prettyprint">options {<br />  directory           "/var/named"; // the default<br />  dump-file           "data/cache_dump.db";<br />  statistics-file     "data/named_stats.txt";<br />  memstatistics-file  "data/named_mem_stats.txt";<br />  version             "currently unavailable";<br />  ;; Forward anything this DNS server can't resolve to OpenDNS<br />  forwarders { 208.67.222.222; 208.67.220.220; };<br />};<br /><br />;; All devices under my local network sit behind<br />;; the kolich.local zone<br />zone "kolich.local" in {<br />  type master;<br />  file "kolich.local.ns";<br />  allow-update { none; };<br />};<br /><br />;; For reverse lookups, going IP addy back to a hostname<br />zone "1.168.192.in-addr.arpa" in {<br />  type master;<br />  file "1.168.192.in-addr.arpa.ns";<br />  allow-update { none; };<br />};<br /></pre><br />I have two zone files: <b>kolich.local.ns</b> and <b>1.168.192.in-addr.arpa.ns</b>.&nbsp; The first zone file maps a list of hostname's to IP-addresses, and the second defines a reverse lookup zone that maps a list IP-addresses back to hostname's.&nbsp; Note that because <b>named</b> is running under a chroot'ed environment, it's usually safest to place these zone files under <b>/var/named/chroot/var/named</b> (yes, that is the correct path) on a typical CentOS/RHEL/Fedora installation.<br /><br />In my <b>/etc/named.conf</b> file, notice I've given two forwarders in the options section.&nbsp; This declaration lets me tell <b>named</b> to forward any requests it cannot resolve to OpenDNS at 208.67.222.222 or 208.67.220.220.<br /><br />Now let's take a quick look at my zone files.&nbsp; First, here is my <b>/var/named/chroot/var/named/kolich.local.ns</b> zone file for my new "kolich.local" zone:<br /><br /><pre class="prettyprint">$TTL    1d<br />kolich.local.  IN    SOA   ns.kolich.local. support.kolich.com. (<br />    2010031500 ; se = serial number<br />    3h         ; ref = refresh<br />    15m        ; ret = update retry<br />    3w         ; ex = expiry<br />    3h         ; min = minimum<br />    )<br /><br />    IN    NS    ns.kolich.local.<br /><br />; private hosts<br />ns         IN    A    192.168.1.2<br /><br />cat        IN    A    192.168.1.3<br />fish       IN    A    192.168.1.4<br />whale      IN    A    192.168.1.5<br />monkey     IN    A    192.168.1.6<br />horse      IN    A    192.168.1.7<br />cow        IN    A    192.168.1.8<br /></pre><br />And here's my <b>/var/named/chroot/var/named/1.168.192.in-addr.arpa.ns</b> reverse lookup zone file that is used to map IP-addresses back to hostname's:<br /><br /><pre class="prettyprint">$TTL    1d<br />@   IN    SOA   ns.kolich.local. support.kolich.com. (<br />    2010031500 ; se = serial number<br />    3h         ; ref = refresh<br />    15m        ; ret = update retry<br />    3w         ; ex = expiry<br />    3h         ; min = minimum<br />    )<br /><br />    IN    NS    ns.kolich.local.<br /><br />; private hosts, reverse lookup<br />2     IN    PTR    ns.kolich.local.<br /><br />3     IN    PTR    cat.kolich.local.<br />4     IN    PTR    fish.kolich.local.<br />5     IN    PTR    whale.kolich.local.<br />6     IN    PTR    monkey.kolich.local.<br />7     IN    PTR    horse.kolich.local.<br />8     IN    PTR    cow.kolich.local.</pre><br />Note that my <b>1.168.192.in-addr.arpa.ns</b> zone file is basically a reverse map of my <b>kolich.local.ns</b> file.&nbsp; Of course, depending on your local network settings, your IP addresses might be different.&nbsp; And no, the systems and devices on my network are not named after animals; the names shown here are just for the sake of this example.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">5 - Starting the DNS Server</span><br /><br />Ok, once all of your configuration files are in place, it's time to start your new local DNS server:<br /><br /><pre class="prettyprint">#(dns)/&gt; /etc/init.d/named start</pre><br />That's it!&nbsp; Assuming <b>named</b> started correctly, I can configure the clients on my local network to point to my new in-house DNS server.&nbsp; On Linux, this involves editing the domain and nameserver configuration inside of <b>/etc/resolv.conf</b> ...<br /><br /><pre class="prettyprint">domain kolich.local<br />nameserver 192.168.1.2<br /></pre><br />Once <b>/etc/resolv.conf</b> is configured properly, I can use the <b>nslookup</b> or <b>dig</b> commands to verify that all is working as expected:<br /><br /><pre class="prettyprint">#(dns)/&gt; nslookup monkey<br />Server:         192.168.1.2<br />Address:        192.168.1.2#53<br /><br />Name:   monkey.kolich.local<br />Address: 192.168.1.6</pre><br />Great!&nbsp; Hostname resolution works well.&nbsp; Now, let's verify that I can go the other way (reverse lookups):<br /><br /><pre class="prettyprint">#(dns)/&gt; nslookup 192.168.1.3<br />Server:         192.168.1.2<br />Address:        192.168.1.2#53<br /><br />2.1.168.192.in-addr.arpa    name = cat.kolich.local.<br /></pre><br />Yup, works fine.&nbsp; So what about that forwarding stuff?&nbsp; Good call, we should also check that the server is forwarding requests for hostname's it can't resolve to OpenDNS:<br /><br /><pre class="prettyprint">#(dns)/&gt; nslookup twitter.com<br />Server:         192.168.1.2<br />Address:        192.168.1.2#53<br /><br />Non-authoritative answer:<br />Name:   twitter.com<br />Address: 128.242.240.20<br /></pre><br />Perfect.&nbsp; Internal hosts resolve correctly, and my DNS server is forwarding all requests it can't resolve to OpenDNS as desired!<br /><br />Finally, with a proper DNS server up and running, I can get back to my coding project.<br /><br />Enjoy.<br />]]>
    </content>
</entry>

<entry>
    <title>Clok: A new way to view time</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/01/clok-a-new-way-to-view-time.html?rss" />
    <id>tag:mark.koli.ch,2010://1.223</id>

    <published>2010-01-21T01:45:00Z</published>
    <updated>2010-01-21T03:03:44Z</updated>

    <summary><![CDATA[Clok is a "very fuzzy" world-clock I built, modeled after Caskey Dickson's idea and Java Clok implementation.&nbsp; Clok is not built to give you the exact time in every time zone; for that, get another, more accurate, world clock.&nbsp; Instead,...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="clok" label="clok" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="css" label="css" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="javascript" label="javascript" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="php" label="php" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[<a href="http://clok.koli.ch/">Clok</a> is a "very fuzzy" world-clock I built, modeled after <a href="http://technocage.com/%7Ecaskey/clok/">Caskey Dickson's idea and Java Clok implementation</a>.&nbsp; Clok is not built to give you the exact time in every time zone; for that, get <a href="http://www.timeanddate.com/worldclock/">another, more accurate, world clock</a>.&nbsp; Instead, my version of Clok is used to help me answer basic time related questions, like:<br /><br /><ul><li>Several of my co-workers are based in London; can I email them at the office, or are they at home?<br /></li><li>I need to call my girlfriend, but she's in Manila visiting family.&nbsp; Is it daytime there?</li><li>Some interesting world news just broke in Dubai; roughly what time is there?</li></ul><br />These questions are all a slight variation of the timeless (no pun intended), "what time is it?"&nbsp; The latest version of Clok can be found at <a href="http://clok.koli.ch/">http://clok.koli.ch</a>.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">1 - How Do I Read this Clok?</span><br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="clok-main-snap.jpg" src="http://mark.koli.ch/2010/01/20/clok-main-snap.jpg" class="mt-image-left" style="margin: 0pt 20px 20px 0pt; float: left;" height="134" width="200" /></span>Each column represents a single hour, in a 24-hour day.&nbsp; Each hour is color coded, according to the various pieces, or periods, of a typical day.&nbsp; As <a href="http://technocage.com/%7Ecaskey/clok/">explained here</a>, "sleeping is obviously the least productive and so that is represented in black. The morning is the time between sleeping and lunch, lunch is a time of recovery and planning for the rest of the day. Then there is the afternoon, which for many is the time when the majority of their work gets done. Finally comes evening, which is personal time, time spent with family, or time taking care of those responsibilities that have to do with running our lives and not earning a paycheck."&nbsp; The colors of each hour in the day directly correspond to these periods.<br /><br />Each row is a different time zone, modeled after <a href="http://en.wikipedia.org/wiki/Time_zone#Standard_time_zones">this list on Wikipedia</a>.&nbsp; And finally, the <span style="color: red; font-weight: bold;">red line</span> is the approximate current time in that time zone.<br /><br />Clok automatically updates itself every minute while open; no browser refresh is required.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">2 - The User Interface</span><br /><br /><span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="clok-mode-snap.jpg" src="http://mark.koli.ch/2010/01/20/clok-mode-snap.jpg" class="mt-image-right" style="margin: 0px 0px 10px 10px; float: right;" height="71" width="124" /></span>You may have noticed that when you mouseover each row on the Clok, four tiny buttons appear on the far right of each time zone.&nbsp; These represent the various modes: H for human, K for hacker/developer/coder, S for server, and R for raccoon (nocturnal).&nbsp; When clicked, these buttons change the mode of Clok.&nbsp; If you're a relatively normal individual with decent sleeping habits, you will find Human mode most useful.&nbsp; If you, or a friend across the world participates in a more nocturnal lifestyle, then you might enjoy Raccoon mode.<br /><br />If you find the time zone annotations annoying, and would like to hide them, click anywhere in the orange header bar and the name/UTC ID of each time zone should disappear.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">3 - Technical Details</span><br /><br />My version of Clok is pure CSS, JavaScript, and PHP (no Flash!).&nbsp; When the DOM is ready, AJAX is used behind the scenes to call a PHP controller on the server, which actually does the work of computing the time in every requested time zone.&nbsp; This controller returns a block of JSON that maps a time zone to the position of its red "current time" bar.&nbsp; The JavaScript loops over these JSON objects in the response, and uses <a href="http://docs.jquery.com/Effects/animate">jQuery to move (animate)</a> the red bars into position.&nbsp; To stay current, an interval fires once every 60,000 ms (1 minute) which triggers Clok to refresh itself.<br /><br /><br /><span style="font-size: 1.1em; font-weight: bold;">4 - Where Can I Find this Clok?</span><br /><br />Try <a href="http://clok.koli.ch/">http://clok.koli.ch</a>.<br /><br />Enjoy.]]>
        
    </content>
</entry>

<entry>
    <title>Java: JumpToLine, Jump or Seek to a Line in a File</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2010/01/java-jumptoline-jump-to-a-line-in-a-file.html?rss" />
    <id>tag:mark.koli.ch,2010://1.222</id>

    <published>2010-01-18T20:13:00Z</published>
    <updated>2010-01-18T20:15:20Z</updated>

    <summary><![CDATA[Seeking to a line number in a text file isn't too hard to implement in Java if you use a few common and trusted API's like Apache's Commons I/O library.&nbsp; Recently I needed some Java that could automatically seek to...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="code" label="code" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="howto" label="howto" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="java" label="java" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[Seeking to a line number in a text file isn't too hard to implement in Java if you use a few common and trusted API's like <a href="http://commons.apache.org/io/">Apache's Commons I/O library</a>.&nbsp; Recently I needed some Java that could automatically seek to a given line in a file and then remember the line number of the last line read.&nbsp; The next time I open the log reader, it should automatically seek itself to the last line read, and let me read any subsequent lines added by another user or process.&nbsp; This is perfect for log file monitoring: the first invocation of the reader would read lines 1 through X and the next invocation would read lines X+1 through Y, and so on.&nbsp; Using Apache's Commons I/O API, this isn't difficult at all.&nbsp; You may or may not know that the Commons I/O API contains a very convenient <a href="http://commons.apache.org/io/api-release/org/apache/commons/io/LineIterator.html">LineIterator class</a>, which lets the developer iterate over lines in a file using a <a href="http://java.sun.com/javase/6/docs/api/java/io/Reader.html">Reader</a>.<br /><br />With that in mind, <a href="http://mark.koli.ch/2010/01/18/JumpToLine.java">meet JumpToLine</a>, a somewhat hackish class I wrote that wraps Apache's Commons I/O LineIterator in a way that lets you <b>seek ahead</b> to a specific line in a file, and is smart enough to remember the last line read (so that you don't read the same line twice).]]>
        <![CDATA[<br /><span style="font-size: 1.1em; font-weight: bold;">Example #1</span><br /><br />Here's how you might use <b>JumpToLine to seek to line 10 in mylog.log</b>, then read that line and every line after it:<br /><br /><pre class="prettyprint">final JumpToLine jtl = new JumpToLine(new File("mylog.log"));<br /><br />try {<br />  // Open the underlying reader and LineIterator.<br />  jtl.open();<br />  // Seek to line 10; will throw a NoSuchElementException if<br />  // out of range.<br />  jtl.seek(10);<br />  // While there are any lines after and including line 10,<br />  // read them.<br />  while(jtl.hasNext()) {<br />    final String line = jtl.readLine();<br />    System.out.println(line);<br />  }<br />} catch (Exception e) {<br />  e.printStackTrace(System.err);<br />} finally {<br />  // Close the underlying reader and LineIterator.<br />  jtl.close();<br />}</pre><br /><span style="font-size: 1.1em; font-weight: bold;">Example #2</span><br /><br />Here's how you might use <b>JumpToLine to seek to the last line read in mylog.log</b>, and then read any subsequent lines added to the file since it was last read:<br /><br /><pre class="prettyprint">final JumpToLine jtl = new JumpToLine(new File("mylog.log"));<br /><br />try {<br />  // Open the underlying reader and LineIterator.<br />  jtl.open();<br />  // Seek to the last line read since we last tried to<br />  // read any lines from this file.<br />  jtl.seek();<br />  // While there are any more lines to read from the last<br />  // line read position, then read them.<br />  while(jtl.hasNext()) {<br />    final String line = jtl.readLine();<br />    System.out.println(line);<br />  }<br />  // For grins, what is the last line number we read?<br />  System.out.println("Last line number read: " +<br />      jtl.getLastLineRead());<br />} catch (Exception e) {<br />  e.printStackTrace(System.err);<br />} finally {<br />  // Close the underlying reader and LineIterator.<br />  jtl.close();<br />}</pre><br />Download <a href="http://mark.koli.ch/2010/01/18/JumpToLine.java">JumpToLine here</a>.&nbsp; Note that <b>JumpToLine is dependent on Apache's Commons I/O</b> API which you <a href="http://commons.apache.org/downloads/download_io.cgi">can download here</a>.<br />]]>
    </content>
</entry>

<entry>
    <title>Happy New Year&apos;s</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2009/12/happy-new-years-1.html?rss" />
    <id>tag:mark.koli.ch,2009://1.221</id>

    <published>2009-12-31T15:55:00Z</published>
    <updated>2009-12-31T16:10:22Z</updated>

    <summary><![CDATA[Last year I wrote up a quick blog post to ring in the New Year, highlighting some of my accomplishments and failures of 2008.&nbsp; In that spirit, keeping the tradition alive, here's my 2009 in a nutshell:I kicked off 2009...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="2010" label="2010" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[<span class="mt-enclosure mt-enclosure-image" style="display: inline;"><img alt="Thumbnail image for shanghai-fireworks-new.year.jpg" src="http://mark.koli.ch/assets_c/2008/12/shanghai-fireworks-new.year-thumb-180x240.jpg" class="mt-image-left" style="margin: 0pt 20px 20px 0pt; float: left;" height="240" width="180" /></span>Last year I <a href="http://mark.koli.ch/2008/12/happy-new-years.html">wrote up a quick blog post to ring in the New Year</a>, highlighting some of my accomplishments and failures of 2008.&nbsp; In that spirit, keeping the tradition alive, here's my 2009 in a nutshell:<br /><br />I kicked off 2009 <a href="http://mark.koli.ch/2009/01/howto-combating-movable-type-trackback-spam.html">figuring out how to block Trackback Spam</a>, and wrote up an <a href="http://mark.koli.ch/2009/01/four-more-reasons-why-i-dislike-facebook.html">opinionated piece re: why I dislike and therefore don't use, Facebook</a>.&nbsp; <a href="http://mark.koli.ch/2009/01/kolichcom-is-not-mycougarlandcom-classic-failure-to-properly-update-dns-records.html">MyCougarLand.com failed to update their DNS records</a>, which generated a lot of traffic (not the kind you would expect) to my blog.&nbsp; In late January, I discovered that I <a href="http://mark.koli.ch/2009/01/jury-acquits-calif-ex-sheriff-mike-carona-of-all-but-1-count-and-all-i-got-was-this-bottle-of-wine.html">own a bottle of wine from a convicted felon</a>.&nbsp; Ala February, I <a href="http://mark.koli.ch/2009/02/finally-joined-twitter-twittercommarkkolich.html">joined Twitter</a>, and heard that <a href="http://mark.koli.ch/2009/03/my-whois-firefox-plugin-featured-by-network-solutions-labs.html">Network Solutions featured my WHOIS Firefox Plugin on their homepage</a>.&nbsp; I bought a <a href="http://mark.koli.ch/2009/03/apc-br1500lcd-review.html">new battery backup UPS for my home data center</a>, after a little physics exercise to <a href="http://mark.koli.ch/2009/03/picking-the-right-ups-battery-backup-and-figuring-out-how-much-it-costs-to-self-host.html">discover the exact type of UPS I needed</a>.&nbsp; I launched <a href="http://mark.koli.ch/2009/03/howto-make-your-own-tiny-url-shortener-service.html">kolich.cc</a>, but then later built and released <a href="http://onyx.koli.ch/">Onyx</a>.&nbsp; With summer approaching, I made my own <a href="http://mark.koli.ch/2009/04/howto-make-your-own-solar-shield.html">solar shield out of cardboard and tinfoil</a> while thinking up <a href="http://mark.koli.ch/2009/04/10-awesome-htaccess-hacks-for-movable-type.html">ten awesome .htaccess hacks for Movable Type</a>.&nbsp; Continuing the awesomeness, I launched a <a href="http://mark.koli.ch/2009/04/kolichcom-is-now-mobile-device-ready-at-kolichmobi.html">mobile version of my blog at kolich.mobi</a> for all of my readers on mobile devices.&nbsp; Like any good year, a few of my <a href="http://mark.koli.ch/2009/05/howto-whole-disk-backups-with-dd-gzip-and-p7zip.html">systems crashed, then recovered</a> but I <a href="http://mark.koli.ch/2009/05/a-little-office-fun-with-hp-laserjet-printers.html">had fun with HP LaserJet printers at the office</a>.&nbsp; I registered <a href="http://mark.koli.ch/2009/05/httpkolichtel-an-interesting-experiment-whois-crossed-with-dns.html">kolich.tel</a>, <a href="http://mark.koli.ch/2009/06/whoa-whats-with-the-green-face-avatar-green4iran.html">went green for Iran</a>, and figured out how to <a href="http://mark.koli.ch/2009/07/howto-include-binary-image-data-in-cascading-style-sheets-css.html">include base-64 encoded binary data in CSS</a>.&nbsp; And, of course, I released <a href="http://mark.koli.ch/2009/07/gagawa-php-12-beta-released.html">Gagawa PHP 1.2</a> before <a href="http://mark.koli.ch/2009/08/mount-san-gorgonio-hike-to-the-summit-and-back-in-a-single-day.html">hiking Mount San Gorgonio</a> in the San Bernardino National Forest, but not before I <a href="http://mark.koli.ch/2009/09/remember-kids-an-http-content-length-is-the-number-of-bytes-not-the-number-of-characters.html">wasted several days at the office on a stupid bug</a>.&nbsp; I <a href="http://mark.koli.ch/2009/09/recently-acquired-kolich-blog-moving-soon.html">registered koli.ch</a> (thank you Network Solutions!), and <a href="http://mark.koli.ch/2009/10/even-better-a-big-5000-x-5000px-animated-gif-for-hot-linkers.html">gave hot linkers a nice big 5000x5000px animated GIF to chew on</a>.&nbsp; And to cap it all off, I <a href="http://mark.koli.ch/2009/11/the-twitter-abacus-silently-logging-every-link-you-click-on-twittercomabacus.html">blew the whistle on Twitter</a> (they're spying on us) and then dove into some C++ to <a href="http://mark.koli.ch/2009/12/uac-prompt-from-java-createprocess-error740-the-requested-operation-requires-elevation.html">discover how Windows UAC works</a>, which by the way, reminded me how much I hate Windows.<br /><br />2009, the end.]]>
        
    </content>
</entry>

<entry>
    <title>Experimenting with More Domains, and Fun with Http 302 Found</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2009/12/experimenting-with-more-domains.html?rss" />
    <id>tag:mark.koli.ch,2009://1.220</id>

    <published>2009-12-29T00:10:00Z</published>
    <updated>2009-12-29T00:11:30Z</updated>

    <summary><![CDATA[My somewhat narcissistic obsession with domain names involving my last name has recently driven me to purchase markkolich.com.&nbsp; The saga leading up to this acquisition started when I snagged koli.ch, and saw my ranking in a few search engine results...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="domains" label="domains" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="http" label="http" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="kolich" label="kolich" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="netsol" label="netsol" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="seo" label="seo" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[My somewhat <a href="http://mark.koli.ch/2009/11/contemplating-cctlds-vs-traditional-dot-com-domains.html">narcissistic obsession</a> with <a href="http://mark.koli.ch/2009/09/recently-acquired-kolich-blog-moving-soon.html">domain names involving my last name</a> has recently driven me to purchase <a href="http://markkolich.com/">markkolich.com</a>.&nbsp; The saga leading up to this acquisition started when I snagged <a href="http://koli.ch/">koli.ch</a>, and saw my ranking in a few search engine results absolutely plummet.&nbsp; I kicked off my own investigation and realized that Google doesn't seem to understand a custom domain hack of my last name.&nbsp; For the record, Yahoo! does.&nbsp; I suppose that's one thing Yahoo! actually does better than Google: properly interpreting domain hacks and other URL trickery.<br /><br />In any event, I setup the <a href="http://markkolich.com/">markkolich.com</a> VirtualHost to redirect to <a href="http://mark.koli.ch/">mark.koli.ch</a> via a <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.3">302 Found</a>.&nbsp; It seems that when Google and Yahoo encounter an <b>HTTP 302 Found</b>, they gracefully redirect their spiders to the destination but maintain the redirector's address.&nbsp; For example, this URL <a href="https://onyx.koli.ch/x2fr">https://onyx.koli.ch/x2fr</a> redirects to <a href="http://plugins.jquery.com/project/Timer">http://plugins.jquery.com/project/Timer</a> using a 302 Found.&nbsp; When spiders encounter <a href="https://onyx.koli.ch/x2fr">https://onyx.koli.ch/x2fr</a> they are forwarded to the destination, but add <a href="http://plugins.jquery.com/project/Timer">http://plugins.jquery.com/project/Timer</a> under <a href="https://onyx.koli.ch/x2fr">https://onyx.koli.ch/x2fr</a> to their index.&nbsp; In other words, the jQuery Timer plugin page will be listed under the URL <a href="https://onyx.koli.ch/x2fr">https://onyx.koli.ch/x2fr</a> in the search results!&nbsp; Interestingly enough, HTTP 301 Moved Permanently does not exhibit this behavior.<br /><br />For the time being, I'm going to see how the new domain stacks up against koli.ch.&nbsp; My expectation is that spiders will index <b>markkolich.com</b>, see a 302 Found, and add it to their index as is without worrying about the destination address.<br /><br />Stay tuned.]]>
        
    </content>
</entry>

<entry>
    <title>A Better Way to Include JavaScript and CSS in your Web-Apps (Break Proxy and Browser Caching too!)</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2009/12/a-better-way-to-include-javascript-and-css-in-your-web-apps-break-proxy-caching-too.html?rss" />
    <id>tag:mark.koli.ch,2009://1.219</id>

    <published>2009-12-23T02:30:00Z</published>
    <updated>2009-12-23T02:42:44Z</updated>

    <summary><![CDATA[Most web-apps include a number of JavaScript and CSS files, that in most cases, need to be included/sourced on every page of the application.&nbsp; Doing so can bring up a few, while trivial, often annoying problems:Dependencies between scripts.&nbsp; For example,...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="css" label="css" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="gagawa" label="gagawa" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="javascript" label="javascript" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="php" label="php" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[Most web-apps include a number of JavaScript and CSS files, that in most cases, need to be included/sourced on every page of the application.&nbsp; Doing so can bring up a few, while trivial, often annoying problems:<br /><br /><ul><li>Dependencies between scripts.&nbsp; For example, if you attempt to use a jQuery plugin before the base jQuery library has loaded into the browser you'll obviously see an error.&nbsp; Working out these dependencies before hand can save you a few headaches.<br /><br /></li><li>Web-browsers and web-proxies cache your JavaScript and CSS files too often.&nbsp; I can't count the number of times on two hands when I made a small change to a JavaScript file, saved the change, refreshed my browser, and .... nothing.&nbsp; The web-browser didn't load the changed file; instead, it loaded the JavaScript from cache.&nbsp; Using a mechanism to always force the web-browser or web-proxy to reload your JavaScript and CSS can save you time.<br /></li></ul>Meet <a href="http://code.google.com/p/gagawa/">Gagawa</a>, an open source object-oriented HTML generation engine written in Java and PHP.&nbsp; You probably wouldn't build an entire site with Gagawa, but it's absolutely perfect for small HTML tasks, like solving this irritating dependency and caching problem.]]>
        <![CDATA[<br />Using Gagawa PHP, problem solved:<br /><br /><pre class="prettyprint">&lt;?php<br /><br />require_once("gagawa.php");<br /><br />$js = array(<br />  // The base jQuery library, needed by all other<br />  // jQuery plugins and your jQuery code.<br />  "/js/jquery-1.3.2.min.js",<br /><br />  // jQuery plugins; jQuery base needs to be loaded<br />  // before you source these.<br />  "/js/jquery.ui.min.js",<br />  "/js/jquery.hotkeys.min.js",<br /><br />  // Some other, unrelated JavaScript library like<br />  // underscore JS.<br />  "/js/underscore.min.js",<br /><br />  // Your JavaScript, with dependencies on jQuery, the<br />  // jQuery plugins and Underscore JS, should be loaded<br />  // last.<br />  "/js/yourjs.min.js"<br />  );<br /><br />$css = array(<br />  "/css/othercss.min.css",<br />  "/css/yourcss.css"<br />  );<br /><br />// Build &lt;script ...&gt;&lt;/script&gt; for each JavaScript<br />// we need, in order. Use time() to break browser<br />// caching.<br />foreach ($js as $url) {<br />  $script = new Script("text/javascript");<br />  echo $script-&gt;setSrc($url."?".time())-&gt;write();<br />  }<br /><br />// Build &lt;link ...&gt;&lt;/link&gt; for each CSS file we need,<br />// in order.  Use time() to break browser caching.<br />foreach ($css as $url) {<br />  $link = new Link();<br />  $link-&gt;setHref($url."?".time())-&gt;setRel("stylesheet")-&gt;setType("text/css");<br />  echo $link-&gt;write();<br />  }<br /><br />?&gt;</pre><br />This code snippet generates some solid output that fits nicely into the &lt;head&gt; of your HTML:<br /><br /><pre class="prettyprint">&lt;script type="text/javascript" src="/js/jquery-1.3.2.min.js?1261531930"&gt;&lt;/script&gt;<br />&lt;script type="text/javascript" src="/js/jquery.ui.min.js?1261531930"&gt;&lt;/script&gt;<br />&lt;script type="text/javascript" src="/js/jquery.hotkeys.min.js?1261531930"&gt;&lt;/script&gt;<br />&lt;script type="text/javascript" src="/js/underscore.min.js?1261531930"&gt;&lt;/script&gt;<br />&lt;script type="text/javascript" src="/js/yourjs.js?1261531930"&gt;&lt;/script&gt;<br /><br />&lt;link href="/css/yourcss.css?1261531930" rel="stylesheet" type="text/css"&gt;&lt;/link&gt;</pre><br />Note that each JavaScript and CSS file is loaded and added to your HTML, in order as desired, eliminating any stray dependency issues.&nbsp; Also, note that I'm appending "?" and the current <a href="http://en.wikipedia.org/wiki/Unix_time">Epoch timestamp</a> to the end of the JavaScript source URL, and CSS Href.&nbsp; PHP's <a href="http://php.net/manual/en/function.time.php">time()</a> function works nicely here because I'll get a different timestamp on each page load.&nbsp; And as far as the browser (or a web-proxy) is concerned, "/js/yourjs.min.js?<b>1261534594</b>" is different than "/js/yourjs.min.js?<b>1234567890</b>" and will be treated as such.&nbsp; With this mechanism, every time you refresh the page, the browser will be forced to reload and reevaluate your scripts.&nbsp; Say goodbye to browsers caching and not recognizing your changes!<br /><br />You can download the latest version of Gagawa at <a href="http://code.google.com/p/gagawa/downloads/list">http://code.google.com/p/gagawa/downloads/list</a>.&nbsp; Also, if you're interested, other Gagawa examples can be found on the <a href="http://code.google.com/p/gagawa/">Gagawa homepage</a>.<br /><br />If you're looking for a real, live, example of this technique in action <a href="https://onyx.koli.ch/">check out Onyx</a>.<br /><br />Merry Christmas.<br />]]>
    </content>
</entry>

<entry>
    <title>Writing Your Own Animation Loop with javax.swing.Timer</title>
    <link rel="alternate" type="text/html" href="http://mark.koli.ch/2009/12/writing-your-own-animation-loop-with-javaxswingtimer.html?rss" />
    <id>tag:mark.koli.ch,2009://1.218</id>

    <published>2009-12-20T20:55:00Z</published>
    <updated>2009-12-21T00:03:17Z</updated>

    <summary><![CDATA[In the last week or so, while working on some Java Swing code for a project at work, I hit Swing bug #4480705.&nbsp; When the user clicked a button to start an "activation", I changed the ImageIcon on a JButton...]]></summary>
    <author>
        <name>Mark Kolich</name>
        <uri>http://mark.koli.ch</uri>
    </author>
    
    <category term="code" label="code" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="gui" label="gui" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="howto" label="howto" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="java" label="java" scheme="http://www.sixapart.com/ns/types#tag" />
    <category term="swing" label="swing" scheme="http://www.sixapart.com/ns/types#tag" />
    
    <content type="html" xml:lang="en-US" xml:base="http://mark.koli.ch/">
        <![CDATA[<span class="mt-enclosure mt-enclosure-image" style="display: inline;"><a href="http://mark.koli.ch/2009/12/20/javax-swing-timer-demo-screencap.jpg"><img alt="javax-swing-timer-demo-screencap.jpg" src="http://mark.koli.ch/assets_c/2009/12/javax-swing-timer-demo-screencap-thumb-180x301.jpg" class="mt-image-left" style="margin: 0pt 20px 20px 0pt; float: left;" height="301" width="180" /></a></span>In the last week or so, while working on some Java Swing code for a project at work, I hit <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4480705">Swing bug #4480705</a>.&nbsp; When the user clicked a button to start an "activation", I changed the ImageIcon on a JButton to an animated GIF to indicate activity (e.g., "we're doing something, please wait").&nbsp; So, while the application was busy, I disabled the JButton and set its ImageIcon to an <a href="http://www.ajaxload.info/">animated spinner</a>.&nbsp; Loading this animated GIF and setting it on the JButton using setIcon() caused the Java Swing EventQueue threads to deadlock, <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4480705">as described here</a>, and my entire application hung.&nbsp; Well technically speaking, not the entire application, but just the Swing portion of the JVM.&nbsp; Clearly, Swing doesn't like animated GIF's as much as the Sun documentation claims.<br /><br />The solution to this deadlock is to avoid using animated GIF's all together, and write your own animate loop using <a href="http://java.sun.com/javase/6/docs/api/javax/swing/Timer.html">javax.swing.Timer</a>.&nbsp; That's exactly what I did, and it works great.<br /><br />For your viewing pleasure, I <a href="http://mark.koli.ch/2009/12/20/SwingTimerDemo.java">wrote up a quick demo/example</a> (see Beavis and Butthead screencap) which demonstrates the use of <b>javax.swing.Timer</b> and how you can integrate it into your Swing application.<br /><br />Download <a href="http://mark.koli.ch/2009/12/20/SwingTimerDemo.java">just the source</a>, or <a href="http://mark.koli.ch/2009/12/20/javax-swing-timer-example.zip">the full Eclipse project</a>.<br /><br />Rock on.]]>
        
    </content>
</entry>

</feed>
