-
Notifications
You must be signed in to change notification settings - Fork 48
Experimental/registry and discovery server #407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
s-heppner
merged 93 commits into
eclipse-basyx:develop
from
rwth-iat:experimental/registry_and_discovery_server
Apr 23, 2026
Merged
Changes from 91 commits
Commits
Show all changes
93 commits
Select commit
Hold shift + click to select a range
ce642c1
Füge experimentelle Änderungen für Registry und Discovery hinzu
Ornella33 7204ae2
Remove test.py from repository and add it to .gitignore
Ornella33 25cf282
correct discovery server implementation
Ornella33 b68efaa
remove unused code
Ornella33 b590e1c
add in-memory storage and adapt README
Ornella33 1b676e7
change main.py and disccovery.py
Ornella33 7cff8cf
Extract server-related components into server app
zrgt a6577be
Refactor `_get_aas_class_parsers`
zrgt 11c59bc
fix aas_descriptor construct method
Ornella33 6d4aab1
Refactor `read_aas_json_file_into`
zrgt a34230f
Refactor `default()`
zrgt 9079d82
fix method update_from
Ornella33 bd2a9c7
Merge remote-tracking branch 'rwth-iat/Experimental/server_app' into …
zrgt a366538
Refactor `_create_dict()`
zrgt 72297f4
Remove `jsonization._create_dict` as not used
zrgt bd48dec
Split `http.py` into `repository` and `http_api_helpers`
zrgt 6fd1612
Refactor server_model and move create interfaces folder
zrgt eba1d89
Refactor `result_to_xml` and `message_to_xml`
zrgt 4acab0d
Move all response related to `response.py`
zrgt 567b5f1
Create base classes for WSGI apps
zrgt 3d15b51
Refactor `http_api_helpers.py` and `response.py`
zrgt cb107ed
Reformat code with PyCharm
zrgt 4e1c647
Small fixes
zrgt 95b2d5a
Refactor
zrgt b65c420
Refactor
zrgt b0f79d6
Refactor some methods in registry.py and fix some typos
Ornella33 7c8fbe2
remove xmlization for Registry and Discovery classes
Ornella33 eb44e8a
change according to xmlization removal for registry and discovery cla…
Ornella33 d608409
fix error with ServerAASToJSONEncoder
Ornella33 dde2499
Refactor `response.py`
zrgt df38540
Refactor utils
zrgt 1da157f
Rename `server_model` to `model`
zrgt a96da47
correct typos from renaming server_model to model
Ornella33 115db62
Remove discovery/registry related code
zrgt 65d1918
Merge remote-tracking branch 'rwth-iat/develop' into refactor/server
zrgt 0c36396
Add missing code from PR #362
zrgt 6b3c646
Revert changes in .gitignore
zrgt 0a8546e
Fix copyright
zrgt bfd1411
Refactor `test_http.py` to `test_repository.py`
zrgt 1fd76de
fix copyright
Frosty2500 a783066
fix MyPy errors, some tests
Frosty2500 66f3320
fix bugs, reintroduce Identifiable check
Frosty2500 3226718
Revert "Remove discovery/registry related code"
zrgt 1268f6a
correct json serialisation for AASDescriptor
Ornella33 74b64d2
adapt filter options for get_all_aas_descritors and remove filter for…
Ornella33 b64d589
add service description
Ornella33 e494d3c
Merge remote-tracking branch 'rwth-iat/refactor/server' into refactor…
zrgt 1de92b3
clean code
Ornella33 59748cf
add README and docker deployment for registry
Ornella33 a7efefc
remove files from another branch
Ornella33 d3d4dbb
Stop tracking unnecessary files
Ornella33 673f18d
Update README
Ornella33 a581603
add docker deployment for discovery service
Ornella33 42dd189
Update repository
Ornella33 d37bc01
Ignore test.py
Ornella33 d907b01
Merge remote-tracking branch 'origin/develop' into experimental/regis…
Ornella33 ac8f7f5
Merge branch 'main' into develop
s-heppner 974e112
Merge branch 'eclipse-basyx:develop' into develop
Frosty2500 5c5e1ae
Merge remote-tracking branch 'origin/develop' into experimental/regis…
Ornella33 01767c1
add init
Ornella33 81994f1
Merge develop
Ornella33 11669cf
Resolve import conflict
Ornella33 f8985e1
Merge remote-tracking branch 'origin/develop' into experimental/regis…
Ornella33 12d64bd
remove import errors
Ornella33 70448ae
remove init file
Ornella33 f885bce
correct errors
Ornella33 5389ab4
Add DescriptorStores (#75)
s-heppner d2761de
Delete unnecessary files descriptorStore.py and provider.py
Ornella33 990e03a
Fix DiscoveryStore and remove MongoDB dependency
s-heppner 298b495
Fix Descriptor types for DescriptorStores
s-heppner 1aab8c2
update discovery
Ornella33 1287a43
add gitignore
Ornella33 e691383
Ignore server/storage dicrectory
Ornella33 27d71e8
Update repository.py from develop branch
Ornella33 21f89c4
Update Registry
Ornella33 8da14be
Update gitignore
Ornella33 648efed
Update Registry after test
Ornella33 1e8f82a
Update base.py to be more generic
Ornella33 f9ba59a
Update gitignore
Ornella33 4b6da4b
Fix myPy issues
Ornella33 7556426
Fix MyPy errors related to repository.py
Ornella33 a93ced2
Fix MyPy errors
Ornella33 b3eaf54
Code Style
Ornella33 3970724
Fix copyright error
Ornella33 d1e3996
Fix MyPy error
Ornella33 a39de9f
Fix circular import problem
Ornella33 fa3136d
Fix communication issue between Basyx Web UI and Basyx Python Servers
Ornella33 3b28b7a
ignore missing type hints for wsgicors
Ornella33 1c9ccd5
improve CORS configuration
Ornella33 5bb2b4d
Solve Codestyle problems
Ornella33 cdc1dfd
Fix discovery asset-link lookup
Ornella33 05e6405
Remove unnecessary imports, dependency, and files in gitignore
Ornella33 0892734
Remove blank line
Ornella33 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from .jsonization import * |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,340 @@ | ||
| import logging | ||
| from typing import Callable, Dict, Optional, Set, Type | ||
|
|
||
| from basyx.aas import model | ||
| from basyx.aas.adapter._generic import ASSET_KIND, ASSET_KIND_INVERSE, JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES, PathOrIO | ||
| from basyx.aas.adapter.json import AASToJsonEncoder | ||
| from basyx.aas.adapter.json.json_deserialization import AASFromJsonDecoder, _get_ts, read_aas_json_file_into | ||
|
|
||
| import app.model as server_model | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| JSON_SERVER_AAS_TOP_LEVEL_KEYS_TO_TYPES = JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES + ( | ||
| ("assetAdministrationShellDescriptors", server_model.AssetAdministrationShellDescriptor), | ||
| ("submodelDescriptors", server_model.SubmodelDescriptor), | ||
| ) | ||
|
|
||
|
|
||
| class ServerAASFromJsonDecoder(AASFromJsonDecoder): | ||
| @classmethod | ||
| def _get_aas_class_parsers(cls) -> Dict[str, Callable[[Dict[str, object]], object]]: | ||
| aas_class_parsers = super()._get_aas_class_parsers() | ||
| aas_class_parsers.update( | ||
| { | ||
| "AssetAdministrationShellDescriptor": cls._construct_asset_administration_shell_descriptor, | ||
| "SubmodelDescriptor": cls._construct_submodel_descriptor, | ||
| "AssetLink": cls._construct_asset_link, | ||
| "ProtocolInformation": cls._construct_protocol_information, | ||
| "Endpoint": cls._construct_endpoint, | ||
| } | ||
| ) | ||
| return aas_class_parsers | ||
|
|
||
| # ################################################################################################## | ||
| # Utility Methods used in constructor methods to add general attributes (from abstract base classes) | ||
| # ################################################################################################## | ||
|
|
||
| @classmethod | ||
| def _amend_abstract_attributes(cls, obj: object, dct: Dict[str, object]) -> None: | ||
| super()._amend_abstract_attributes(obj, dct) | ||
|
|
||
| if isinstance(obj, server_model.Descriptor): | ||
| if "description" in dct: | ||
| obj.description = cls._construct_lang_string_set( | ||
| _get_ts(dct, "description", list), model.MultiLanguageTextType | ||
| ) | ||
| if "displayName" in dct: | ||
| obj.display_name = cls._construct_lang_string_set( | ||
| _get_ts(dct, "displayName", list), model.MultiLanguageNameType | ||
| ) | ||
| if "extensions" in dct: | ||
| for extension in _get_ts(dct, "extensions", list): | ||
| obj.extension.add(cls._construct_extension(extension)) | ||
|
|
||
| @classmethod | ||
| def _construct_asset_administration_shell_descriptor( | ||
| cls, dct: Dict[str, object], object_class=server_model.AssetAdministrationShellDescriptor | ||
| ) -> server_model.AssetAdministrationShellDescriptor: | ||
| ret = object_class(id_=_get_ts(dct, "id", str)) | ||
| cls._amend_abstract_attributes(ret, dct) | ||
| if "administration" in dct: | ||
| ret.administration = cls._construct_administrative_information(_get_ts(dct, "administration", dict)) | ||
| if "assetKind" in dct: | ||
| ret.asset_kind = ASSET_KIND_INVERSE[_get_ts(dct, "assetKind", str)] | ||
| if "assetType" in dct: | ||
| ret.asset_type = _get_ts(dct, "assetType", str) | ||
| global_asset_id = None | ||
| if "globalAssetId" in dct: | ||
| ret.global_asset_id = _get_ts(dct, "globalAssetId", str) | ||
| specific_asset_id = set() | ||
| if "specificAssetIds" in dct: | ||
| for desc_data in _get_ts(dct, "specificAssetIds", list): | ||
| specific_asset_id.add(cls._construct_specific_asset_id(desc_data, model.SpecificAssetId)) | ||
| if "endpoints" in dct: | ||
| for endpoint_dct in _get_ts(dct, "endpoints", list): | ||
| if "protocolInformation" in endpoint_dct: | ||
| ret.endpoints.append(cls._construct_endpoint(endpoint_dct, server_model.Endpoint)) | ||
| elif "href" in endpoint_dct: | ||
| protocol_info = server_model.ProtocolInformation( | ||
| href=_get_ts(endpoint_dct["href"], "href", str), | ||
| endpoint_protocol=( | ||
| _get_ts(endpoint_dct["href"], "endpointProtocol", str) | ||
| if "endpointProtocol" in endpoint_dct["href"] | ||
| else None | ||
| ), | ||
| endpoint_protocol_version=( | ||
| _get_ts(endpoint_dct["href"], "endpointProtocolVersion", list) | ||
| if "endpointProtocolVersion" in endpoint_dct["href"] | ||
| else None | ||
| ), | ||
| ) | ||
| ret.endpoints.append( | ||
| server_model.Endpoint( | ||
| protocol_information=protocol_info, interface=_get_ts(endpoint_dct, "interface", str) | ||
| ) | ||
| ) | ||
| if "idShort" in dct: | ||
| ret.id_short = _get_ts(dct, "idShort", str) | ||
| if "submodelDescriptors" in dct: | ||
| for sm_dct in _get_ts(dct, "submodelDescriptors", list): | ||
| ret.submodel_descriptors.append( | ||
| cls._construct_submodel_descriptor(sm_dct, server_model.SubmodelDescriptor) | ||
| ) | ||
| return ret | ||
|
|
||
| @classmethod | ||
| def _construct_protocol_information( | ||
| cls, dct: Dict[str, object], object_class=server_model.ProtocolInformation | ||
| ) -> server_model.ProtocolInformation: | ||
| ret = object_class( | ||
| href=_get_ts(dct, "href", str), | ||
| endpoint_protocol=_get_ts(dct, "endpointProtocol", str) if "endpointProtocol" in dct else None, | ||
| endpoint_protocol_version=( | ||
| _get_ts(dct, "endpointProtocolVersion", list) if "endpointProtocolVersion" in dct else None | ||
| ), | ||
| subprotocol=_get_ts(dct, "subprotocol", str) if "subprotocol" in dct else None, | ||
| subprotocol_body=_get_ts(dct, "subprotocolBody", str) if "subprotocolBody" in dct else None, | ||
| subprotocol_body_encoding=( | ||
| _get_ts(dct, "subprotocolBodyEncoding", str) if "subprotocolBodyEncoding" in dct else None | ||
| ), | ||
| ) | ||
| return ret | ||
|
|
||
| @classmethod | ||
| def _construct_endpoint(cls, dct: Dict[str, object], object_class=server_model.Endpoint) -> server_model.Endpoint: | ||
| ret = object_class( | ||
| protocol_information=cls._construct_protocol_information( | ||
| _get_ts(dct, "protocolInformation", dict), server_model.ProtocolInformation | ||
| ), | ||
| interface=_get_ts(dct, "interface", str), | ||
| ) | ||
| cls._amend_abstract_attributes(ret, dct) | ||
| return ret | ||
|
|
||
| @classmethod | ||
| def _construct_submodel_descriptor( | ||
| cls, dct: Dict[str, object], object_class=server_model.SubmodelDescriptor | ||
| ) -> server_model.SubmodelDescriptor: | ||
| ret = object_class(id_=_get_ts(dct, "id", str), endpoints=[]) | ||
| cls._amend_abstract_attributes(ret, dct) | ||
| for endpoint_dct in _get_ts(dct, "endpoints", list): | ||
| if "protocolInformation" in endpoint_dct: | ||
| ret.endpoints.append(cls._construct_endpoint(endpoint_dct, server_model.Endpoint)) | ||
| elif "href" in endpoint_dct: | ||
| protocol_info = server_model.ProtocolInformation( | ||
| href=_get_ts(endpoint_dct["href"], "href", str), | ||
| endpoint_protocol=( | ||
| _get_ts(endpoint_dct["href"], "endpointProtocol", str) | ||
| if "endpointProtocol" in endpoint_dct["href"] | ||
| else None | ||
| ), | ||
| endpoint_protocol_version=( | ||
| _get_ts(endpoint_dct["href"], "endpointProtocolVersion", list) | ||
| if "endpointProtocolVersion" in endpoint_dct["href"] | ||
| else None | ||
| ), | ||
| ) | ||
| ret.endpoints.append( | ||
| server_model.Endpoint( | ||
| protocol_information=protocol_info, interface=_get_ts(endpoint_dct, "interface", str) | ||
| ) | ||
| ) | ||
| if "administration" in dct: | ||
| ret.administration = cls._construct_administrative_information(_get_ts(dct, "administration", dict)) | ||
| if "idShort" in dct: | ||
| ret.id_short = _get_ts(dct, "idShort", str) | ||
| if "semanticId" in dct: | ||
| ret.semantic_id = cls._construct_reference(_get_ts(dct, "semanticId", dict)) | ||
| if "supplementalSemanticIds" in dct: | ||
| for ref in _get_ts(dct, "supplementalSemanticIds", list): | ||
| ret.supplemental_semantic_id.append(cls._construct_reference(ref)) | ||
| return ret | ||
|
|
||
| @classmethod | ||
| def _construct_asset_link( | ||
| cls, dct: Dict[str, object], object_class=server_model.AssetLink | ||
| ) -> server_model.AssetLink: | ||
| ret = object_class(name=_get_ts(dct, "name", str), value=_get_ts(dct, "value", str)) | ||
| return ret | ||
|
|
||
|
|
||
| class ServerStrictAASFromJsonDecoder(ServerAASFromJsonDecoder): | ||
| """ | ||
| A strict version of the AASFromJsonDecoder class for deserializing Asset Administration Shell data from the | ||
| official JSON format | ||
|
|
||
| This version has set ``failsafe = False``, which will lead to Exceptions raised for every missing attribute or wrong | ||
| object type. | ||
| """ | ||
|
|
||
| failsafe = False | ||
|
|
||
|
|
||
| class ServerStrippedAASFromJsonDecoder(ServerAASFromJsonDecoder): | ||
| """ | ||
| Decoder for stripped JSON objects. Used in the HTTP adapter. | ||
| """ | ||
|
|
||
| stripped = True | ||
|
|
||
|
|
||
| class ServerStrictStrippedAASFromJsonDecoder(ServerStrictAASFromJsonDecoder, ServerStrippedAASFromJsonDecoder): | ||
| """ | ||
| Non-failsafe decoder for stripped JSON objects. | ||
| """ | ||
|
|
||
| pass | ||
|
|
||
|
|
||
| def read_server_aas_json_file_into( | ||
| object_store: model.AbstractObjectStore, | ||
| file: PathOrIO, | ||
| replace_existing: bool = False, | ||
| ignore_existing: bool = False, | ||
| failsafe: bool = True, | ||
| stripped: bool = False, | ||
| decoder: Optional[Type[AASFromJsonDecoder]] = None, | ||
| ) -> Set[model.Identifier]: | ||
| return read_aas_json_file_into( | ||
| object_store=object_store, | ||
| file=file, | ||
| replace_existing=replace_existing, | ||
| ignore_existing=ignore_existing, | ||
| failsafe=failsafe, | ||
| stripped=stripped, | ||
| decoder=decoder, | ||
| keys_to_types=JSON_SERVER_AAS_TOP_LEVEL_KEYS_TO_TYPES, | ||
| ) | ||
|
|
||
|
|
||
| class ServerAASToJsonEncoder(AASToJsonEncoder): | ||
|
|
||
| @classmethod | ||
| def _get_aas_class_serializers(cls) -> Dict[Type, Callable]: | ||
| serializers = super()._get_aas_class_serializers() | ||
| serializers.update( | ||
| { | ||
| server_model.AssetAdministrationShellDescriptor: cls._asset_administration_shell_descriptor_to_json, | ||
| server_model.SubmodelDescriptor: cls._submodel_descriptor_to_json, | ||
| server_model.Endpoint: cls._endpoint_to_json, | ||
| server_model.ProtocolInformation: cls._protocol_information_to_json, | ||
| server_model.AssetLink: cls._asset_link_to_json, | ||
| } | ||
| ) | ||
| return serializers | ||
|
|
||
| @classmethod | ||
| def _abstract_classes_to_json(cls, obj: object) -> Dict[str, object]: | ||
| data: Dict[str, object] = super()._abstract_classes_to_json(obj) | ||
| if isinstance(obj, server_model.Descriptor): | ||
| if obj.description: | ||
| data["description"] = obj.description | ||
| if obj.display_name: | ||
| data["displayName"] = obj.display_name | ||
| if obj.extension: | ||
| data["extensions"] = list(obj.extension) | ||
| return data | ||
|
|
||
| @classmethod | ||
| def _asset_administration_shell_descriptor_to_json( | ||
| cls, obj: server_model.AssetAdministrationShellDescriptor | ||
| ) -> Dict[str, object]: | ||
| """ | ||
| serialization of an object from class AssetAdministrationShell to json | ||
|
|
||
| :param obj: object of class AssetAdministrationShell | ||
| :return: dict with the serialized attributes of this object | ||
| """ | ||
| data = cls._abstract_classes_to_json(obj) | ||
| data.update(cls._namespace_to_json(obj)) | ||
| data["id"] = obj.id | ||
| if obj.administration: | ||
| data["administration"] = obj.administration | ||
| if obj.asset_kind: | ||
| data["assetKind"] = ASSET_KIND[obj.asset_kind] | ||
| if obj.asset_type: | ||
| data["assetType"] = obj.asset_type | ||
| if obj.global_asset_id: | ||
| data["globalAssetId"] = obj.global_asset_id | ||
| if obj.specific_asset_id: | ||
| data["specificAssetIds"] = list(obj.specific_asset_id) | ||
| if obj.endpoints: | ||
| data["endpoints"] = list(obj.endpoints) | ||
| if obj.id_short: | ||
| data["idShort"] = obj.id_short | ||
| if obj.submodel_descriptors: | ||
| data["submodelDescriptors"] = list(obj.submodel_descriptors) | ||
| return data | ||
|
|
||
| @classmethod | ||
| def _protocol_information_to_json(cls, obj: server_model.ProtocolInformation) -> Dict[str, object]: | ||
| data = cls._abstract_classes_to_json(obj) | ||
|
|
||
| data["href"] = obj.href | ||
| if obj.endpoint_protocol: | ||
| data["endpointProtocol"] = obj.endpoint_protocol | ||
| if obj.endpoint_protocol_version: | ||
| data["endpointProtocolVersion"] = obj.endpoint_protocol_version | ||
| if obj.subprotocol: | ||
| data["subprotocol"] = obj.subprotocol | ||
| if obj.subprotocol_body: | ||
| data["subprotocolBody"] = obj.subprotocol_body | ||
| if obj.subprotocol_body_encoding: | ||
| data["subprotocolBodyEncoding"] = obj.subprotocol_body_encoding | ||
| return data | ||
|
|
||
| @classmethod | ||
| def _endpoint_to_json(cls, obj: server_model.Endpoint) -> Dict[str, object]: | ||
| data = cls._abstract_classes_to_json(obj) | ||
| data["protocolInformation"] = cls._protocol_information_to_json(obj.protocol_information) | ||
| data["interface"] = obj.interface | ||
| return data | ||
|
|
||
| @classmethod | ||
| def _submodel_descriptor_to_json(cls, obj: server_model.SubmodelDescriptor) -> Dict[str, object]: | ||
| """ | ||
| serialization of an object from class Submodel to json | ||
|
|
||
| :param obj: object of class Submodel | ||
| :return: dict with the serialized attributes of this object | ||
| """ | ||
| data = cls._abstract_classes_to_json(obj) | ||
| data["id"] = obj.id | ||
| data["endpoints"] = [cls._endpoint_to_json(ep) for ep in obj.endpoints] | ||
| if obj.id_short: | ||
| data["idShort"] = obj.id_short | ||
| if obj.administration: | ||
| data["administration"] = obj.administration | ||
| if obj.semantic_id: | ||
| data["semanticId"] = obj.semantic_id | ||
| if obj.supplemental_semantic_id: | ||
| data["supplementalSemanticIds"] = list(obj.supplemental_semantic_id) | ||
| return data | ||
|
|
||
| @classmethod | ||
| def _asset_link_to_json(cls, obj: server_model.AssetLink) -> Dict[str, object]: | ||
| data = cls._abstract_classes_to_json(obj) | ||
| data["name"] = obj.name | ||
| data["value"] = obj.value | ||
| return data |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| from .local_file import * |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.