このトピックでは、コントロールに関するアップグレードでの変更の詳細について説明します。
コントロールに関連するその他のアップグレードの問題については、次のトピックを参照してください。
ページ フローで使用されるコールバック対応コントロールの置き換え
バージョン 8.1 からアップグレードされるアプリケーションに対して行われる変更の詳細なリストについては、「WebLogic Workshop 8.1 からバージョン 10.x へのアップグレード時における変更点」を参照してください。
バージョン 8.1 ではコントロールはエンタープライズ JavaBean のコンテキスト内で実行されるため、それらはトランザクションのスコープ内で自動的に実行されます。バージョン 10.x では、コントロールは POJO (Plain Old Java Object) になります。そのため、トランザクションをサポートするには、コントロール インタフェースにトランザクション サポート用の @TransactionAttribute
アノテーションを付ける必要があります。
アップグレード時に、アップグレード ツールによってトランザクション サポート用のアノテーションが追加されます。以下のバージョン 8.1 とバージョン 10.x の例は、アップグレード前後の簡単なコントロール インタフェースを示しています。
バージョン 8.1 では、アノテーションは不要でした。
package localControls.nestedControls; import com.bea.control.Control; /** * VerifyFunds コントロールのパブリック インタフェース。 * このコントロールは VerifyFundsImpl.jcs に実装される */ public interface VerifyFunds extends Control { interface Callback { void onTransactionComplete(String message, boolean isBalanceAvailable, boolean isInventoryAvailable); } /** * @common:operation */ void submitPO(java.lang.String poNumberString, java.lang.String customerIDString, int itemNumber, int quantityRequested, double startingBalance); }
バージョン 10.x では、@TransactionAttribute
アノテーションによってトランザクションのサポートが要求されていることが示されます。
package localControls.nestedControls; import com.bea.control.annotations.TransactionAttribute; import com.bea.control.annotations.TransactionAttributeType; import org.apache.beehive.controls.api.bean.ControlInterface; import org.apache.beehive.controls.api.events.EventSet; /** * VerifyFunds コントロールのパブリック インタフェース */ @ControlInterface() @TransactionAttribute(TransactionAttributeType.REQUIRED) public interface VerifyFunds { @EventSet(unicast = true) interface Callback { void onTransactionComplete(String message, boolean isBalanceAvailable, boolean isInventoryAvailable); } void submitPO(java.lang.String poNumberString, java.lang.String customerIDString, int itemNumber, int quantityRequested, double startingBalance); }
バージョン 10.x では、コントロール ファクトリ (実行時に 1 つのコントロールから複数のコントロール インスタンスを作成できるバージョン 8.1 の機能) はサポートされていません。コードでコントロール ファクトリが使用されている場合は、その機能を代替のソリューションに置き換える必要があります。
明示的に複数のコントロール インスタンスをインスタンス化するコントロール関連の API を使用することで、コントロール ファクトリに似た機能をサポートできます。次の例では、バージョン 8.1 のサンプル アプリケーションに含まれていたコントロール ファクトリのサンプルを簡略化したものが使用されています。
package controlfactory; import java.util.HashMap; import java.util.Map; import java.io.Serializable; import javax.jws.WebMethod; import javax.jws.WebService; import org.apache.beehive.controls.api.bean.ControlReferences; import org.apache.beehive.controls.api.bean.Controls; import weblogic.jws.Conversation; import weblogic.jws.WLHttpTransport; import javax.jws.soap.SOAPBinding; import controlfactory.SlowServiceControlBean; import controlfactory.SlowServiceControl; /** * 次のコードは、コントロールに関連付けられる API を使用することで * バージョン 8.1 のコントロール ファクトリ機能を実現する方法を示している * * この例のコードは、バージョン 8.1 の SamplesApp アプリケーションに * 含まれるコントロール ファクトリのサンプルを簡略化したものである */ @WLHttpTransport(serviceUri = "controlfactory/ServiceFactoryClient.jws") @WebService(serviceName = "ServiceFactoryClient", targetNamespace = "http://workshop.bea.com/ServiceFactoryClient") @javax.jws.soap.SOAPBinding(style = javax.jws.soap.SOAPBinding.Style.DOCUMENT, use = javax.jws.soap.SOAPBinding.Use.LITERAL, parameterStyle = javax.jws.soap.SOAPBinding.ParameterStyle.WRAPPED) @ControlReferences(SlowServiceControl.class) public class ServiceFactoryClient implements java.io.Serializable { static final long serialVersionUID = 3553L; static Map<String, Long> serviceControlsMap = new HashMap<String, Long>(); int m_numServices; /** * このメソッドは、numServices パラメータに入る数に基づいて * 複数のコントロール インスタンスを作成する作業を行う */ @Conversation(Conversation.Phase.START) @SOAPBinding(style = javax.jws.soap.SOAPBinding.Style.DOCUMENT, use = javax.jws.soap.SOAPBinding.Use.LITERAL, parameterStyle = javax.jws.soap.SOAPBinding.ParameterStyle.WRAPPED) @WebMethod() public void startServices(int numServices) { int i; if (numServices > 0) { try { for (i = 0; i < numServices; i++) { m_numServices = numServices; // コントロール ファクトリを使用する代わりに、 // Controls.instantiate メソッドを使用してコントロールの別のインスタンスを // インスタンス化する SlowServiceControlBean bean; bean = (SlowServiceControlBean) Controls.instantiate(Thread .currentThread().getContextClassLoader(), "controlfactory.SlowServiceControlBean", null); // さらに、新しいコントロール インスタンスごとに // コールバック ハンドラのインスタンスをペアにする。This handler will listen // サービスの infoReady コールバックをリスンする SlowServiceCallbackHandler handler = new SlowServiceCallbackHandler( bean); bean.addCallbackListener(handler); // サービス コントロールを呼び出し、後で識別するためのラベルとなる // ユニークな値を渡す String serviceName = "Service" + i; bean.requestInfo(serviceName); // インスタンス名のインデックスが付いた、各コントロールの起動時刻を // 非表示にする serviceControlsMap.put(serviceName, new Long( (new java.util.Date().getTime()))); } } catch (Exception e) { e.printStackTrace(); } } } /** * コントロールの infoReady コールバック用のコールバック ハンドラ */ public class SlowServiceCallbackHandler implements SlowServiceControl.Callback, Serializable { static final long serialVersionUID = 1L; private SlowServiceControlBean serviceControl; public SlowServiceCallbackHandler(SlowServiceControlBean control) { serviceControl = control; } public void infoReady(String name) { // このコントロールが起動してからの秒数を計算する long timeTaken = (new java.util.Date().getTime() - ((Long) serviceControlsMap .get(name)).longValue()) / 1000; // ControlBean インスタンスでは、インスタンスに関する情報の取得に // 便利な多くのメソッドが提供される String controlId = serviceControl.getControlID(); // 検出された情報を出力する printControlInfo(name, controlId, timeTaken); } public void onAsyncFailure(String arg0, Object[] arg1) { // TODO 自動生成されるメソッド スタブ } } @WebMethod @Conversation(Conversation.Phase.FINISH) public String finishServices() { return "Finished. " + m_numServices + " services invoked."; } public void printControlInfo(String serviceName, String controlId, long time) { System.out.println("Control callback received from " + serviceName + ":" + controlId + " after " + time + " seconds."); } }
この例には以下の API が含まれています。
org.apache.beehive.controls.api.bean.ControlReferences
org.apache.beehive.controls.api.bean.Controls
発生する状況は限られているが、サービス コントロール用の Xbean 型が生成される際に、WSDL からのラッパー型がサービス コントロール内の型として公開されてしまうことがある (たとえば、1 つのオペレーション シグネチャ内に同じ Document 型が複数回出現する場合など)。生成される型の JAR がターゲット JWS のクラスローダに対して可視になっていると、デプロイ時に型の重複エラーが送出される。
この場合、ラッパー型を使用するサービス コントロールを、コントロールが呼び出すサービスとは別のプロジェクトに配置します。SerivceControl クラスがユーティリティ プロジェクトからロードされる場合、そのサービス コントロールとターゲットのサービスはそれぞれ別のアプリケーション内に存在する必要がある。
バージョン 8.1 ではサービス コントロールを WSDL に関連付けないようにできましたが、正常にアップグレードするためにはサービス コントロールを WSDL に関連付ける必要があります。
アプリケーションをアップグレードする前に WebLogic Workshop 8.1 でサービス コントロールと WSDL とを関連付けることができます。最も簡単な方法は、WSDL からサービス コントロールを再生成することです。[アプリケーション] タブで WSDL を右クリックして、[サービス コントロールの生成] をクリックします。
サービス コントロール内に貼り付けることで、手動で WSDL を関連付けることもできます。ソース ビューでサービス コントロールを開いてファイルの最後までスクロールし、他のすべてのコードの後に @common:define アノテーションの値として WSDL の内容を貼り付けます。次の例では、value 属性の値が二重コロンで囲まれており、その後に Javadoc コメントが続きます。
/** @common:define name="MyServiceWsdl" value:: ... WSDL の内容 ... * :: */
WSDL を追加したら、コントロールの宣言に @jc:wsdl アノテーションを次のように追加します。
/** * (他のアノテーション) * @jc:wsdl file="#MyServiceWsdl" */ public interface MyServiceControl extends ControlExtension, ServiceControl
file 属性の値は @common:define の name 属性と同じですが、# 記号が先頭に追加されています。
バージョン 8.1 では抽象 WSDL (サービス定義のない WSDL) からサービス コントロールを生成できました。その後、呼び出しやリスンを行うためのエンドポイントをプログラム的にまたはアノテーションを使用してコンフィグレーションできました。しかし、WebLogic Server バージョン 10.x では抽象 WSDL はサポートされていません。そのため、Workshop のアップグレード ツールでは必要なアノテーションとともにサービス コントロールをアップグレードできず、アップグレードされたコードにコンパイル エラーが残されます。同様に、9.x で抽象 WSDL からサービス コントロールを生成しようとすると、サービスが必須であることを示すエラーが表示されます。
これを回避する方法の 1 つは、アップグレード ツールを使用する前に WSDL に <service> 定義を 1 つ追加することです。エンドポイントはアノテーションから取得されるため、追加されたエントリはアップグレードされたコードでは使用されません。
ServiceControl.getEndPoint()
メソッドおよび ServiceControl.setEndPoint(URL)
メソッドはバージョン 10.x では非推奨となっており、将来のバージョンで削除される可能性があります。 新しいコードでこの種類の API が必要になる場合は、それぞれ ServiceControl.getEndpointAddress()
および ServiceControl.setEndpointAddress(String)
を使用する必要があります。
これらのメソッドの最も一般的な使い方をサポートするのに、バージョン 8.1 のメソッドで使用されていた URL
インスタンスは必須ではありません。String
インスタンスで十分です。get* メソッドはデバッグに便利です。エンドポイントの場所の取得やログへの記録を行う方法を提供します。set* メソッドは、送り先が別のサーバやクラスタにある場合などに、実行時にエンドポイントの場所を動的にコンフィグレーションするのに便利です。
Web サービスのセキュリティ モデルが変更されたことにより、バージョン 10.x のサービス コントロールを使用したセキュリティ特性の指定方法がバージョン 8.1 とは異なっています。バージョン 8.1 では、WebLogic Workshop WSSE ポリシー ファイル内にセキュリティ ポリシーと値の両方を指定しました。バージョン 10.x では、これら 2 つのセキュリティ要素の特性を複数の場所に分けて指定するようになっています。
バージョン 10.x のサービス コントロールにおけるセキュリティ要素の指定モデルを簡単に説明すると、以下のようになります。
サービス コントロールをアップグレードする際、@jc:location アノテーションで指定された JMS URL はアップグレード ツールで正しくアップグレードされません。手動で URL を変更する必要があります。バージョン 8.1 で指定されていた URL の例と、それに対応するアップグレード後の URL を以下に示します。
バージョン 8.1 :
@jc:location jms-url="jms://localhost:7001/weblogic.jws.jms.QueueConnectionFactory/jws.queue?URI=/services/MyService.jws&java.naming.factory.initial=com.myco.jndi.factory"
バージョン 10.x
@ServiceControl.Location(urls = {
"jms://localhost:7001/services/MyService.jws&java.naming.factory.initial=com.myco.jndi.factory?URI=jws.queue&FACTORY=weblogic.jws.jms.QueueConnectionFactory"
})
URL を完全にアップグレードするには、次のような変更を行います。
変更を加えた後の JMS URL は、以下に示す例のような形式になります (接続ファクトリを指定する場合)。
@ServiceControl.Location(urls = {
"jms://localhost:7001/services/MyService.jws&java.naming.factory.initial=com.myco.jndi.factory?URI=jws.queue&FACTORY=weblogic.jws.jms.QueueConnectionFactory"
})
.jcx ファイルをアップグレードするときに、アップグレード プロセスが一部の MBCS 文字をアップグレード後のファイルにコピーできない場合がある。この状況は、UTF-8 エンコーディングの WSDL が他の種類の MBCS エンコーディング (MS932 (Japanese) など) のファイルに含まれていて、MBCS 文字がそのファイルで WSDL 定義の外側にある場合に発生する。
この場合、生成されるファイルには元の文字の代わりに「????」が含まれて、コンパイルされなくなる。
この場合、以下の修正のいずれかを実行します。
バージョン 8.1 の EJB コントロールからエクスポーズされる getJNDIName メソッドおよび setJNDIName メソッドについてはバージョン間の相違があるため、アップグレードされたコードでエラーが発生します。バージョン 8.1 ではこれらのメソッドは EJBControl.getJNDIName および EJBControl.setJNDIName としてエクスポーズされましたが、バージョン 10.x ではビルド時に生成されるコントロール Bean クラスから getJndiName および setJndiName としてエクスポーズされます (大文字小文字の違いにも注意)。
メソッド呼び出しを置き換えることで、アップグレードされたコードを修正する必要があります。たとえば、バージョン 8.1 で次のようにコントロールのインスタンスからこれらのメソッドを呼び出していたとします。
import mypackage.MyEJBControl; ... private MyEJBControl ejbControl; public String getEJBJNDIName() { String jndiName = ejbControl.getJNDIName(); }
バージョン 10.x では、EJB コントロールのコントロール Bean クラスのインスタンスからメソッドを呼び出します。 (プロジェクトが正常にビルドされるまではコントロール Bean は生成されません)。
import mypackage.MyEJBControlBean; ... private MyEJBControlBean ejbControlBean; public String getEJBJNDIName() { String jndiName = ejbControlBean.getJndiName() }
EJB コントロールは他のタイプのコントロールとは仕組みが異なっているために、アップグレード ツールでは EJB コントロールで使用されるセキュリティ アノテーションを自動的にアップグレードできません。この相違に対処するには、必要なメソッドとアノテーションを含むように EJB コントロール拡張ファイルを手動で編集する必要があります。
コントロールのセキュリティ アノテーションは、クラス レベルではサポートされなくなりました。クラス レベルのセキュリティ アノテーションがあるコントロールでは、アップグレード時に、コントロール内で定義されている各メソッドにセキュリティ アノテーションがコピーされます。しかし EJB コントロールの場合、コントロール拡張 (バージョン 8.1 の JCX ファイル) で直接定義されているメソッドはありません。代わりにメソッドは EJB ホーム インタフェース定義およびインタフェース定義から継承されます。セキュリティ アノテーションが省略されてしまうことによってセキュリティ ギャップが発生するため、アップグレード ツールではセキュリティ アノテーションのあるすべての EJB コントロールに対してエラーが生成されます。
この相違に対処するには、EJB のメソッドをコントロール拡張ファイルに手動でコピーし、各メソッドに com.bea.control.annotations.Security アノテーションを追加します。EJB 自体に対する変更 (新しいメソッドの追加など) が行われるたびに、以前クラス レベルで定義されていたセキュリティ制約が引き続き有効になるようにコントロール拡張を更新しなければならない可能性があることに注意してください。
次のテンプレートスタイルの例では、行うべき典型的な変更を示します。
/** * @common:security [ここに属性] */ public class MyEjb extends EJBControl, MyHome, MyLocal [other types] { // なし (メソッドはホーム インタフェース定義およびインタフェース定義から継承されていた) }
アップグレード後、対応するバージョン 10.x のアノテーションとともにメソッドが宣言されるように、アップグレードされたコントロール拡張を編集する必要があります。
public class MyEjb extends EJBControl, MyHome, MyLocal [その他] { @Security [ここに属性] void methodFromHome() @Security [ここに属性] void methodFromBean() }
バージョン 10.x では JMS コントロールを使用してメッセージを受信できません。バージョン 8.1 の @jc:jms
アノテーションにメッセージの受信動作を指定する属性が含まれている場合には、アップグレード時にこれらの属性は無視されます。たとえば、次に示すバージョン 8.1 のアノテーションは、その後にあるバージョン 10.x のアノテーションに移行されます。
バージョン 8.1 :
@jc:jms receive-type="topic" receive-jndi-name="jms.AccountUpdate" connection-factory-jndi-name="weblogic.jws.jms.QueueConnectionFactory"
バージョン 10.x へのアップグレード
@JMSControl.Destination(jndiConnectionFactory = "weblogic.jws.jms.QueueConnectionFactory")
以下に、アップグレードされたコードでの対応策を 2 つ示します。
受信側アプリケーションを更新できる場合は、受信側アプリケーションをコールバックのある JWS に置き換えることによって、JMS コントロールをサービス コントロールに置き換えることができます。システム間の API に JMS プロパティまたはユーザ プロパティの使用が含まれていたときは、これらのプロパティをメッセージの定義に追加するか、またはカスタム SOAP ヘッダとして追加する必要があります。
受信側アプリケーションを更新できない場合は、代わりの方法として、呼び出し側アプリケーションにタイマー コントロールを追加し、応答メッセージのポーリングをトリガするタイマー イベントを使用します。応答メッセージが存在すると、JMS メッセージ (および関連プロパティ) は必要に応じてイベント メソッドのシグネチャと一致するように処理されます。この機能は、呼び出し側アプリケーションへの影響を最小限に抑えるためにカスタム コントロールにカプセル化できます。
バージョン 8.1 の JMS コントロールは sendJMSMessage メソッドのパラメータとして JMSControl.Message 型を取りましたが、バージョン 10.x ではこのメソッドは javax.jms.Message のインスタンスを取ります。完全にアップグレードするには、それに従ってコードを変更する必要があります。
@org.apache.beehive.controls.system.jms.JMSControl.Message アノテーションを使用するコードのアップグレードでは、「アノテーションの型に関連するあいまいさの解決」で説明しているような型のあいまいさの問題が発生することがあります。完全修飾された型名に関する問題の解決については、その節を参照してください。
アップグレード ツールでは、コントロール メソッド パラメータの JMS プロパティへのバインディングに対する JMS コントロール サポートは完全にはアップグレードされません。具体的に言うと、メソッド パラメータ自体にアノテーションを付ける必要がありますが、アノテーションは付けられません。バージョン 8.1 のコードのアップグレード後に必要なアノテーションを手動で追加することによって、これらのバインディングを確実にサポートできます。
次の例では、accountID パラメータに、そのパラメータにバインドされる JMS プロパティを示す @JMSControl.Property アノテーションが付けられています。
@JMSControl.Properties({ @JMSControl.PropertyValue(name = "transactionType", value = "DEPOSIT") }) public void deposit( AccountTransaction transaction, @JMSControl.Property(name="accountIdentifier") String accountID);
バージョン 8.1 のデータベース コントロールに対応している、バージョン 10.x の標準的な JdbcControl (org.apache.beehive.controls.JdbcControl) では、バージョン 8.1 の「RowSet コントロール」機能はサポートされていません。 アップグレード時に行セット機能が確実に保持されるようにするため、データベース コントロールは BEA から提供されている下位互換性のある JdbcControl (com.bea.control.JdbcControl) にアップグレードされます。
アップグレードされたアプリケーションで行セット機能が使用されていない場合には、アップグレード後に下位互換性のあるコントロールからコントロールに更新することをお勧めします。これを行うには、コントロールを編集して org.apache.beehive.controls.system.jdbc.JdbcControl を拡張します (コントロールで利用できないバージョン 8.1 のどの機能も使用されていない場合)。アップグレードされたアプリケーションで行セット機能が使用されている場合には、この機能が JbcControl.SQLRowSet アノテーションの rowsetSchema 属性によって表されていることに注意してください。
アップグレードした 8.1 データベース コントロールを 10.x 以降のアプリケーションに追加すると、アプリケーションで求められるコントロールの機能に応じて、次のいずれかを実行できます。
新しい JdbcControl コントロールを挿入すると、Beehive ベースのコントロールになります。
バージョン 8.1 のデータベース コントロールでは、@jc:sql array-max-length 属性の値として「all」を指定することでクエリに対するすべての行の取得がサポートされていました。バージョン 10.x の JDBC コントロールでは、この値はサポートされていません。代わりに数値を指定します。アップグレード ツールでは、この変更は自動的には行われません。
たとえば、次のような変更を行います。
バージョン 8.1 : @jc:sql array-max-length="all" statement="SELECT * FROM Customers"
バージョン 10 : @JdbcControl.SQL(arrayMaxLength = 1024, statement = "SELECT * FROM Customers")
バージョン 8.1 ではタイマー コントロールの start メソッドを複数回呼び出してもエラーなしでタイマーを開始することができましたが、onTimeout コールバックは start メソッドの個々の呼び出しに必ずしも対応していませんでした。バージョン 10.x のタイマー コントロールでは、start メソッドの複数回呼び出しを許可しないことで API が簡素化されています。タイマー コントロールがまだ実行中である場合に start メソッドを呼び出しても効果はありません。
バージョン 8.1 では、コントロール イベント ハンドラは任意の型の例外を送出できました (コントロール イベント ハンドラはバージョン 8.1 では「コールバック ハンドラ」と呼ばれていました)。バージョン 10.x では、捕捉された例外をイベント ハンドラが送出する場合には、コントロールの EventSet メソッドに対して宣言された適切な throws 句のサブセットを代わりに送出する必要があります。
アプリケーションのアップグレード後にイベント ハンドラの例外処理を修正することで、バージョン 10.x の要件に対処できます。以下に 2 つの対応策を示します。
バージョン 8.1 のカスタム コントロールのアノテーション定義は、バージョン 10.x にアップグレードされません。バージョン 10.x ではアノテーションを定義するための方法は Java 5 アノテーション モデルに基づきます。バージョン 8.1 向けに記述されたコントロールをアップグレードするには、アノテーション定義を新しいモデルに合わせて書き換える必要があります。
カスタム アノテーションのアップグレードの詳細については、システム コントロール用の Apache Beehive ソース コードを確認してください。新しいモデルを使用するアノテーションが示されています。
バージョン 8.1 からのコントロール コンテキスト API の変更については、「コンテキスト API の変更に対する処理」を参照してください。
バージョン 8.1 では、カスタム コントロールのインタフェース コードまたは実装コードに common:message-buffer タグを適用できました。バージョン 10.x では、このアノテーションに対応するアノテーション com.bea.control.annotations.MessageBuffer はコントロールのインタフェース コードでしかサポートされていません。この変更に対応するには、アプリケーションをアップグレードする前に、該当するアノテーションを実装コードから削除する必要があります。
バージョン 8.1 では、Web サービス (バージョン 8.1 の JWS ファイル) やカスタム コントロールなどのコンポーネントが実行時環境と対話するためのコンテキスト API が提供されていました。以下では、バージョン 10.x に移行された (または移行されなかった) コンテキスト API のサポートの概要について説明します。
com.bea.control.JwsContext インタフェースの役割の大部分は、現在では weblogic.jws.Context アノテーションとともに使用される weblogic.wsee.jws.JwsContext に属するようになりました (それぞれについては BEA e-docs ページの説明を参照してください)。
しかし、バージョン 8.1 の API の一部は非推奨になり、バージョン 10.x では使用できなくなりました。例としては、getCallbackLocation、getCallbackPassword といった、com.bea.control.JwsContext のコールバック指向のメソッドなどがあります。これらのメソッドに対応する、バージョン 10.x で使用できるメソッドについては、「weblogic.wsee.jws.CallbackInterface」を参照してください。たとえば、getCallbackLocation メソッドは CallbackInterface.getEndpointAddress メソッドに置き換えることができます。
バージョン 8.1 のコントロール コンテキスト クラスによってエクスポーズされていたいくつかの API について、エクスポーズの方法が変更されたり、関連がなくなり使用できなくなったり、というような変更がバージョン 10.x のコントロール モデルによってもたらされました。現在のモデルではコンテナの環境と対話するためのコントロールの役割が簡素化されているため、多くの場合で対応策がありません。具体的には、会話関連のメソッドがコントロール内でサポートされなくなりました。
次の表に、バージョン 8.1 のコントロール コンテキスト関連のクラスおよびメソッドと、それに対応するバージョン 10.x のクラスおよびメソッド (存在する場合) を示します。
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||