• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Jeanne Boyarsky
  • Ron McLeod
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • paul wheaton
  • Rob Spoor
  • Devaka Cooray
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
  • Frits Walraven
  • Tim Moores
Bartenders:
  • Mikalai Zaikin

"STRUTS-like action"

 
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I am playing around with developing a front controller for a test web app. The controller works but right now just simply forwards all requests to a JSP page. Remember, I am just testing here.
What I need to do now is implement some sort of Struts-like Action so that instead of redirecting to a JSP page, I am redirected to an "action" that eventually forwards to a JSP. I know, STRUTS already does all this. I know how to use STRUTS. I am trying to basically make a STRUTS-light to play around with.
So would I need to use reflection to instantiate the "action" class? Or is there a different way?
Thanks for any tips.
 
Gregg Bolinger
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Ok, I went ahead and used some reflection to run my "action". It seems to work ok, but I would like to know if there are other ways of doing this.
Thanks.
 
Sheriff
Posts: 67746
173
Mac Mac OS X IntelliJ IDE jQuery TypeScript Java iOS
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Gregg, that's exactly how I do it. Once I identify the class name of the appropriate action (all of which implement my Action interface) I instatiate the action via Class.newInstance().
bear
P.S. I also pool the actions once created to reduce object thrashing...
[ December 26, 2003: Message edited by: Bear Bibeault ]
 
Gregg Bolinger
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Bear Bibeault:
Gregg, that's exactly how I do it. Once I identify the class name of the appropriate action (all of which implement my Action interface) I instatiate the action via Class.newInstance().
bear
P.S. I also pool the actions once created to reduce object thrashing...
[ December 26, 2003: Message edited by: Bear Bibeault ]


Thanks Bear. Right now I am working on the method invoking that I need in each of my Action classes. I can't believe how simple the struts architecture really is when you take out all the Jakarta Commons Packages STRUTS adds to thier distro.
 
Gregg Bolinger
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
P.S. I also pool the actions once created to reduce object thrashing...
Huh?
 
Bear Bibeault
Sheriff
Posts: 67746
173
Mac Mac OS X IntelliJ IDE jQuery TypeScript Java iOS
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Rather than creating the action object for every request that needs to use it, I instantiate a single instance (using lazy creation) of the class and pool it for reuse. This means, of course, that the action must be thread-safe (since more than one simultaneous request may need the action), but that's no big deal.
bear
P.S. Method invocation should be straight-forward if your actions all implement an interface with the required methods.
P.P.S Yeah, that's one reason I'm not a member of the Struts fan club -- great idea, messily implemented imho.
[ December 26, 2003: Message edited by: Bear Bibeault ]
 
Sheriff
Posts: 7001
6
Eclipse IDE Python C++ Debian Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
If you want an even simpler solution, you don't even need the reflection. Just construct each of the available action instances in "init", fill a Map of some sort with them, pop the Map in the application context, and when a request comes in, just pick the pre-built action out of the map.
The downside with this is that you can't change your mind about which actions you have in the system without a recompile. But you have already said that you are using JSP, so you must have a compiler on the deployment machine anyway.
An upside is that you get early indication of possible missing actions. With a reflection-based approach, you don't really know until a request comes in whether the named action exists or is valid.
P.S. Count me in to the Struts non-fan-club. I sometimes disapair how many people seem to think that Struts is the only web framework, or that Struts is the only MVC implementation. :roll:
 
Gregg Bolinger
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Frank Carver:
If you want an even simpler solution, you don't even need the reflection. Just construct each of the available action instances in "init", fill a Map of some sort with them, pop the Map in the application context, and when a request comes in, just pick the pre-built action out of the map.
The downside with this is that you can't change your mind about which actions you have in the system without a recompile. But you have already said that you are using JSP, so you must have a compiler on the deployment machine anyway.
An upside is that you get early indication of possible missing actions. With a reflection-based approach, you don't really know until a request comes in whether the named action exists or is valid.
P.S. Count me in to the Struts non-fan-club. I sometimes disapair how many people seem to think that Struts is the only web framework, or that Struts is the only MVC implementation. :roll:


That's not a bad idea Frank. I think I will code a test case your way and see which feels better to my own style. I liked using Reflection because I never have before and I felt all techie doing it. But I am all for simplicity. This would also help keep me from having do define some config file for my actions.
Something else I am trying to do with this is create the app in a way that security is built into the software instead of using the App Server. The reason for this is because App Servers tend to markup their security definitions differently or use different types of Database access for authentication or a different type of XML file with the Roles, etc.
So for each action I have, I would like to specify somewhere that it requires a certain level of authentication prior to executing. So I might have a config that looks something like:
action=actionName
authentication=true
role=admin
success=page1.jsp
failure=page2.jsp
That way, the controller can say whether or not the user has rights to continue.
Now, I thought about doing this with a Filter and I did some testing but I am not sure if I should use a Filter or not. It seems to just add another layer of complexity and that is what I am trying to stay away from. What do you guys think?
[ December 27, 2003: Message edited by: Gregg Bolinger ]
 
Ranch Hand
Posts: 309
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
greg,
this discussion reminds me of the time (few months ago) when I was getting started with designing a web appication, all by myself for the first time as part of my internship and I had to decide on using various technologies like Struts, JSF... I decided to write my own MVC framework though on a smaller footprint.Reason, I dont know Struts and was not sure abt the learning curve and I also did not know JSPs(is JSPs a must for STRuts, i donno).
I did exactly what you said in your initial posts, used reflection, had a controller that loads the appropriate module(action) by looking into a config.xml file. Well, I am happy that I now have built a complete web application(it is not very big) and which is live now, based on ideas from Struts and also my industry experience
Now getting back to the your question on authentication(you mean authorisation right, to be precise), I think it would be a good idea to have the controller delegate this task to an authorisation component, meaning, the controller after getting the request can delegate the task of authorisation to another component which would return whether the user is authorised to perform this task or not. And then based on that, you can use reflection to call the action or throw "unauthorised" message back.
I used Filter in my application for a simple task to check whether the user is logged in when he is requesting something. So, anyone who tries to cut-copy-paste my URL will get redirected to the login page if he is not logged in. I handle this logic in my filter.
I would like to know any suggestions/improvements on my mentioned ideas. This post is really interesting cos it's bases on my recent and ongoing work.
Regards,
Shankar.
[ December 27, 2003: Message edited by: shankar vembu ]
 
Gregg Bolinger
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Now getting back to the your question on authentication(you mean authorisation right, to be precise)...
I probably do mean authorization. Thanks. Handing the authorization off to a "delegate" does sound like a good solution. Now, when you did this though, do you mean you just checked whether or not the user was allowed to view a page or perform an action and you let the filter handle the actual login check? So if I understand it correctly, when a page is accessed, the filter checks to make sure the user is logged in and if not, redirects to login. If the user is logged in, the Controller then determines if the user has the correct previliges?
If I am understanding this correctly, why seperate the login and role check? Why not just let the Filter do both and let the Controller direct traffic. OR, why not lose the Filter and let the Controller do both? Just curious.
 
Frank Carver
Sheriff
Posts: 7001
6
Eclipse IDE Python C++ Debian Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
If I am understanding this correctly, why seperate the login and role check? Why not just let the Filter do both and let the Controller direct traffic. OR, why not lose the Filter and let the Controller do both? Just curious.
Of course you can take these alternative approaches if you want. The main reason for the suggested approach is to clarify the distinction between authentication (login) and authorization (role/resource/action decision).
In many simple systems, these two things are effectively the same - any authenticated user has all roles. In the general case, though, an authenticated user may have several roles (and lack several others), or may be part of several user groups which themselves have roles. Roles may affect at a fine-grained level which actions are allowed on which resources, and so on.
In such complex cases, authentication (login) is relatively simple - check to see if the session has a valid "user" object, if not then redirect to some sort of login/warning/welcome page suite to establish one. The key thing is that such a process does not need to know anything about the requested actions, supplied parameters or affected resources. Authentication is therefore an ideal job for a lightweight filter. Using this approach also has the major benefit that it can be used to protect static resources (HTML pages, stored documents, images etc.) which are not served using the controller.
The potential complexity of the authorization process is why it is usually assigned to the controller. The main job of the controller is to decide on what to do based on the supplied information about requested actions, parameters and resources. Adding "roles" as another variable in to this mix does not significantly affect the job.
Has this helped?
 
Gregg Bolinger
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Frank Carver:
If I am understanding this correctly, why seperate the login and role check? Why not just let the Filter do both and let the Controller direct traffic. OR, why not lose the Filter and let the Controller do both? Just curious.
Of course you can take these alternative approaches if you want. The main reason for the suggested approach is to clarify the distinction between authentication (login) and authorization (role/resource/action decision).
In many simple systems, these two things are effectively the same - any authenticated user has all roles. In the general case, though, an authenticated user may have several roles (and lack several others), or may be part of several user groups which themselves have roles. Roles may affect at a fine-grained level which actions are allowed on which resources, and so on.
In such complex cases, authentication (login) is relatively simple - check to see if the session has a valid "user" object, if not then redirect to some sort of login/warning/welcome page suite to establish one. The key thing is that such a process does not need to know anything about the requested actions, supplied parameters or affected resources. Authentication is therefore an ideal job for a lightweight filter. Using this approach also has the major benefit that it can be used to protect static resources (HTML pages, stored documents, images etc.) which are not served using the controller.
The potential complexity of the authorization process is why it is usually assigned to the controller. The main job of the controller is to decide on what to do based on the supplied information about requested actions, parameters and resources. Adding "roles" as another variable in to this mix does not significantly affect the job.
Has this helped?


Perfect explination Frank. Thanks. That really helps! Now I know exactly how I want to do what I need to do.
 
(instanceof Sidekick)
Posts: 8791
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Cool thread. I have done something similar in a hand-thrown Wiki server. All the discussion of putting actions in a map and getting the action that matches the incoming request sounded very familiar. But I didn't see what keys you are using to "get" the action. I haven't used Struts so I guess I couldn't make that part of the Struts-like leap. For my own, I put two hidden fields on every form that posts: formname and action. The combination is the key to the map. I also used the Post-Redirect-Get pattern heavily - post never generates HTML.
 
Ranch Hand
Posts: 3404
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I'm not sure why reflection is OK to use in a JSP context - ie a web application ? Surely reflection doesn't allow for a good separation between client-processing and server-side processing ?
 
Gregg Bolinger
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
For my own, I put two hidden fields on every form that posts: formname and action.
Here is the only downside I see to doing this. I don't like calling jsp's directly. I would rather my "actions" forward to all jsp's.
Now, speaking of doing that, the only reason I do it this way currently is because I was told by someone else that this was the MVC way. Since JSP's are for viewing purposes only, you should never call one directly.
I would like to know other thoughts on this...
 
Gregg Bolinger
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by HS Thomas:
I'm not sure why reflection is OK to use in a JSP context - ie a web application ? Surely reflection doesn't allow for a good separation between client-processing and server-side processing ?


What do you mean by this? I think maybe you aren't clear as to what we are using reflection for. Instead of have a servlet for every single bit of processing in a web app. We (and STRUTS) uses 1 servlet called a "Front Side Controller" servlet. It looks up the "actions" from a config file or a mapping of some sort and dynaically loads and calls a method on the appropriate action class. This is what we use reflection for.
 
shankar vembu
Ranch Hand
Posts: 309
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
All,
We have so far discussed the request flow, and how does one return the response.In my design, every action(module) generates an XML file and returns it to the point where the action was delegated to the action class. Over there, I use an XSL, perform a transformation and return the response. In this way, each module in my application executes it's businness logic and generates an XML. One can do whatever he wanna do with this XML. In the above case XML+XSL=HTML.
There are some drawbacks in my design.
1. Form data: I was not quite sure how to separate presentation+business logic when handling form data. Meaning, I access the form elements' name in my action classes. So if i change the names, I have to perform the change in the client code + server code. I was OK with this cos mine is not a very big application with many forms. But I know this is not exactly the right way to do. I think Struts has something called form bean, i.e. the action module returns a form bean and the JSPs use this form bean to create the forms.Right? I dont know JSPs and how would one handle these issues if he is not using JSPs, in my case the presentation layer is all XML+XSL.
2. JDBC: How would you design the JDBC layer?? In my case, I have a bean class for every table in my database. And I write all JDBC operations in the respective beans called database beans. And again, since my JDBC layer is not big, I was OK with all my beans filled with SQL queries. Atleast this is the only place where I have the SQL queries and not spread all over the codebase. And this is also the place where I access the form data and perform insertion,updation,deletion to/from the database. In this way, I could move all this work to one area.
The second issue is not exactly related to servlets. Feel free to ignore it. Since JDBC layer is part of many applications, I thought it would be OK to raise this issue here.
Regards.
 
Gregg Bolinger
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I have always just used JSP's for my presentation. Each action will basically forward the request/response to a JSP depending on what happens in the delegate. Pretty much like with STRUTS how you define your forward mappings in the action parameters of the struts-config.
I however, don't like how I have to have an ActionForm (which is really just a bean with an additional validation method if you choose) and then how I also have to have the actual FormBean or DynaForm if so choses. I just use simple Javabeans for processing all my JSP forms. It's simple and easy.
As far as the Database Layer, typically, I create a completely seperate Data Layer by creating classes for getting and recieving info from the DB. These classes are used from my action classes. I have been looking at Hibernate lately which I like, but not sure how I feel about the added complexity (IMHO) and all the extra XML files. Remember, I stay away from STRUTS because I can do it simpler. I don' really see a problem having SQL stuff in a JavaBean for your form though. I have done that before also.
 
Frank Carver
Sheriff
Posts: 7001
6
Eclipse IDE Python C++ Debian Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Stan wrote: I put two hidden fields on every form that posts: formname and action
This is pretty close to what I have used a few times. The only improvement I have managed is to use a more abstract form/page type rather than the actual form name. This allows multiple similar forms to share actions if necessary, and decouples the specifics of the forms even more from the implementation.
Gregg, I think you (or I) may be missing the point. I read Stan's suggestion as including the name of the form the POST is coming fromand an action. It is entirely up to the controller to decide which page to despatch the request to once it has been processed. Think of it as a finite state machine. Knowing a "current state" and a "transition", the state machin is able to calculate the next state (which can, of course, be the same as the originating state, if required).
HS Thomas wrote: I'm not sure why reflection is OK to use in a JSP context - ie a web application ? Surely reflection doesn't allow for a good separation between client-processing and server-side processing ?
And Gregg replied: It looks up the "actions" from a config file or a mapping of some sort and dynaically loads and calls a method on the appropriate action class. This is what we use reflection for.
My worry (and, I guess HS's worry, too) is how closely coupled the names of the "actions" are to actual class names. If the class name is somehow implicitly inferred from the text of the action parameter (for example by loading and instantiating a class with a similar name) then this is too closely coupled for my liking. If there exists in a config file somewhere an explicit mapping between action parameter text and class name, then the framework is more loosely coupled. However, if this is the case, you might as well generate the source code to initialise a Map with prebuilt classes at "build" time, and not bother with run-time reflection at all.
So how does Struts do this? How does it go from an abstract action parameter (such as "edit") to a particular method call on a particular object? Why does Struts seem to need reflection?
 
Gregg Bolinger
Ranch Hand
Posts: 15304
6
Mac OS X IntelliJ IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
So how does Struts do this? How does it go from an abstract action parameter (such as "edit") to a particular method call on a particular object? Why does Struts seem to need reflection?
Exert from struts-config.xml

path is simply the URI stripped down to just the requested "page". Type is where struts uses reflection. When the request comes in for "/InputSubmit" the struts controller knows to load the class "app.InputAction" and invoke the public ActionForward execute method passing it the request and response objects. The whole thing is pretty trivial really.
BTW - I have never actually looked at the STRUTS source to verify this, so it is an assumption at best. But I'm willing to bet it's pretty close.
[ December 28, 2003: Message edited by: Gregg Bolinger ]
 
HS Thomas
Ranch Hand
Posts: 3404
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Frank Carver : However, if this is the case, you might as well generate the source code to initialise a Map with prebuilt classes at "build" time, and not bother with run-time reflection at all.
Using reflection at build time would be a better solution, IMHO.
 
HS Thomas
Ranch Hand
Posts: 3404
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
shankar vembu : And again, since my JDBC layer is not big, I was OK with all my beans filled with SQL queries. Atleast this is the only place where I have the SQL queries and not spread all over the codebase. And this is also the place where I access the form data and perform insertion,updation,deletion to/from the database. In this way, I could move all this work to one area.
I think this is the same idea behind using JSTL - and is not recommended for complex database processing.
 
Frank Carver
Sheriff
Posts: 7001
6
Eclipse IDE Python C++ Debian Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
HS Thomas wrote: Using reflection at build time would be a better solution, IMHO.
Could you expand on this, please? I've never personally found a use for reflection at build time - I've always found that the ability to generate code before compilation has eliminated any necessity for it. What do you see as the advantages of build-time reflection?
 
HS Thomas
Ranch Hand
Posts: 3404
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I meant using reflection at build time than at run-time was a better solution.BTW I'm not terribly familiar with any other types of code generators like Ant or XDoclet. I'd be surprised if they don't use reflection in the pre-compile stages.
Generating the code sounds nifty if by that it's meant finding and copying method names and other properties into new classes with new functions. It seems to me using reflection would be a lot more powerful as reflection can detect network conditions as in generating dynamic proxies or other circumstances that change. Perhaps it depends on what is being generated and whether it should be generated at run-time or build time.
If you do not not have access to the source code for a class or its documentation, then using the Reflection API is the only way to learn about its constructs. Using methods like getConstructors(), getMethods() and getFields(), we capture objects representing these parts into arrays. We can then query each object about its specific properties. For example, for a Method object, we can retrieve its name (getName()), its modifiers (getModifiers()), exceptions it may throw (getExceptionTypes()), parameter types (getParameterTypes()) and return type (getReturnType()).
This could be really wrong , in which case I hope someone points out just how wrong it is.
[ December 29, 2003: Message edited by: HS Thomas ]
 
HS Thomas
Ranch Hand
Posts: 3404
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
What do you see as the advantages of build-time reflection?
Primarily detecting changes to the project ,build-time reflection could throw up discrepancies like method name changes that may have crept in since the last compilation of a class.
.
[ December 30, 2003: Message edited by: HS Thomas ]
 
Frank Carver
Sheriff
Posts: 7001
6
Eclipse IDE Python C++ Debian Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I'd be surprised if they don't use reflection in the pre-compile stages.
In general they don't, because they don't need to. Ant provides a built in method of "token substitution" to replace "build time variables" with values (from config files or calculated by the build process). Xdoclet reads the source code and looks for instructions in specially formatted comments.
In the general case, at "build time" there is plenty of source code and configurations about, but very few precompiled class files. Reflection may be considered as a way of looking at a loaded class file and working out what was in the source code. But if you have the source code to hand, there's not much point.
Generating the code sounds nifty if by that it's meant finding and copying method names and other properties into new classes with new functions. It seems to me using reflection would be a lot more powerful as reflection can detect network conditions as in generating dynamic proxies or other circumstances that change. Perhaps it depends on what is being generated and whether it should be generated at run-time or build time.
Code generation is often much more simple than what you describe. In the current release version of my Friki source code, for example, I have a few lines which say:

In my ant build files the token "filter.class" is read from a config file passed in as a parameter, and I have a "batch script" which builds different versions based on different configs:
ClassicOneDotFour.prp:

build.bat:

After filtering for the "ClassicOneDotFour" build, but before compilation, the above code snippet has been changed to valid, compilable code:

The big advantage with this sort of process is that the code which is actually compiled and delivered contains none of the setup and configuration stuff which is really only needed at build or deploy time. Each of the three builds contains only the class files actually needed, and each delivered class file contains only the lines of code actually used by that build.
Neat, huh?
 
HS Thomas
Ranch Hand
Posts: 3404
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
That does look neat and simple,Frank
 
I would challenge you to a battle of wits, but I see you are unarmed - shakespear. Unarmed tiny ad:
a bit of art, as a gift, the permaculture playing cards
https://gardener-gift.com
reply
    Bookmark Topic Watch Topic
  • New Topic