|
|
I got myself recruited into eProductivity
|
Peter Pressnel wrote a post about class casting and polymorphism in LotusScript. Unfortunatelly, in LS you can only extend one class, not implement Interfaces (like in every other OO language). Interfaces lets you add functionality to a given class by implementing the Interface: a Person will also by "Compareable" by implementing that interface. Now every method which needs a "Compareable" argument can work with Persons.
This missing feature in LS basicly means that you have to build this functionality anew every time you need it: you need a person.compareToAnotherPerson(aPerson as Person) and also your sorted list (or whatever needs something compared) needs to know about that method. This means a lot of unnecessary programming and also it binds your code together and you will very soon get either a very big LS file or the famous "Illegal circular use" error. Not nice!
But we have help from the Adapter Pattern. Instead of adding the functionality by implementing an interface we ask for an adapter of the object which implements this interface [1].
The adapater pattern is usually also implemented via an interface: IAdaptable.getAdapter(classname) which either returns an object of that classname or nothing. As there is no Interfaces in LS this method is implemented in a basic class from which every other of my custume classes is derived: BasicObject. This class also implements "toString() (usefull as a message to logError()...) and such basic things.
The API contract for that method is very easy: either return an object of the specified class, which represents the original object or nothing. Classcasting an object now becomes:
dim manager as Manager set manager = person.getAdapter("MANAGER" if manager is nothing then Error 1000, "Person is not a Manager!" [...]
instead of
dim manager as Manager if not (person isa ucase("Manager" ) then error 1000, "Person is a not a manager!" set manager = person [...]
So the insert method of a sorted list, which expects a "Compareable" object (or nothing), would be something like this: list.insert(me.getAdapter("COMPAREABLE" ).
This is the BaseObject.getAdapter implementation.
Class BaseObject ' [ public function toString() as String ... ]
'/** ' * Returns an Adapter of the current Object of the aked-for type or nothing ' * The basic implementation returns this object if the caller asked for the class ' * or a subclass of the class of the current object ' * Should be overwritten to do something more usefull! ' * @autor Jan Schulz ' */ Public Function getAdapter(nameOfObject As String) As BaseObject If Ucase(nameOfObject) = Typename(Me) Then Set getAdapter = Me End If If Me Isa Ucase(nameOfObject) Then Set getAdapter = Me End If End Function End Class
This LotusScript was converted to HTML using the ls2html routine, provided by Julian Robichaux at nsftools.com.
An addition would be the implementation of an AdapterManager (see the article on eclipse.org): This would even solve the problem that you need to know the adapter class in the "adaptable" object (-> problems with "Illegal circular use"...). This would need a small AdapterFactory with one methods: getAdapter(object as BaseObject, classname as String). This factory would be implemented for each used combination and then an instance of such an object added to the AdapaterManager. Now you can query the AdapaterManager for an Adapater of your object: getAdapterManager().getAdapter(object, "SomeClassName" . In this method you would ask each adapterFactory for this combination and return if you get an object back (or nothing if no factory can return such an object). Now the object wouldn't need to know that it is adaptable to a "SomeClassName".
[1] This pattern is usually used in other OO languages to keep the API of an class small and clean: if you need your person compareable, the methods of the "Compareable" Interface would be part of the API contract for the "Person" class. This is usually not desireably, especially if you add serveral interfaces.
|
The Adapter Pattern or working around that LS has ...
|
After reading Chris Toohey's Quickpoll about Framesets vs. Web I started to think about rewriting a typical Notes Interface (outline to the left, views on the right) to be more "web 2.0" friendly with a landing page. To complete the challenge, the UI should be translateable and, of course, nice looking. The first problem: the typical landing page has "call to action" buttons: big, shiny buttons with an icon and some text. So, how can you do that in Notes? My tries:
- Buttons: can be translated via onLoad events and "document.forms[0].Button.value = document.forms[0].cfdField_LabelForButton.value", no icons possible
- HTML pass thrugh Computed text (=translateable) with <input>s -> just plain ugly.
- HTML pass thrugh Computed text with <button>s -> Does not
work, as the notes internal html parser does not recognise the button
tag
- Notes Table (with nice looking background + rounded corners) and a hotspot -> does not work, as the hotspot can't be set around the table and also because I haven't found a way to vertical center text in a table cell.
- Outline with one entry, styled with rounded corners, translateable the UI (lables are computeable), mouse over effects for the text. Only problem: setting width to "fits to content" has no effect, which means that the translations must be as long as the original text (seems to me that this is a notes bug).
Here is a screenshot:
 The last one with the styled outline entry seems to be the best idea up to now, but the "fit to content" problem is bad 
Anybody with some ideas how to get nice, translateable, hover aware buttons with icons on it in the notes client?
Here is the demo DB...
|
Nice looking buttons in the Notes Client
|
Another " Fun with..." blog entry...
Substitution are great: tired of domian.com/subdir/blog.nsf/dx/something? Then define a URL substitution (/subdir/blog.nsf -> /blog) and you get domain.com/blog/dx/something. Thats nice!
Unfortunately there are some gotchas as we found today:
- You can't use the substituted URL as a homepage: putting in /blog/dx/default will result in an 404. Which would anyway be stupid as your linked content (css, js, pictures) is not anymore available, as the browser gets the result of it as / and not anymore as /blog/dx/something. So looking for ../css/default.css would result in domain.com/../css/default.css, which is of course not available. The workaround: put the complet URL there: http://www.domain.com/blog/dx/default and the browser gets a redirect to that URL. Unfortunately this results in an additional dns request in most browsers, (= a little more wasted time) . Just redirecting to /blog/dx/default seems not to be possible (please prove me wrong...).
- You can't get the browsed un-substituted URL *anywhere* in the called code: there you only get the translated CGI Variables, meaning you have no clue what you are called. This is a problem if you want to redirect to a DB configured default homepage. An example: I would browse domain.com/blog, this would open the subdir/blog.nsf and in there the configured element. In my case it is a page (form or agent not possible
), which has some ComputedText on it, which redirects to the default document (/dx/default, configured in a profile doc). As the redirect needs to be absolute, I need the pathentry before the view name. @WebDBName gives /subdir/blog.nsf, as gives *all* CGI variables with path infos (at least in an agent). Now I need to have this piece of information available in the DB Profil, that this DB is called /blog and not /subdir/blog.nsf. Keeping the same data in two place is bad. - The same problem arise, when you use logins and substituted URLs: call "/blog/page?OpenPage&login" and you get redirected to "/subdir/blog.nsf/page?OpenPage&login" (using "/blog/dx/default?login" you get redirected to "/". WTF?). This is not nice if you really want to hide your internal structures. I haven't yet tried what happens if you just put anonymous as No access and so get a loginpage without ?login. I suspect it's the same.
All in all: URL substituition is really usefull, but doing somethin more facy with it and it becomes a struggle...
|
Fun with ... URL substitution
|
Oh my, wasn't there a more professional way to do this:
 Basicly it looks like they took two things and just placed them on top of each other... When you click on the splash screen you can move it around, but without the progress bar. If you click on another process and then on the notes process, only the progressbar is shown (fun again...).
Anyway: back to the basic client... Why?
- You can open two ID Files/ Notes instances at the same time (normal and Admin)
- Simple much faster
- a lot easier for the admin (i know the internals of my data directory to fix any isue I came across, but not of the data/workspace one...)
- I haven't found anything in the fancy new UI, which I really need.
8.5 will be bad: I can't open two IDs anymore, because I need the designer client in my admin instance, which is usually the second client I open. And using Designer in 8.5 means that I have to open my admin notes first (one of them can be a standard client, but it must be the first one), which basicly means that each time I have to look something up in the design, I have to close my normal basic notes, my admin basic notes, open the admin standard notes, switch to designer and open my normal notes. And it takes ages to open a not-yet-opend database in DDE. This sucks!
|
Notes 8.0.2 standard splashscreen and rant about s...
|
|
|
Mailserver: server task hang at 100% CPU - Problem...
|
|
|
Notes 8.x Help System also bound to all interfaces...
|
|
|
Mailfile Owner changeable via web, even if forbidd...
|
Notes 8 and higher seems to listen to some ports. Unfortunatelly it seems that it listen to the wrong interface, as I get a firewall warning:
 TCPView shows this:
 So notes listen on all interfaces (0.0.0.0) instead of on an local one (which would show 127.0.0.1). To show another example, here is "hamster", a local mailserver, which does the right thing and binds only to the 127.0.0.1 interface and never showed up via such a firewall warning:
 So two things:
- Why does Notes need to listen on all interfaces?
If I guess right it is used to communicate between the 'oldstyle' nlnotes and the new eclipse user interface. If so: no need to bind to 0.0.0.0, 127.0.0.1 is enought. This could also mean that someone outside can use my nlnotes autenticated client to see the information? Or is notes expecting callbacks from the server? That would be something new for me and as calling nlnotes itself does not do this, so I suspect that this is not the case. => So I see no reason why notes couldn't bind to 127.0.0.1 only: less problems with security and no problems with the firewall...
- If notes needs to bind to that interface, the installer needs to drill a hole into the firewall, as *every* other windows ("server"
programm, which needs to do that (this includes skype, bittorent thingies and so on...). I've normaly no admin rights on my laptop (hapy user of sudo fror windows) and our normal office users do not have that at all -> we had to add that to all clients -more or less- manually after installing the client  So: did I miss something? If not: can someone at IBM please fix either the interface-binding or the installer? Thanks!
|
Notes 8.x and the windows firewall
|
I've a linux server, which sometimes hangs with a server task at 100% CPU and lines like this in the log: " Unable to access notes.ini. Determine what application or hardware fault is preventing access. Previous cache values used." or " Error updating local ID file: Could not open the ID file". A restart fixes this, but never for long (sometimes same day, sometimes weeks). It seems like this started with the 8.0.2 Domino.
I searched the IBM site for similar bugreports an found Domino server hang after NTI error iError: 9, failed with TLOOK. Unfortunatelly I've no idea, if that's really our bug (somedebug output needs to be enabled, but the article does not say what) and also the SPR seems not yet in the fix list.
We habe a PA Account, but I couldn't find the "report a bug" link on the IBM site and it seems I don't have the rights to open a Service request. So: How do I report a bug? Just write it in the forums?
|
Domino Bug: how to report it?
|
|
|
Initial Mailfilequota uses GB instead of MB
|
Just a short follow up to my problem from this afternoon about recompile going into an endless loop or not saving at all, when changing the order of the Use statements (Error "Duplicate PUBLIC Name" : it seems that the compiler can't cope with long names of script libs. My scriptlib was named "de.katzien.ls.MVC.Model.MonitoringCapability". Changing it to "de.katzien.ls.MVC.Model.Monitoring" fixed the problem.
|
Compile Problem fixed: Scripname was too long
|
... or to hell... Currently I'm trying to build an LS framework, which is runtime configurable. Instead of using static functions or simple class extention, I try to build an object graph, where each object adds some functionality. So something which needs to run after a Document is saved will add a observer to the document model and do the work in the right callback method. This results in small objects and class libs, but lots of use of "Use". Unfortunatelly designer seems to be unable to cope with this. Right now I'm in a situation, where designer compiles my code, if I press strg+s, but is sent into an endless loop if I use "Recompile all Lotusscript".
Other fun includes "Use" ordering: this compiles with strg+s (but sends "Recompile all" into an endless loop):
Use "class::logger" Use "de.katzien.App.Infomail" Use "de.katzien.App.InfomailDB" Use "de.katzien.ls.Email"
this not:
Use "class::logger" Use "de.katzien.App.InfomailDB" Use "de.katzien.App.Infomail" Use "de.katzien.ls.Email"
The only change is the ordering... Error: Duplicate PUBLIC Name "CHANGE_LISTENER_EVENT_BEFORE_CHANGING" in USE de.katzien.App.Infomail This name is the public Const in script lib "MonitoringCapability", which is use'd directly by .App.Infomail and indirectly by .App.InfomailDB (uses InfomailEntry which uses MonitoringCapability). setting this to private just ends up with the next error and I can't set a complete class to private if I want to use that class in some other lib...
And don't even ask about "Illegal Circular Use".
@IBM: Please improve the Compiler for LotusScript!
|
Sending Designer into endless loops with "Recompil...
|
Todays fun: my logger (basicly an AgentLog, but with an underlying NotesDocument) opened a second doc where it shouldn't and worse didn't save the first document with the proper logging output. Turned out, that the last logAction call in the logger.delete sub (called by the garbage collector) found that the underlying logdoc was nothing (and so not saved until I turned on flushAlways) and so created a new one with only the "new log opend" and "log closed" message in it. Fun if you expect half a page of debugging output for an webagent (which you need because the agent is not working ) and only get "Log opened and closed"...
The fix was to cache the parent Database of the Logdocument in the Logger object.
This Database was actually cached (Dim logDB...) in the OpenLogFunction Declaration section (the Logger is an extention), but it seems that this script lib was garbage collected before calling delete on my object and as the DB got collected, so gets every object which came from that DB. By-by logdoc...
It seems that something changed between last years domino (7.0.x or 8.0.1, not sure which I tested on) and 8.0.2 in the garbage collection routine, as the same logger setup worked some month ago without this problems. Or I garabage collection is influenced how many classes/ScriptLibs you use in the your agent...
So, todays lesson learned: Do not cache objects in other scriptlibs Declaration section if you use that object in delete... Fortunately this is my only class with a deconstructor 
|
Fun with ... garbage collection
|
I like to write code like this:
public function getSomething() as Something if oSomething is nothing then set oSomething = new Something() end if set getsomething = oSomething end function
How do this with dynamic arrays? I was very surprised when this threw an error:
dim arrSomethings() as Something if isArray(arrSomethings) then print ubound(arrSomething) ' unitialized array error when calling the ubound function
You have to call a redim arrSomething(0 to 0) bevor you can do anything with that array. I tested it with every isXYZ function I could find in the designer help, but i couldn't find a way to detect that...Somehow I agree with Tommy Valand: LS Arrays are strange. Give me a Java collections like API and I'm happy  BTW: datatype (arrSomething) gives "8738", even after the redim, but the designer help says that "8704" is a Dynamic array. Bug?
|
How to test if an array is initialized?
|