简介
Jakarta Tapestry是一个开源的Java Web应用框架。你或许会说:"大同小异的东西罢了."多数
情况下,我同意你的观点,然而,只要花上些许时间研究一下,你会发现Tapestry跟别的框架大为不同,它是值得严肃对待的。
Tapestry是一个基于控件的框架以致于用它开发Web应用类似开发传统的GUI应用。你用Tapestry开发Web应用时你无需关注以操作 为中心的(operation-centric) Servlet API.引用Tapestry网站上的一句话:"Tapestry用对象(objects),方法(methods),属性(properties)替代 以往的URLs和查询参数, 重新诠释Web应用开发.Tapestry 3.0即将发布,它有大量的改进和新的特性。
Tapestry的目标
简单性
Tapestry应用与传统的Servlet应用相比代码量更少。大多数传统的Servlet应用包含如下厌烦和
重复的任务:解析查询参数,处理HttpSession对象,构建URLs。Tapestry消除了传统Servelt应用中许多无趣的"衔接"代码("plumbing" code)使开发者把精力集中到应用逻辑上来。
一致性
Tapestry为开发Web应用的页面提供了一致的方式。这样有助于消除传统Servlet应用开发中臆测。由于所有Tapestry应用中的页面都是用相同的可复用的控件组织而成,所以工作方式是相似的。
效率
Tapestr应用拥有高度的可升级性,它利用缓存和对象池使每个请求的处理时间最小化。Tapestry应用拥有跟传统Servlet应用相仿的性能。
错误反馈
任何开发过Servlet/JSP应用的人毫无质疑有类似经历:为了找出Web.xml文件有什么错误,不得不花费大量时间察看浏览器中的堆栈信息。Tapestry拥有优秀的错误报告方式,最值得一提的是它会指出哪个文件以及那一行导致了错误。
与Struts比较
既然Apache Struts可能是当今应用最广泛的Web应用框架,拿Tapestry与它比较是唯一公平的。以下是一些观察比较,它们来源于为这篇文章开发的几个简单的Tapestry应用和为几个Struts工程的工作经历。
Struts的优点
1 一个Servlet/JSP开发者熟悉Struts无需太久。然而Tapestry的学习曲线会长一点,因为它与流行的Web应用框架不太相同 。
2 Struts在Java社区里被广泛接受和使用。为你的项目找一个好的Struts开发人员并非难事,Tapestry近来在开发者社区里 赢得一些关注,然而仍有许多Java老手不知道Tapestry为何物。
3 既然Struts被如此广泛的使用,所以有很多Struts资源可供参考。相比大多数开源软件,Tapestry拥有非常可观的资源和文档,但跟Struts相比仍有差距。
Tapestry的优点
1 你开发一个Tapestry应用无需关注Servlet API,你也无须为你的Servlet应用写一些典型的"衔接"代码。虽然Struts简化了工作,然而Servlet API 仍是你需面对的。
2 Tapestry的页面模板除了几个特别属性和标识就是一个标准的HTML文件。 如果你是一个开发小组中的HTML设计高手,然而你不懂Java或JSP,这就给你带来很大方便。
3 因为Tapestry页面是标准的HTML,所以可以用HTML所见即所得(WYSIWYG)编辑器编辑和预览该页。当一个页需要修改它的外观并不需要通知服务器让它重新编译JSP.
4 Tapestry不需要一个至高的,整个应用范围的配置文件。Tapestry应用中的每一页是独立的,改变一页不会影响开发其他 页面的开发者,因为并没有一个配置文件把所有页面的浏览粘连在一起。
5 Tapestry拥有极好的错误报告。如果你在一个页的模板或页面规范犯了一个错误,Tapestry会指出导致错误的行号。
6 用Tapestry开发是种乐趣。这样说听上去似乎老调,然而用Tapestry开发一个Web应用相比应用其他流行的框架更为自然有趣。用Tapestry开发是应用了一种基于控件的架构,与开发传统的GUI应用非常相似。
Tapestry 架构
Tapestry框架是标准Servlet API的一种扩展。它需要J2SDK1.2或更高版本的J2SDK和一个与Servlet API 2.2(或更高)兼容的应用服务器/Servlet容器。
一个Tapestry应用由许多拥有唯一名称的页面组成。一个页面由一个模板和一些可复用的控件构成。模板由标准的HTML标签和一些额外的属性和标签构成,这些额外的属性和标签是为了告诉Tapestry框架这个页面的那些部分是由Tapestry控件组成。
简单的Tapestry应用
为了最好的描述构建一个Tapestry页面的方方面面,我们可以看看这个Pig Latin翻译器应用的代码。这个应用只有一个页面,在这页里输入一个text值把它翻译成Pig Latin,然后把翻译好的值显示给用户。
在Tapestry应用中每个页由3个部分组成:一个HTML模板,一个页面规范文件,一个Java类。
这里有这个页面屏幕抓图:
页面模板由标准的HTML标签和一些额外的属性和标签构成,这些额外的属性和标签是为了告诉Tapestry框架这个页面的那些部分是由 Tapestry控件组成。页面模板存放在Web应用的根context目录下。通常,Tapestry在启动时会寻找和呈现一个名叫"Home"的页。 虽然我们可以改变这种行为,但依照Tapestry的惯例会更简单。
Home.html
<html>
<head>
<title>Tapestry Pig Latin Translator</title>
</head>
<body>
<h1>Pig Latin Translator</h1>
<form jwcid="@Form"① listener="ognl:listeners.submit"②>
<table border="1">
<tr>
<td>Value to Translate:</td>
<td>
<input type="text" jwcid="@TextField"③ value="ognl:inputValue"/>
</td>
</tr>
<tr>
<td>Pig Latin:</td>
<td>
<jwcid="@Insert"④ value="ognl:pigLatinValue"/>
</td>
</tr>
</table>
<input type="submit" jwcid="@Submit"⑤ value="Translate"/>
</form>
</body>
</html>
页面模板的绝大部分是普通的HTML,只有少部分Tapestry特有的属性和标签。这种模板机制的优势就是Tapestry页面模板可以在一个可见即所得的编辑器里创建和预览。描述Tapestry控件部分的标识是有限的和突出的。
标识里的jwcid所指是被应用的Tapestry控件的Java Web Component ID.在上面代码断里,我们是隐式地使用控件。隐式的控件是指直接在页面模板里声明使用的控件,。jwcid的前缀 '@ '符号就是通知Tapestry这里声明使用了一个隐式控件。
在上面的Pig Latin Translator页面模板里用了四个控件:Form①, TextField③, Insert④ 和Submit⑤。它们只是Tapestry框架提供的包含超过40个控件的控件库里的四个。在后面的范例中,我们将会看到如何使用显式控件。显式控件是 指控件在页面规范文件里声明后再使用的控件。
在前面的HTML模板里,使用控件的同时,也为控件指定了参数。例如控件Form①有一个listener②参数它指定了当表单提交时对应的页面类 调用的方法名称。那个ognl:前缀的使用贯穿页面的HTML模板,指向的是Object Graph Navigation Language (OGNL)。OGNL是一个强大的开源的表达式语言,用于将页面内控件的属性绑定到页面类的属性。
现在我们看看页面规范文件。页面规范文件是一个扩展名为page的XML文件,这个文件有许多职责,在众多职责中最基本是指定页面对应的Java类。页面规范文件存放在webapp的WEB_INF目录。
Home.page
<?xml version="1.0"?>
<!DOCTYPE page-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd>
<page-specification class="Home">
<property-specification name="inputValue" type="java.lang.String"/>
<property-specification name="pigLatinValue" type="java.lang.String"/>
</page-specification>
页面规范文件的根元素有一个class的属性,它指定了这个页对应的Java类。这个类必须要实现org.apache.tapestry.Ipage接口。页面规范同时页定义了两个属性(property)元素,以便Tapestry在页面类里创建新的属性。
Tapestry框架提供了org.apache.tapestry.html.BasePage class,它实现了Ipage接口。页面类被存放在Web-INF/classes目录下,跟你的Web应用的所需要的其他类放在一起。
Home.java
import org.apache.tapestry.html.BasePage;
import org.apache.tapestry.IRequestCycle;
public abstract class Home extends BasePage {
public abstract String getInputValue();
public abstract void setInputValue(String inputValue);
public abstract String getPigLatinValue();
public abstract void setPigLatinValue(String pigLatinValue);
public void submit(IRequestCycle cycle) {
String inputValue = getInputValue();
String pigLatinValue = new PigLatinTranslator().translate(inputValue);
setPigLatinValue(pigLatinValue);
}
}
你要提醒的第一件事或许是这个类为什么是抽象类。它还有几个抽象方法访问inputValue,pigLatinValue属性。这里利用了Tapestry会在运行时刻创建子类的功能,这个子类会创建你在页面规范里声明的属性和生成相应的访问方法。
在表单提交时页面类的submit方法会被调用。为什么会这样?因为我们在页面模板里将Form控件的listener属性指定为:ognl:listeners.submit。这就意味着一个名叫submit的listener会通过页面类的listeners被访问。
所有的页面类和控件类都从org.apache.tapestry.AbstractComponent这个类继承来一个叫listeners的属性。当submit方法完成后,页面会显示被翻译好的词。
最后讲讲Web.xml这个Web发布描述文件。Tapestry,像许多其他的流行的Web应用框架一样,由一个Servlet构成,但是还需要一个发布描述文件。那个发布描述文件应该被存放在WEB-INF目录。
web.xml
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd>
<web-app>
<display-name>Tapestry Pig Latin Translator</display-name>
<servlet>
<servlet-name>tapestry</servlet-name>
<servlet-class>org.apache.tapestry.ApplicationServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>tapestry</servlet-name>
<url-pattern>/app</url-pattern>
</servlet-mapping>
</web-app>
虽然Pig Latin翻译应用非常简单,但是它会让你对在Tapestry应用中一个页面的3个组成部分有了基本的了解。它也展示了创建一个Tapestry应用的一个页面只需写多么少的代码。
表单输入验证
Tapestry 提供了一些控件以便校验用户的输入。校验子系统是ValidField控件的核心。在下面的登录应用中我们将使用ValidField控件。ValidField控件位于表单内,对用户在客户端的校验提供了有用的反馈和视觉上的错误提示。
区域化
在Tapestry中,区域化是相当简单的。Tapestry允许文字和图形的区域化。为了区域化页面的内容,你可以为每一个添加一个 properties文件,或者提供一个区域化的模板。为每个页面提供一个资源文件的方式远比管理和维护一个巨大的全局的应用范围的资源文件简单。如果页 面的区域化并不仅仅只是文字信息的区域化,例如页面的布局不同或者组成的控件不同,这样情况提供区域化的页面模板就能派上用场了。我们会在下面的登录应用 的使用Tapestry区域化。
创建控件
Tapestry发布时提供了40多个自带的控件。如果你想知道更多的关于Tapestry自带控件的信息,请参考Tapestry Component Reference.想看看Tapestry的控件的应用范例可访问Tapestry Component Workbench.如果你发现你需要一个Tapestry本身没提供的控件,你可以自己创建一个。创建你自己的Tapestry控件跟创建一个页面是相 似的。一个典型的Tapestry控件由一个控件规范文件(XML文档),一个HTML控件模板,一个实现了 org.apache.tapestry.Icomponent接口的Java类。这个议题有点超出本文的范围,但是如果你有兴趣学习如何创建你自己的 Tapestry控件,你可以参考Tapestry的原创人,Tapestry In Action 一书的作者--Howard Lewis Ship写的 Designing Tapestry Mega-Components 。
Tapestry 登录应用
你在Pig latin翻译器应用中看到了Tapestry的一些基本特性。与其用一个复杂的应用展示Tapestry所有的特性以致于压得你揣不过气来,还不如通过 一些简单的应用让你找到一点对Tapestry的感觉。下面这个应用展示Tapestry如何处理页面导航,区域化,验证和其他一些特性。
这里有一个Home页的屏幕抓图,下面跟着它的页面模板。
Home.html
<html>
<head>
<title>Welcome to the Tapestry Login Application</title>
</head>
<body>
<h1>Welcome to the Tapestry Login Application</h1>
<span jwcid="@PageLink"① page="Login">Login</span>
</body>
</html>
这个Home页的页面模板除了一个jwcid属性定义使用一个Tapestry PageLink①控件以外都是标准的HTML。
PageLink控件生成了一个指向Login页的超链接。既然Home页没有任何动态的行为所以它不需要页面规范和页面对应的Java类。
这里是Login页的屏幕抓图,后面跟着是它的页面模板。
Login.html
<html>
<head>
<title>
<span key="title">①Login</span>
</title>
</head>
<body jwcid="@Body">②
<span jwcid="@Conditional" condition="ognl:beans.delegate.hasErrors">③
<div style="color: red">
<span jwcid="@Delegator" delegate="ognl:beans.delegate.firstError">④
Error Message
</span>
</div>
</span>
<p style="font-weight: bold" >
<span key="hint">Hint: Your password is your username spelled backwards.</span>
</p>
<form jwcid="@Form" listener="ognl:listeners.login" delegate="ognl:beans.delegate">
⑤
<table>
<tr>
<td align="right">
<span jwcid="@FieldLabel" field="ognl:components.inputUsername"⑥>
Username:
</span>
</td>
<td>
<input type="text" jwcid="inputUsername"⑦ value="simpson_h"
size="30"/>
</td>
</tr>
<tr>
<td align="right">
<span jwcid="@FieldLabel" field="ognl:components.inputPassword">
Password:
</span>
</td>
<td>
<input type="text" jwcid="inputPassword" hidden="true" value=""
size="30"/>
</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="submit" jwcid="@Submit" value="message:login"/>
</td>
</tr>
</table>
</form>
</body>
</html>
这个页面模板大多数是通常的HTML。我们从页面模板中可以看到Tapestry的区域化特性:它使用一个span元素,这个span元素带有一个 叫key的属性,key的值映射到Login.properties文件里一个属性。一个Body控件被声明使用,因为它对客户端的JavaScript 校验是必需的。
为Form component⑤设定delegate属性激活表单输入验证。delegate属性是我们在页面规范里声明的 org.apache.tapestry.valid.IvalidationDelegate的实现类。如果验证错误发生了,我们用 Conditional component③控件判断delegate是否有任何错误,如果有就把第一个错误④显示给用户。如果ognl 表达式ognl:beans.delegate.hasErrors 为true,Conditional控件将显示它的内容实体。所有的页面类和控件类都从AbstractComponent继承来一个叫beans的属 性。这个beans属性是一个org.apache.tapestry.IbeanProvider的实例,利用它可以通过名字取得在页面规范文件里定义 的beans.FieldLabel⑥被用于为inputuserName validField控件显示标签,这个FieldLabel控件也被用来与表单的验证代理协作,指出包含错误的输入域。
InputUserName⑦控件是一个显示控件的例子。显式控件是指在页面规范文件声明的控件。InputUsername和inputPassword控件都是显式的,它们与FieldLabel联合显示它们的displayName属性。
下面的是Login页的资源文件。Login.properties跟页面规范一并存放在WEB-INF目录。
Login.properties
title = Login to the Application
hint = Hint: Your password is your username spelled backwards.
login = Login
username = Username:
password = Password:
invalidpassword = Invalid Password
Here is the page specification for the Login page.
Login.page
<?xml version="1.0"?>
<!DOCTYPE page-specification PUBLIC
"-//Apache Software Foundation//Tapestry Specification 3.0//EN"
"http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd>
<page-specification class="com.ociweb.tapestry.Login">
<bean name="delegate" class="org.apache.tapestry.valid.ValidationDelegate"/>①
<bean name="requiredValidator"②
class="org.apache.tapestry.valid.StringValidator">
<set-property name="required" expression="true"/>
<set-property name="clientScriptingEnabled" expression="true"/>
</bean>
<property-specification name="username" type="java.lang.String"/>
<property-specification name="password" type="java.lang.String"/>
<component id="inputUsername" type="ValidField"> ③
<message-binding name="displayName" key="username"/> ④
<binding name="validator" expression="beans.requiredValidator"/> ⑤
<binding name="value" expression="username"/> ⑥
</component>
<component id="inputPassword" type="ValidField"> ⑦
<message-binding name="displayName" key="password"/>
<binding name="validator" expression="beans.requiredValidator"/>
<binding name="value" expression="password"/>
</component>
</page-specification>
Page-specification元素的class属性和两个property-specification元素与Pig Latin翻译器应用是相似的。
你会发现第一个新东西-bean元素①,bean元素把一个org.apache.tapestry.valid.ValidationDelegate
的实例指定了名称"delegate"。页面HTML模板里的Form控件把它的参数delegate设定为
ognl:beans.delegate,就是指向了这个org.apache.tapestry.valid.ValidationDelegate实例。
Bean元素②把一个org.apache.tapestry.valid.StringValidator的实例指定了名称"
requiredValidator", 以用于验证。这个bean的required属性被设为true表明使用这个bean的域是必须被验证的。这个bean的 clientScriptingEnabled属性被设定为ture,表明使用这个bean的域客户端的JavaScript验证功能是激活的。 RequiredValidator bean被用于验证inputUsername和inputPassword的内容。
控件inputUsername③被控件规范声明为ValidField,ValidField是一种用于Tapestry验证子系统的特殊版本的 TextField控件。Message-binding元素被用于指定inputUsername控件的displayName参数的值,这个值是 用"username"为关键字从login.properties④文件里得到。InputUsername控件的validator参数被设定为 requiredValidator bean,这是我们在页面规范里声明过的⑤。控件的value参数跟页面Java类的username属性绑定在一起⑥。控件inputPassword 的控件规范跟控件inputUsername几乎相似,除了用于取得displayName的关键字和绑定的页面Java类的属性不同。
通过使用ValidField控件和为表单(form)提供一个ValidationDelegate, 我们激活了Login表单的验证功能。除了服务器端的验证,Tapestry也提供了客户端的验证(利用JavaScript)。下面就是当用户提交一个 表单而没有为UserName域提供值时,一个JavaScript错误对话框弹出时的屏幕抓图。
下面就是当用户提交一个表单而没有为Password域提供值时,一个JavaScript错误对话框弹出时的屏幕抓图。
下面就是Login页对应的Java 类。
Login.java
package com.ociweb.tapestry;
import org.apache.tapestry.html.BasePage;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.valid.ValidationConstraint;
import org.apache.tapestry.valid.IValidationDelegate;
public abstract class Login extends BasePage {
public abstract String getUsername();
public abstract void setUsername(String username);
public abstract String getPassword();
public abstract void setPassword(String password);
public void login(IRequestCycle cycle) {
String username = getUsername();
String password = getPassword();
StringBuffer sb = new StringBuffer(username);
String validPassword = sb.reverse().toString();
if (password.equals(validPassword)) {
cycle.activate("Success");①
} else {
String errorMessage = getMessage("invalidpassword");②
IValidationDelegate validationDelegate =
(IValidationDelegate) getBeans().getBean("delegate");③
validationDelegate.record(errorMessage,
ValidationConstraint.CONSISTENCY);④
}
}
}
跟Pig Latin翻译器应用一样,我们的页面类也是抽象的,它有抽象方法访问在页面规范里定义的属性(properties)。Tapestry会在运行时刻创 建username和password属性。Login方法只是简单的验证一下用户输入的密码值是否刚好是用户名的反向。如果密码通过验证,用户将被引领 导Success page①。
如果密码输入有误,我们用关键字"invalidPassword"通过从 org.apache.tapestry.AbstractComponent里继承来的getMessage()方法从 Login.properties②里查找对应的资源。我们需要把密码错误信息纪录到我们在页面规范中定义的页面validation delegate中去。我们可以利用我们在页面规范中指定的名称,从页面的beans属性中找回validationDelegate③。最后,我们调用 org.apache.tapestry.valid.IvalidationDelegate的record方法把将要显示给用户看的错误信息保存起 来。下面就是当用户输入错误密码的提交后的屏幕抓图。
下面就是Success页的页面模板。Success页的页面模板仅仅包含HTML标识,所以它不需要页面规范和页面
Java类。
Success.html
<html>
<head>
<title>Successful Login</title>
</head>
<body>
<p>
Congratulations! You have successfully logged on.
</p>
</body>
</html>
总结
我希望这篇文章已经向你展示了Tapestry框架在构建Web应用的是多么简单,然而优雅。Tapestry与大多数主流Web应用框架最大不同 在于它让你用基于控件的方式开发,而非以操作为中心的方式开发。如果这篇文章激起了你的兴趣,我建议你把它下载下来利用它你自己的简单的Web应用。通过 感受简单的应用,这是你了解这个框架的优点的唯一途径。如果你想在你的下一个项目里使用Tapestry,我强烈建议你购买
Tapestry In Action 这本书。我拥有这本书,对它我感到很满意。
References
1 Zip file with all source code and war files from the article. (12K)
http://www.ociweb.com/jnb/jnb2004_05.zip
2 Tapestry Home Page http://jakarta.apache.org/tapestry/
3 Tapestry In Action Page http://www.manning.com/lewisship/
4 Tapestry Wiki http://jakarta.apache.org/tapestry/wiki_frame.html
5 OGNL page http://www.ognl.org/
6 Tapestry Component Reference
http://jakarta.apache.org/tapestry/doc/ComponentReference/index.html
7 Tapestry Component Workbench http://www.t-deli.com/app
8 Designing Tapestry Mega-Components
http://www.onjava.com/pub/a/onjava/2001/11/21/tapestry.html
源文档 <http://blog.csdn.net/gmplayer/archive/2004/11/15/182923.aspx>
Jun 04, 2023 05:36:43 PM
You created some decent points there. I looked on the net for any problem and located most individuals goes along with together with your site. 手機回收
=====================
I was recommended this web site by my cousin. I’m not sure whether this post is written by him as nobody else know such detailed about my trouble. You’re amazing! Thanks! 電腦回收價格
======================
Some really good articles on this website , thankyou for contribution. 翻新apple
======================
This is a terrific site, could you be interested in doing an interview about just how you created it? If so e-mail me! 興趣班英文
=======================
This is a terrific site, could you be interested in doing an interview about just how you created it? If so e-mail me! 興趣班英文
Jun 19, 2023 03:39:00 PM
Giving thanks for your write-up. I know that with today’s complicated world, folks have many beliefs and this has made it to be really hard for learners just like me. However, you have made the idea very easy for me to fully grasp and I now have the awareness of the correct thing. Your own continued reputation as among the top experts about this topic may be boosted through words associated with appreciation from subscribers like me. Thanks, once more. 花藝課程
Jun 28, 2023 10:39:28 PM
You have a excellent blog here! do you want to develop invite posts in this little weblog? cologne
==================
Howdy sir, you have a really nice blog layout “ beauty
===================
I’d like to visit your blog more often however recently it appears to be taking endlessly to come up. I go to from work, and our connection there is pretty good. Do you think the problem may very well be in your finish? cosmetics
====================
Your blog is one of a kind, i love the way you organize the topics.:,”-* style
====================
I’m not sure exactly why but this site is loading very slow for me. Is anyone else having this issue or is it a issue on my end? I’ll check back later on and see if the problem still exists. beauty
=====================
I see something genuinely interesting about your web site so I saved to my bookmarks . lotions
=====================
I’d should check with you here. Which is not some thing I usually do! I love reading a post that may get people to feel. Also, many thanks for permitting me to comment! skincare
======================
Howdy I’m thus excited I found your own web page, I really identified you accidentally, as i had been studying about Yahoo for something different, Anyhow I will be right here now and would likely prefer to point out cheers for any amazing publish plus a all-round thrilling blog (I also adore the actual theme/design), I can’t have enough time in order to browse everything in the moment but I have bookmarked that as well as added your own RSS feeds, then when We have moment I am to read far more, Please keep up the fantastic work. beauty
=======================
Hi my friend! I wish to say that this post is amazing, great written and include approximately all significant infos. I would like to see extra posts like this. makeup
=======================
Thanks a lot for sharing this with all folks you actually understand what you’re speaking about! Bookmarked. Please also discuss with my site =). We could have a link alternate arrangement among us! cosmetics
Jul 25, 2024 01:35:12 AM
So lot to occur over your amazing blog. Your blog procures me a fantastic transaction of enjoyable.. Salubrious lot beside the scene. Kissimmee Termite Treatment
Jul 25, 2024 01:35:27 AM
Make the most of mainly premium substances - you will find him or her for: Orlando Bed Bug Treatment
Jul 25, 2024 01:35:38 AM
It's superior, however , check out material at the street address. Oviedo Bed Bug Treatment
Oct 07, 2024 12:56:09 PM
This is helpful, nonetheless it can be crucial so that you can check out the following website: CPA Long Island
Oct 10, 2024 08:33:15 PM
Thanks for writing such a good article, I stumbled onto your blog and read a few post. I like your style of writing... Dr Ayman Attia