Friday, December 13, 2013

AngularUI Router state and location change events

If you're using AngularJS with the AngularUI Router, and you want to intercept location change attempts so you can prevent certain changes or redirect a user to a login page - without flickering that will occur if you redirect after a page is loaded - one of the things you'll need to understand is the order of location change events and state change events. Their orders are different under different circumstances, a fact which tripped me up until I knew about it.

This was tested specifically with AngularJS 1.2.4 and AngularUI Router 0.2.5.

If the user clicks on a link created with ui-sref, or the state is changed with $state
  • $stateChangeStart will precede $locationChangeStart.
  • Calling event.preventDefault() in a $stateChangeStart listener will prevent the $locationChangeStart event, and will prevent the page change completely.
  • Calling event.preventDefault() in a $locationChangeStart listener without doing the same in a $stateChangeStart listener will prevent the URL from changing but will change the state (content), leaving your app in a bad situation.

If instead the user clicks on a link created with href, or types a URL into the browser, or the location is changed using $location
  • $locationChangeStart will precede $stateChangeStart.
  • Calling event.preventDefault() in a $locationChangeStart listener will prevent the $stateChangeStart event, and will prevent the page change completely.
  • Calling event.preventDefault() in a $stateChangeStart listener without doing the same in a $locationChangeStart listener will prevent the state (content) from changing, but the URL will change, leaving your app in a bad situation.

This difference makes sense in a way. If you trigger a state change, it goes through the UI Router, and then the UI Router triggers a location change. If you trigger a location change through $location, that's part of Angular itself, so UI Router will learn of the event after Angular. Regardless, this makes things tricky if you want your app to respond properly in both scenarios.

The solution
Synchronizing logic within both event listeners would be extremely difficult, if not impossible, to get right. My solution is to switch all my ui-sref links to be normal href links, and when I want to trigger a page change programmatically, I do it through $location, never through $state. That way every page change goes through Angular first, and the $locationChangeStart (and $locationChangeSuccess) is all I have to worry about.

I still use UI Router for its nested views, which I could not do without.

One more thing - the first visit
Keep in mind that in the case of a user typing a URL into the browser, the $locationChangeStart and $stateChangeStart event listeners will only be called if the user already has the site loaded. If it is the user's first visit to the site in that browser session, the listeners will not be called because they will not have been registered when the change started. You'll need another way to keep a user out of certain pages in that case. My solution of choice is to redirect the user from within the resolve block of the controller of each page that a non-logged-in user should not be able to access.

Wednesday, April 28, 2010

Custom Realm in Tomcat 6.0.20

I needed to define a custom realm in Tomcat, in order to customize the way user accounts are checked before being allowed to login. I found several websites that explained bits and pieces, but nothing that explained the whole thing in a way that someone who has never worked with custom realms (i.e., me) could understand. Here is an explanation.

Disclaimer: please note that when I say things like "You should not do X," I am explaining a way to get this up and running simply. This is the way that worked for me. You might need to make changes based on your configuration or if you want it to work differently than I wanted it to work. If you know what you're doing or want to experiment, don't let my suggestions stop you! Of course if that's the case, you probably don't need to read this.

First some links. These helped me, but did not get me all the way there.
Apache Tomcat Configuration Reference
Realm Configuration HOW-TO
HOW-TO again - the section of the document that (at the end) has a very brief paragraph listing some necessary steps.
MBean Descriptor How To
Tomcat 6.0 API

Now, the steps I followed:

  1. Write a class that implements org.apache.catalina.Realm. In my case, I chose to subclass JDBCRealm. This is where you do whatever customization it is that you need to.

  2. In the same package (the same folder) as the class you wrote in the previous step, place a file called mbeans-descriptor.xml. Any name for the XML file should work, since you'll be pointing Tomcat to it later. If you already have one of these and you know what you're doing, you could just add an mbean tag to it. If you have no idea what an MBean is, here is an example:


    <?xml version="1.0" encoding="UTF-8"?>

    <!DOCTYPE mbeans-descriptors PUBLIC
    "-//Apache Software Foundation//DTD Model MBeans Configuration File"
    "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd">
    <mbeans-descriptors>


    <mbean name="MyCustomRealmClass"
    className="org.apache.catalina.mbeans.ClassNameMBean"
    description="My description for this realm"
    domain="Catalina"
    group="Realm"
    type="me.custom.MyCustomRealmClass">

    <attribute name="className"
    description="Fully qualified class name of the managed object"
    type="java.lang.String"
    writeable="false"/>

    <attribute name="connectionURL"
    description="Database URL"
    type="String"/>

    <attribute name="connectionName"
    description="Database connection username"
    type="String"/>

    <attribute name="connectionPassword"
    description="Database connection password"
    type="String"/>

    <attribute name="connectionPassword"
    description="Database connection password"
    type="String"/>

    <attribute name="debug"
    description="The debugging detail level for this component"
    type="int"/>

    <attribute name="driverName"
    description="Database driver"
    type="String"/>

    <attribute name="userTable"
    description="The name of the user account database table"
    type="String"/>

    <attribute name="userNameCol"
    description="The name of the column that holds the username in the database"
    type="String"/>

    <attribute name="userCredCol"
    description="The name of the column that holds the user's encrypted password in the database"
    type="String"/>

    <attribute name="digest"
    description="The encryption method for the user's password"
    type="String"/>

    <attribute name="userRoleTable"
    description="Name of the database table that holds user roles"
    type="String"/>

    <attribute name="roleNameCol"
    description="Name of the column that holds user roles in the database user role table"
    type="String"/>

    <attribute name="validate"
    description="Validate"
    type="Boolean"/>



    </mbean>
    </mbeans-descriptors>


    I went ahead and gave you the whole file that I used.

    In the mbean tag, the only values you should change are the name, description, and type. Don't change className, unless you really know what you're doing.

    Now, what the heck are those attributes? They are whatever fields you want your custom realm to be given when it is created. Take connectionName, for example. We're declaring it here as a String. In your custom realm class, you should provide a getter and a setter for it:

    public void setConnectionName(String connectionName) {
    this.connectionName = connectionName; // or whatever
    }

    public String getConnectionName() {
    return connectionName; // or whatever
    }


    If your custom realm extends an existing realm, as mine does, then you do not have to re-define these getters and setters if they already exist in the superclass. You do still have to declare the fields in your mbean XML.

  3. Point $CATALINA_HOME/conf/server.xml to the mbean XML file. To do this, modify the following Listener to include the location of that XML file; it should look like this:
    <listener classname="org.apache.catalina.mbeans.ServerLifecycleListener"
    descriptors="/me/custom/mbeans-descriptor.xml">
    </listener>


    The descriptors attribute is the one you will/may need to add.

  4. In your application's context.xml file, declare your use of the realm as you would with a built-in realm. Here is where you specify the values to be given to your realm when it is created - those fields you declared in the mbeans file, which you created getters and setters for in your custom realm class. Here is an example - you will obviously want to use different values:

    <Realm className="me.custom.MyCustomRealmClass"
    debug="9"
    connectionURL="jdbc:sqlserver://localhost:1433; SelectMethod=cursor; databasename=MyDbName;"
    connectionName="myAppUser"
    connectionPassword="myPassword"
    driverName="com.microsoft.sqlserver.jdbc.SQLServerDriver"
    userTable="UUSER"
    userNameCol="username"
    userCredCol="passwordEncrypt"
    digest="SHA-256"
    userRoleTable="UUSER"
    roleNameCol="role"
    validate="true" />


  5. Whenever I start up Tomcat, Tomcat copies my web app's context file from webapps/<webapp name>/META-INF/context.xml into $CATALINA_HOME/conf/Catalina/localhost/<webapp name>.xml. Redeploying the web app the dirty way (shutting off Tomcat and replacing the appropriate directory within the webapps folder, then turning on Tomcat again) leaves the old version of this context file in $CATALINA_HOME/conf/Catalina/localhost/. I have to manually delete it before restarting Tomcat in order to get the new version to copy into that directory. Having an old version of your context file there means any changes you make won't actually take effect - at least not this custom realm example.

    I suspect if I cleanly redeployed the webapp, this would not be a problem. However, I have not tried that, and I wanted to point it out.

Saturday, December 27, 2008

Slow Dell Laptop Keyboard in Windows Vista

My computer is a Dell XPS 1330M, purchased January 2008. The computer itself, and tech support, have both been a nightmare since I've purchased them. But, I wanted to talk about something specific, so I'll save the other issues for later.

I noticed recently that typing on my computer became very sluggish. I would type a sentence, and I would have to wait a few seconds for the text to appear on the screen. It didn't matter what program I was in: Firefox, IE, Notepad. I'm a fast typer, but no other computers I've ever used (whether older versions of Windows, or several versions of Linux, or Solaris) have had this problem unless there was a huge memory hog. I could not identify a big memory hog through the Task Manager. Deleting temp files and defragging did not help.

I found the solution on another blog. The problem is the Dell feature that allows you to access media without booting into Windows - called MediaDirect. I disabled the InstantOffice feature through the settings inside MediaDirect (which is on my start menu), and immediately I could feel the mouse was less sluggish (I hadn't even noticed it was sluggish). I tested typing in Notepad, and it felt fine. No reboot was necessary, although I did anyway for good course - it is Windows after all. No problems with typing since then! Admittedly it's only been a day.

Interesting to note that the problem began around the time I bought my iPhone. Shortly after buying it I moved all my computer's contacts into Windows Contacts, which makes it available to InstantOffice. I think that's what made it so slow. The author of the post I referenced above wrote that the next day his computer became sluggish again, and removing MediaDirect fixed it for good. For me, simply disabling it did the trick.

My New Blog

Being both a software developer and a regular computer user, there are problems I encounter for which I sometimes spend hours searching for answers. My goal for this blog is to document the answers I eventually find or figure out to those problems, with the hope that it will help someone else with the same problem.

If you find something useful, or have a better way to solve a problem, feel free to comment!