Friday, May 9, 2008

Passing Additional Parameters to MatchEvaluator

I think the easiest way to explain what this blog entry is about is to just give a real life situation I had. I am using Microsoft’s LogParser (what a great tool) to pull all Windows event logs from different server into one SQL Server database. LogParser does this quite nicely. A side note: I noticed that by default –formatMsg is set to ON which causes end of line characters to go away. This can be annoying when you want to display formatted message from the Windows Event log. I highly recommend setting –formatMsg to OFF if you want the Message column in the Windows EventLog to be formatted as you see it in the built in Event Viewer. With that said, I am not going to go into how to do this because it is not important to this blog entry.

What is important is that you can assume you have a string and you want to use regular expressions to do a search and replace on keywords. One of the big problems is that if you create an actual delegate method for the MatchEvaluator to call it can only have one parameter and that is the one it is expect. This is an issue if you need to pass additional information such as the css style (in my case) to this method. An alternate way of doing this in .Net 2.0 or later is to take advantage of Anonymous methods (See http://msdn.microsoft.com/en-us/library/0yw3tz5k(VS.80).aspx for more information). Anonymous methods allow you to cross traditional scope boundaries of variables as use the variables of the method that has defined the Anonymous method.

public string HighlightKeyWord(string bodyOfText, string regex, string cssStyle)
{
try
{
   bodyOfText = Regex.Replace(bodyOfText, regex,
      new MatchEvaluator(
         delegate(Match m) { return string.Format("<span class=\"{0}\">{1}</span>", cssStyle, m.Value); }
      )
   );
} // end try
catch (ArgumentException ex){ // Syntax error in the regular expression }
return bodyOfText;
}

Notice in this code cssStyle variable is referenced in the anonymous method even though it is defined in the parent method.

Here is an example of how to use this code:

public string HighlightUserName(string text)
{
   return HighlightKeyWord(text, "mydomain\\\\\\w+", "highlightUserName");
}

In this example of usage, I assume that there is a css class called highlightUserName. It would change the text such that all places that have mydomain\myusername in the text would be changed to <span class=”highlightUserName”>mydomain\myusername</span> and thus show as formatted text in the browser.

Tuesday, April 22, 2008

Paste Unformatted Text in Word

If you copy and paste formatted text from different sources into MS Word you may have noticed that it usually will paste it in the original format. This is nice in some cases, but in most cases I find that I want the text to be unformatted (so that it uses the format of the paragraph that I am pasting it into in MS Word). To paste unformatted text all you have to do is go to the Edit Paste Special… menu item and when the dialog appears, select the “Unformatted Text” and click the OK button. While this may be an easy solution it is a complete waste of time in comparison to just typing Control-V. If you do this a lot, you want to be able to paste unformatted text with a single key stroke. Here is how to do this.

In MS Word, go to Tools menu Macro Record New Macro…

In the dialog that appears enter a name for your macro. It can’t contain spaces, and really isn’t important other than it should describe what you are doing with it. In this case, I named my macro, PasteUnformattedText. After entering the macro name, click the Keyboard button. This will open the Customize Keyboard dialog. In the Customize Keyboard window, type the key stroke you want to use to invoke the macro. I set mine to Control-Shift-V. Be sure to click the Assign button, then click the Close button. You will then see a little floating window / palette. Use it to stop recording. At this point you have a macro that is invoked by a key stroke of your choice, but it doesn’t do anything. Now let’s make the macro actually do something. Go to Tools menu Macro Macros… to bring up the Macros dialog. Select your macro, and click the Edit button. This will open the MS Visual Basic code editor. You will see a method that is called whatever you called your macro. If there is any text (besides the ones with an apostrophe before them – these are comments) in the method body, delete it. Your editor should look something like this… Sub PasteUnformattedText()

‘ PasteUnformattedText Macro ‘ Macro recorded 4/22/2008 by usb00528 End Sub

Add one line to this so that it looks like the following… Sub PasteUnformattedText()

‘ PasteUnformattedText Macro ‘ Macro recorded 4/22/2008 by usb00528 Selection.PasteAndFormat (wdFormatPlainText) End Sub

Save and close the window. No give it a test by copying some formatted text, and pasting it using your keystroke. Side Note You don’t have to follow these exact steps to get this done. You can create a new macro, and then later go to Tools Customize… and then select the Macros item from the Categories list. Then select your macro, and click the Keyboard… button to get to the Customize Keyboard dialog you used as described above. The bottom line is, you need to create a macro some how, put the one line of code in it, and assign a keystroke, or a toolbar item if you prefer.

Thursday, April 3, 2008

SharePoint Usage by List Type

SharePoint uses MS SQL Server and thus we can directly query this database to do some reporting. Below is a query that counts the number of entries for each type of list (Announcements, Contacts, Discussion Boards, Document Library, Events, Generic List, Issue List, Liks List, Image Library, InfoPath Form Library, Survey, Task List, Other). For example, if we look at the the sample results below there are 6932 documents in all the document libraries in all sites within SharePoint.

Select case tp_servertemplate when 104 then 'Announcement' when 105 then 'Contacts' when 108 then 'Discussion Boards' when 101 then 'Document Library' when 106 then 'Events' when 100 then 'Generic List' when 1100 then 'Issue List' when 103 then 'Links List' when 109 then 'Image Library' when 115 then 'InfoPath Form Library' when 102 then 'Survey' when 107 then 'Task List' else 'Other' end 'ListType', sum(tp_itemcount) as EntryCount from lists inner join webs ON lists.tp_webid = webs.Id Where tp_servertemplate IN (104,105,108,101, 106,100,1100,103,109,115,102,107,120) and tp_itemcount > 2 -- if there are only three then it is likely the sample data or a test record group by tp_servertemplate order by 2 desc

Example Results:

Type # of Entries Document Library 6932 Generic List 400 Events 356 Issue List 328 Task List 305 Announcement 292 Links List 281 Discussion Boards 276 Survey 193 Image Library 147 Contacts 128 Other 18 InfoPath Form Library 3

While that is an interesting indicator of how much the feature is being used, it doesn’t really a good picture of how many sites are using the features. In other words, all 6932 documents could be on one site and no other site is using document libraries. Below is a query that gives the number of sites that are using each type of list.

select count(distinct(webs.fullurl)) 'NumberOfSitesThatUseType', case tp_servertemplate when 104 then 'Announcement' when 105 then 'Contacts' When 108 then 'Discussion Boards' when 101 then 'Document Library' when 106 then 'Events' when 100 then 'Generic List' when 1100 then 'Issue List' when 103 then 'Links List' when 109 then 'Image Library' when 115 then 'InfoPath Form Library' when 102 then 'Survey' when 107 then 'Task List' else 'Other' end as Type from lists inner join webs ON lists.tp_webid = webs.Id Where tp_servertemplate IN (104,105,108,101, 106,100,1100,103,109,115,102,107,120) --and tp_itemcount > 2 and FullUrl like 'sites/%' group by tp_servertemplate order by 'NumberOfSitesThatUseType' desc

Example Results: Sites Type 135 Document Library 31 Links List 30 Announcement 27 Issue List 21 Task List 20 Discussion Boards 18 Events 13 Survey 10 Contacts 7 Image Library 7 Generic List 1 Other

List Sites To get a list of all top level sites I recommend the following query: select * from webs where FullUrl like 'sites/%' To get a list of all sites (includes sites created below sites) I recommend the following query. NOTE: This will also include sub sites like Meeting Work spaces, etc. select * from webs w join sites s on (w.siteid = s.id) where w.FullUrl like 'sites/%'

Find sites that are not using a type of list Sometimes you want to find all the sites that are NOT using a particular type of list. First thing you need to know is that each list type is stored as a number, not a human friendly string. Here are the mappings you will need to understand what the numbers mean. You will notice the above queries translate them using a case statement. Note: the tp_servertemplate field can have the following values:

  • 104 = Announcement
  • 105 = Contacts List
  • 108 = Discussion Boards
  • 101 = Document Library
  • 106 = Events
  • 100 = Generic List
  • 1100 = Issue List
  • 103 = Links List
  • 109 = Image Library
  • 115 = InfoPath Form Library
  • 102 = Survey List
  • 107 = Task List

You will need to know these number for the below query. Just change the number in the query to the type of list you want to use. select webs.fullurl as [Site Relative Url], webs.Title As [Site Title], lists.tp_title As Title, tp_description As Description, tp_itemcount As [Total Item] from lists inner join webs ON lists.tp_webid = webs.Id Where tp_servertemplate = 105 -- Contact List order by tp_itemcount desc

Wednesday, April 2, 2008

Starting and stopping Windows Service from the command line

In this article I will show how to restart (stop then start) a Windows Service from the command line. While it is true you can do all this from the user interface using the mouse, sometimes it is useful to be able to script restarting of services if a service is hung or down for some reason. An example of this is Apache Tomcat (assuming you installed it as a Windows Service and not to run from the command line to start with. I am going to use Apache Tomcat Windows Service as an example, but this article should apply to any Windows Service.

The below assumes you have already started a command prompt by going to Start Menu | Run… and typed cmd and then enter.

List Services

First the first thing you may want to know how to do is get a list of all started Windows services.

C:\>net start

These Windows services are started:

Apache Tomcat

Application Layer Gateway Service

Automatic Updates

Background Intelligent Transfer Service

COM+ Event System

Cryptographic Services

….

Stop a Service

Running the command below will stop the Apache Tomcat service. It is important to note the use of double-quotes here. You need them so that Apache Tomcat is treated as one parameter instead of two. The same goes for any other service with a space in its name. Also, notice the name use with the start command must match what is shown in the list above.

C:\>net stop “Apache Tomcat”

The Apache Tomcat service was stopped successfully.

Start a Service

Running the command below will start the Apache Tomcat service. The same basic rules apply as when you stopped the service.

C:\>net start “Apache Tomcat”

The Apache Tomcat service is starting.

The Apache Tomcat service was started successfully.

Restart a Service

Running the command below will restart (stop and immediately start) the Apache Tomcat service. The same basic rules apply as when you stopped the service.

C:\>net stop “Apache Tomcat”

The Apache Tomcat service was stopped successfully.

C:\>net start “Apache Tomcat”

The Apache Tomcat service is starting.

The Apache Tomcat service was started successfully.

Friday, March 21, 2008

Converting to a different timezone using C#

Here is some code that I like to use for converting between timezone using C#. There are two methods. One that uses DateTime and the other uses DateTime? which can be null.
/// <summary> /// Converts a date that is assumed to be have an offset from UTC of sourceUtcOffset to an offset from UTC of targetUtcOffset /// </summary> /// <param name="sourceDate">The date and time that needs to be converted</param> /// <param name="sourceUtcOffset">The offset from UTC of the sourceDate</param> /// <param name="targetUtcOffset">The offset from UTC of the timezone that the DateTime should be converted to.</param> /// <returns>A DateTime that has been adjusted to the offset from UTC of targetUtcOffset</returns> public static DateTime ConvertDateTime2AnotherTimezone(DateTime sourceDate, int sourceUtcOffset, int targetUtcOffset) { DateTime asUtcDateTime = sourceDate.AddHours(sourceUtcOffset); DateTime targetDateTime = asUtcDateTime.AddHours(targetUtcOffset); return targetDateTime; }
/// <summary> /// Converts a date that is assumed to be have an offset from UTC of sourceUtcOffset to an offset from UTC of targetUtcOffset /// </summary> /// <param name="sourceDate">The date and time that needs to be converted</param> /// <param name="sourceUtcOffset">The offset from UTC of the sourceDate</param> /// <param name="targetUtcOffset">The offset from UTC of the timezone that the DateTime should be converted to.</param> /// <returns>A DateTime that has been adjusted to the offset from UTC of targetUtcOffset</returns> public static DateTime? ConvertDateTime2AnotherTimezone(DateTime? sourceDate, int sourceUtcOffset, int targetUtcOffset) { if (sourceDate.HasValue) { DateTime nonNullSourceDate = sourceDate.Value; DateTime asUtcDateTime = nonNullSourceDate.AddHours(-1 * sourceUtcOffset); DateTime targetDateTime = asUtcDateTime.AddHours(targetUtcOffset); return new DateTime?(targetDateTime); } // if there is no value, then it is null, and thus we have nothing to convert else { return sourceDate; } }

Thursday, March 20, 2008

Lessons learned with Web Services in Eclipse 6

I am using MyEclipse 6. It is quite nice. I learned the hard way were to find some resources and how to do things like Exception handling. I found the process to be much more difficult and the learning curve much steeper than I thought it would be. Here are some things I learned. MyEclipse has a project type called Web Service Project. Use this to start a new project that will be your web service. It also allows you to use XFire web services (not CXF until version ?? of MyEclipse). You can use Java 1.5 or Java 5. Getting Started:
Start with the tutorial I mention in my blog to get started: http://www.myeclipseide.com/documentation/quickstarts/webservices/
Examples: Get the XFire distribution (http://xfire.codehaus.org/Download and pick the Binary Distribution in zip package for the newest release) because it has some good examples in it. I didn't find this until way too late. You can also download the source code for XFire on this page if you want to also. Once you download the zip I highly recommend the Book example. It shows how to return complex types, and how to create your own custom exceptions. How to store application settings
I am sure there are many ways to do this... check out my blog on: http://justgeeks.blogspot.com/2008/03/storing-application-settings-in.html Make Complex types that use java.util.Date as a data type be nillable when generated in the WSDL.
The basic solution is create a file called YourClassNameHere.aegis.xml and put it in the same location as the class that defines the complex type that contains the java.util.Date property. In the file, do something like this:
<mappings>
<mapping>
<property name="datePropertyName" nillable="true"/>
<property name="anotherDatePropertyName" nillable="true"/>
</mapping>
</mappings>
Now your automatically generated WSDL will have nillable="true" for the properties that you specified in the mapping file. This is particular important when using ASP.Net 2.0 and Nullable Types to consume your web service. It means the difference between DataTime and DateTime?. For more details check out: http://www.nileshk.com/node/69#comment-12609 Exception Handling You can follow the example in the Book example noted above, or you can do it another way also. You can do much like they did in the Book example, except just have your custom exception class extend java.lang.Exception instead of the XFire specific type. This will cause your custom exception class to be serialized and set as the Detail property in the SoapException. You can then parse it using a standard XML parser on the client side. You will be catching an Exception of type SoapException on ASP.Net. If you want your properties of your custom exception class to show up in the serialized text, you will need to make sure that you have setters and getters for each member variable in your class. One key is that your web methods must explicitly throw your custom exception, not just java.lang.Exception. If you don't, the Detail property of the SoapException won't be filled in when it is serialized and sent to your client (web service consumer). This basically tells Aegis to use our custom exception type instead of just java.lang.Exception. Changing parameters (in0, in1, in2, etc) to the real names when the WSDL is generated. I searched long and hard to find a simple solution for this one. Much to my amazement there did not seem to be too many intuitive solutions. I picked the easiest one. My solution is very simple and requires no maintenance. It seems to be a bug to me, so.... Do just like the tutorial referenced in the Getting Starting section of this blog and add the new web service just as says EXCEPT when you get to the page that asks you for the Service Interface and Service impl. class, set the Service interface to the same thing as Service impl class. This will affect your server.xml file. Finish the wizard as noted in the tutorial. Next, open the xxxImpl.java file and remove the implements xxxx from the class declaration such that it does not implement the interface file. If there is an interface file you can safely remove it from your project now. You will never need it now. Now, when the WSDL is generated it will use the proper parameter names as you would expect. Go figure! Word of caution, I did not have as much luck doing effectively manually doing the same thing AFTER I did the wizard as the tutorial suggests. So, I suggest doing it from the start when you do it in the wizard. I also found that I couldn't query the for the WSDL without my CPU going to nearly 100% when I did it manually. I don't really know why, but it did. I suspected that maybe I just had to many methods in my class and was slowing the parsing of the class to generate the WSDL or something, so I put all my real code in another class and just have stub like web methods that simply call the other object and a method on it. This seemed to help, but all these issues could have been do to needing a reboot and trying too many different things and messing up some files. Hard to tell, so let me know if anyone else has a similar experience. Other Tips Make your life easier, just forget about getting access to the Request and other servlet type things. It is just too much of a pain. Stay away from web.xml when it comes to storing application settings. Accessing it programmatically seems to be container (application server) specific, so use a properties file or xml file.

Storing application settings in a property file

This is not a totally necessary class, but it doesn't abstract loading text from a properties file. For example if you need to store application specific settings or even localizations you can use this class to access it using flexible paths (dot notation or directory path style). Here is how to use it: Properties props = PropertyLoader.loadProperties("/some/package/Config.properties"); String val = props.getProperty(key); package mypackage;
import java.util.ResourceBundle; import java.util.Properties; import java.io.InputStream; import java.util.Locale; import java.util.Enumeration;
public class PropertyLoader { /** Creates a new instance of PropertyLoader */ public PropertyLoader() { } /** * Looks up a resource named 'name' in the classpath. The resource must map * to a file with .properties extention. The name is assumed to be absolute * and can use either "/" or "." for package segment separation with an * optional leading "/" and optional ".properties" suffix. Thus, the * following names refer to the same resource: * <pre> * some.pkg.Resource * some.pkg.Resource.properties * some/pkg/Resource * some/pkg/Resource.properties * /some/pkg/Resource * /some/pkg/Resource.properties * </pre> * * @param name classpath resource name [may not be null] * @param loader classloader through which to load the resource [null * is equivalent to the application loader] * * @return resource converted to java.util.Properties [may be null if the * resource was not found and THROW_ON_LOAD_FAILURE is false] * @throws IllegalArgumentException if the resource was not found and * THROW_ON_LOAD_FAILURE is true */ public static Properties loadProperties (String name, ClassLoader loader) { if (name == null) throw new IllegalArgumentException ("null input: name"); if (name.startsWith ("/")) name = name.substring (1); if (name.endsWith (SUFFIX)) name = name.substring (0, name.length () - SUFFIX.length ()); Properties result = null; InputStream in = null; try { if (loader == null) loader = ClassLoader.getSystemClassLoader (); if (LOAD_AS_RESOURCE_BUNDLE) { name = name.replace ('/', '.'); // Throws MissingResourceException on lookup failures: final ResourceBundle rb = ResourceBundle.getBundle (name, Locale.getDefault (), loader); result = new Properties (); for (Enumeration keys = rb.getKeys (); keys.hasMoreElements ();) { final String key = (String) keys.nextElement (); final String value = rb.getString (key); result.put (key, value); } } else { name = name.replace ('.', '/'); if (! name.endsWith (SUFFIX)) name = name.concat (SUFFIX); // Returns null on lookup failures: in = loader.getResourceAsStream (name); if (in != null) { result = new Properties (); result.load (in); // Can throw IOException } } } catch (Exception e) { result = null; } finally { if (in != null) try { in.close (); } catch (Throwable ignore) {} } if (THROW_ON_LOAD_FAILURE && (result == null)) { throw new IllegalArgumentException ("could not load [" + name + "]"+ " as " + (LOAD_AS_RESOURCE_BUNDLE ? "a resource bundle" : "a classloader resource")); } return result; } public static Properties loadProperties (final String name) { return loadProperties (name, Thread.currentThread ().getContextClassLoader ()); } private static final boolean THROW_ON_LOAD_FAILURE = true; private static final boolean LOAD_AS_RESOURCE_BUNDLE = true; // if true localization and caching is used. private static final String SUFFIX = ".properties"; }