perfsonar-dev - perfsonar: r5370 - branches/new-structure-with-base2/ps-mdm-base2/src/main/java/org/perfsonar/base2/service/registration
Subject: perfsonar development work
List archive
perfsonar: r5370 - branches/new-structure-with-base2/ps-mdm-base2/src/main/java/org/perfsonar/base2/service/registration
Chronological Thread
- From:
- To:
- Subject: perfsonar: r5370 - branches/new-structure-with-base2/ps-mdm-base2/src/main/java/org/perfsonar/base2/service/registration
- Date: Thu, 3 Dec 2009 07:38:53 -0500
Author: trzaszcz
Date: 2009-12-03 07:38:53 -0500 (Thu, 03 Dec 2009)
New Revision: 5370
Modified:
branches/new-structure-with-base2/ps-mdm-base2/src/main/java/org/perfsonar/base2/service/registration/LSRegistrationAction.java
Log:
LSRegistrationAction - enhancement - change to abstract class
Modified:
branches/new-structure-with-base2/ps-mdm-base2/src/main/java/org/perfsonar/base2/service/registration/LSRegistrationAction.java
===================================================================
---
branches/new-structure-with-base2/ps-mdm-base2/src/main/java/org/perfsonar/base2/service/registration/LSRegistrationAction.java
2009-12-02 15:22:00 UTC (rev 5369)
+++
branches/new-structure-with-base2/ps-mdm-base2/src/main/java/org/perfsonar/base2/service/registration/LSRegistrationAction.java
2009-12-03 12:38:53 UTC (rev 5370)
@@ -1,7 +1,3 @@
-/**
- * $Id$
- * Project: perfSONAR
- */
package org.perfsonar.base2.service.registration;
import java.io.BufferedReader;
@@ -12,6 +8,7 @@
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.LinkedList;
import java.util.List;
import org.apache.log4j.Logger;
@@ -30,432 +27,406 @@
import org.perfsonar.base2.xml.nmwg.NMWGNamespaceFactory;
import org.perfsonar.base2.xml.nmwg.Subject;
+import edu.emory.mathcs.backport.java.util.Collections;
+
/**
*
- * TODO: to be refactored...
+ * This class provides support for Registration. It consists of methods
that are helpful to work with
+ * receive/process responses.
+ *
+ * LSRegistrationActions bases on configuration file - configuration.xml
where are defined addresses.
+ *
+ * This addresses can be defined in two form :
+ * - single direct address
+ * - address to hint.roots that contains addresses
*
- * need of more complex structure keeping information about LS-es
- * - status of LS (available, unavailable)
- * - failures (increment after each connection failure, reset when
connected)
- * - last result code
- * - key to be sent as keepalive
- * - ...
- *
+ * @author Slawomir Trzaszczka
+ *
*/
-public class LSRegistrationAction extends Action implements SchedulerAction {
+public abstract class LSRegistrationAction extends Action implements
+ SchedulerAction {
+ private boolean initialized = false;
- // ----------------------------------------------------------- Variables
+ private Logger logger = Logger.getLogger(LSRegistrationAction.class);
- /**
- * Default data source class.
- */
- protected static final String DEFAULT_REGISTER_DATA_SOURCE =
"org.perfsonar.base2.service.registration.DumbRegisterDataSource";
+ /**
+ * Message type.
+ */
+ protected static final String REGISTRATION_REQUEST_MESSAGE_TYPE =
"LSRegisterRequest";
- /**
- * Default value of refresh parameter. It is the number of iterations
(sending a request to LS(s)) after when refresh operation
- * is peformed (all LS keys are removed).
- * 0 - no refershing; 1- refershing after just 1 iteration; 2 -
refreshing after 2 iterations, ...
- */
- protected static final int DEFAULT_REFRESH = 100;
+ /**
+ * EventType name which is included in the metadata of
LSRegisterRequest
+ * message.
+ */
+ private static final String DEFAULT_REGISTRATION_EVENT_TYPE =
"http://ogf.org/ns/nmwg/tools/org/perfsonar/service/lookup/registration/service/2.0";
- protected int refreshCounter = 0;
- protected int refresh;
+ /**
+ * Provides metadatata configuration which is sent to LS(s).
+ */
+ protected RegisterDataSource dataSource = null;
- /**
- * The name of extension section in the configuration. The extension
contains the address of remote file having LS addresses.
- */
- protected static final String REGISTER_LS_ADDRESSES_EXTENSION_NAME =
"registerExtension-lsAddresses";
+ protected String registrationEventType =
DEFAULT_REGISTRATION_EVENT_TYPE;
- /**
- * EventType name which is included in the metadata of LSRegisterRequest
message.
- */
- protected static final String DEFAULT_REGISTRATION_EVENT_TYPE =
-
"http://ogf.org/ns/nmwg/tools/org/perfsonar/service/lookup/registration/service/2.0";
+ /**
+ * Set of information about the service taken from the configuration.
+ */
+ protected LookupInformation lookupInformation = null;
- /**
- * Message type.
- */
- protected static final String REGISTRATION_REQUEST_MESSAGE_TYPE =
"LSRegisterRequest";
+ /**
+ * Sends messages to LS(s).
+ */
+ protected LSRegistrator registrator;
- /**
- * Message type.
- */
- protected static final String KEEPALIVE_RESPONSE_MESSAGE_TYPE =
"LSKeepaliveResponse";
+ public LSRegistrationAction() {
+ super();
+ }
- /**
- * Allows logging.
- */
- private static final Logger logger =
Logger.getLogger(LSRegistrationAction.class.getName());
+ /**
+ * main action of this class, in first step initialization is
executed
+ *
+ */
+ public void runAction() {
- /**
- * Provides metadatat configuration which is sent to LS(s).
- */
- protected RegisterDataSource dataSource = null;
+ if (!initialized) {
+ try {
+ init();
+ initialized = true;
+ } catch (PerfSONARException e) {
+ e.printStackTrace();
+ logger.warn("Cannot initialize "
+ +
this.getClass().getCanonicalName());
+ }
+ }
+ execute();
+ refresh();
- protected String registrationEventType = DEFAULT_REGISTRATION_EVENT_TYPE;
-
- /**
- * Set of information about the service taken from the configuration.
- */
- protected LookupInformation lookupInformation = null;
+ }
+
+ /**
+ * initializes this class
+ *
+ * @throws PerfSONARException
+ */
+ protected abstract void init() throws PerfSONARException;
- /**
- * Sends messages to LS(s).
- */
- protected LSRegistrator registrator = null;
+ /**
+ * business logic of this class
+ */
+ protected abstract void execute();
- /**
- * The array of LS addresses.
- */
- protected URL[] lsURLs;
+ /**
+ * Removes all existing keys
+ */
+ protected abstract void refresh();
- /**
- * The array of keys sent by LSs (the order of keys refer to LSs in
lsURLs array).
- */
- protected Element[] lsKeys;
+ /**
+ *
+ * returns RegisterDataSource configured in configuration.xml file
+ *
+ * @return
+ * @throws PerfSONARException
+ */
+ protected RegisterDataSource getRegisterDataSource()
+ throws PerfSONARException {
- protected boolean initiated = false;
+ String className = getOption("registerDataSource").getValue();
+ if (className == null || className.trim().equals("")) {
+ logger
+ .warn("registerDataSource option of
register action is empty");
+ return null;
+ }
+ RegisterDataSource registerDataSource = null;
- // -----------------------------------------------------------
Constructors
+ try {
+ registerDataSource = (RegisterDataSource)
Class.forName(className)
+ .newInstance();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ throwException(e);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ throwException(e);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ throwException(e);
+ }
+ return registerDataSource;
- public LSRegistrationAction() {
- super();
- }
+ }
+ /**
+ * get registerEventType option value. If not present, take default
+ *
+ * @return
+ */
+ protected String getRegistrationEventType() {
+ String evt = getOption("registerEventType").getValue();
+ if (evt != null) {
+ logger
+ .debug("Found [registerEventType]
option value [" + evt
+ + "]");
+ return evt;
+ } else {
+ logger.debug("Taking default [registerEventType]
value ["
+ + DEFAULT_REGISTRATION_EVENT_TYPE +
"]");
+ return DEFAULT_REGISTRATION_EVENT_TYPE;
+ }
+ }
- // ----------------------------------------------------------- Methods
+ /**
+ *
+ * returns LookupInformation configured in configuration.xml file
+ *
+ * @return
+ * @throws PerfSONARException
+ */
+ protected LookupInformation getLookupInformation() throws
PerfSONARException {
+ Configuration configuration =
ConfigurationManager.getInstance()
+ .getConfiguration();
+ LookupInformation lookupInformation = (LookupInformation)
configuration
+
.getService().getEntry(Configuration.LOOKUP_INFORMATION);
+ return lookupInformation;
+ }
- public void runAction() {
+ /**
+ *
+ * throws exception
+ *
+ * @param ex
+ * @throws PerfSONARException
+ */
+ private void throwException(Exception ex) throws PerfSONARException {
+ throw new PerfSONARException("error",
+ "Unable to create an object of : " +
className + ": "
+ + ex.toString());
+ }
- try {
- if (!initiated) {
- init();
- initiated = true;
- }
- execute();
- refresh();
- } catch (Exception ex) {
+ /**
+ * Get list of URLs to Lookup Services to register to.
+ * Addresses returned from this method are shuffled - aim of this
operation is avoiding
+ * registration to first address
+ *
+ * @return
+ * @throws PerfSONARException
+ */
+ protected LinkedList<URL> getLSAddresses() {
- logger.error(
- "Unable to run an action : "
- + getName() + ": "
- + ex.toString());
+ List<URL> addressesList = new ArrayList<URL>();
- }
+ // get values from options lsAddress-1, lsAddress-2,
lsAddress-3, ...
+ // and get LSes lists from URLs
- }
+ for (String optName : options.keySet()) {
+ // handles lsAddress-1, lsAddress-2, lsAddress-3, ...
+ if (optName.startsWith("lsAddress")) {
- protected void init () throws PerfSONARException {
+ try {
+ URL url = new
URL(getOption(optName).getValue());
+ addressesList.add(url);
+ } catch (MalformedURLException ex) {
+ logger.warn("Unable to convert LS
address [" + optName
+ + "] into URL object.
Exception was: "
+ + ex.getMessage());
+ }
- dataSource = getRegisterDataSource();
- registrationEventType = getRegistrationEventType();
- lookupInformation = getLookupInformation();
+ // handles lsList-1, lsList-2, ...
+ } else if (optName.startsWith("lsList")) {
- lsURLs = getLSAddresses();
- lsKeys = new Element[lsURLs.length];
- for (int i = 0; i < lsURLs.length; i++) lsKeys[i] = null;
+ try {
+ String remoteResource =
getOption(optName).getValue();
+ addLSAddressesFromURL(addressesList,
remoteResource);
+ } catch (MalformedURLException ex) {
+ logger.warn("Unable to read LS list
from URL address ["
+ + optName + "] into
URL object. Exception was: "
+ + ex.getMessage());
+ } catch (IOException ex) {
+ logger.warn("Unable to read LS list
from URL address ["
+ + optName + "] into
URL object. Exception was: "
+ + ex.getMessage());
+ }
- registrator = getLSRegistrator();
- refresh = getRefreshParameter();
+ }
+ }
- }
+ if (addressesList.size() == 0) {
+ return null;
+ } else {
+ LinkedList<URL> glsAddresses = new LinkedList<URL>();
+ // shuffle addresses to randomize order of the GLS
+ Collections.shuffle(addressesList);
+ for (URL url : addressesList) {
+ glsAddresses.add(url);
+ }
+ return glsAddresses;
+ }
+ }
+ /**
+ * Populates urls collection with urls/lines downloaded from urlString
+ *
+ * @param urls
+ * @param urlString
+ * @throws MalformedURLException
+ * @throws IOException
+ */
+ private void addLSAddressesFromURL(List<URL> urls, String urlString)
+ throws MalformedURLException, IOException {
- protected void execute() throws PerfSONARException {
+ URL rootHintsUrl = new URL(urlString);
+ URLConnection rhc = rootHintsUrl.openConnection();
+ BufferedReader in = new BufferedReader(new
InputStreamReader(rhc
+ .getInputStream()));
+ String line;
+ while ((line = in.readLine()) != null) {
+ String s = line.trim();
+ if ((s.length() > 0) && // not empty
+ (!s.startsWith("#")) &&
(!s.startsWith("//"))) { // not
+ // commentary
+ // # or
+ // //
- if (lsURLs == null || lsURLs.length == 0) {
- logger.error("No LS addresses");
- return;
- }
+ URL lsUrl = new URL(line);
+ urls.add(lsUrl);
+ }
+ }
+ in.close();
- ServiceMessage serviceRequestMessage = getLSRegisterMessage();
- Message requestMessage = (Message)
serviceRequestMessage.getElement();
+ }
+
+ /**
+ *
+ * returns message that will contains summarized data
+ *
+ * @return
+ * @throws PerfSONARException
+ */
+ protected ServiceMessage getLSRegisterMessage() throws
PerfSONARException {
- ServiceMessage[] serviceResponseMessage = new
ServiceMessage[lsURLs.length];
+ ServiceMessage serviceMessage = new ServiceMessage();
- for (int i = 0; i < lsURLs.length; i++) {
+ Message message = new Message();
+ message.setType(REGISTRATION_REQUEST_MESSAGE_TYPE);
+ serviceMessage.setElement(message);
- if (lsKeys[i] == null) {
- // sending the LSRegisterRequest
- serviceResponseMessage[i] =
registrator.register(requestMessage, lsURLs[i]);
- } else {
- // sending the LSKeepaliveRequest
- serviceResponseMessage[i] = registrator.keepalive(lsKeys[i],
lsURLs[i]);
- }
+ Metadata metadata = new Metadata();
+ metadata.setId("serviceLookupInfo");
+ message.setMetadata(metadata);
- Element responseMessage = serviceResponseMessage[i].getElement();
+ EventType eventType = new EventType();
+ eventType.setEventType(registrationEventType);
+ metadata.setEventType(eventType);
- Element responseMetadata = null;
- Element eventType = null;
+ Subject subject = new Subject("perfsonar");
+ subject.setId("commonParameters");
+ metadata.setSubject(subject);
- if (responseMessage != null) {
- responseMetadata = responseMessage.getFirstChild("metadata");
- if (responseMetadata != null) {
- eventType = responseMetadata.getFirstChild("eventType");
- }
- }
-
- if (eventType != null) {
- if (eventType.getText().trim().indexOf("success") != -1) {
- lsKeys[i] = responseMetadata.getFirstChild("key");
- } else {
- lsKeys[i] = null;
- try {
- // in case of LSKeepaliveRequest failed
LsRegiserRequest should be sent
- if
(responseMessage.getAttribute("type").equals(KEEPALIVE_RESPONSE_MESSAGE_TYPE))
{
- i--;
- continue;
- }
- } catch (Exception ex) {;}
- }
- } else {
- lsKeys[i] = null;
- }
+ Element service = new Element("service", "psservice",
+
NMWGNamespaceFactory.getNamespace("psservice"));
+ service.setId("serviceParameters");
+ subject.addChild(service);
- } //for
+ Collection<String> keys =
lookupInformation.getOptions().keySet();
+ for (String name : keys) {
+ Element element = new Element(name, "psservice",
+
NMWGNamespaceFactory.getNamespace("psservice"));
+
element.setText(lookupInformation.getOption(name).getValue());
+ service.addChild(element);
+ }
- }
+ Data data = null;
+ Message msg = (Message)
dataSource.getRegisterData().getElement();
+ Collection<Metadata> c = msg.getMetadataCollection();
+ if (!c.isEmpty()) {
+ System.out.println("**");
+ for (Metadata m : c) {
+ data = new Data();
+ data.setMetadataIdRef(metadata.getId());
+ data.addChild(m);
+ message.addChild(data);
+ }
+ } else {
+ System.out.println("*");
+ data = new Data();
+ data.setMetadataIdRef(metadata.getId());
+ message.addChild(data);
+ }
+ return serviceMessage;
+ }
+ protected Element getResponseMetadata(ServiceMessage response) {
+ Element responseMessage = response.getElement();
- /**
- * Removes all existing keys
- */
- protected void refresh() {
+ if (responseMessage != null) {
+ Element responseMetadata = responseMessage
+ .getFirstChild("metadata");
+ return responseMetadata;
+ }
- refreshCounter++;
- if (refreshCounter == refresh) {
- refreshCounter = 0;
- for (int i = 0; i < lsURLs.length; i++) lsKeys[i] = null;
- }
+ return null;
+ }
- }
+ protected boolean isResponseSuccess(Element responseMetadata) {
+ Element eventType =
responseMetadata.getFirstChild("eventType");
+ if (eventType != null &&
eventType.getText().trim().contains("success")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ protected Element getKeyFromResponse(ServiceMessage response) {
- protected RegisterDataSource getRegisterDataSource() throws
PerfSONARException {
+ Element responseMetadata = null;
+ responseMetadata = getResponseMetadata(response);
- String className = DEFAULT_REGISTER_DATA_SOURCE;
- try {
- className = getOption("registerDataSource").getValue();
- if (className == null || className.trim().equals(""))
- logger.warn("registerDataSource option of register action is
empty");
- } catch (Exception pex) {
- logger.warn("registerDataSource option of register action is
missing");
- }
+ if (responseMetadata != null) {
+ if (isResponseSuccess(responseMetadata)) {
+ return responseMetadata.getFirstChild("key");
+ }
+ }
- RegisterDataSource registerDataSource = null;
- try {
+ return null;
+ }
+
+ /**
+ *
+ * returns LSRegistrator configured in configuration.xml
+ *
+ * @return
+ * @throws PerfSONARException
+ */
+ protected LSRegistrator getLSRegistrator() throws PerfSONARException {
- registerDataSource = (RegisterDataSource)
Class.forName(className).newInstance();
+ String className = getOption("registrator").getValue();
+ LSRegistrator registrator = null;
- } catch (Exception ex) {
- throw new PerfSONARException(
- "error",
- "Unable to create an object of : "
- + className + ": "
- + ex.toString());
- }
+ try {
+ registrator = (LSRegistrator) Class.forName(className)
+ .newInstance();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ throw new PerfSONARException("error",
+ "Unable to create an object of : " +
className + ": "
+ + e.toString());
- return registerDataSource;
+ } catch (IllegalAccessException e) {
+ throw new PerfSONARException("error",
+ "Unable to create an object of : " +
className + ": "
+ + e.toString());
- }
+ } catch (ClassNotFoundException e) {
+ throw new PerfSONARException("error",
+ "Unable to create an object of : " +
className + ": "
+ + e.toString());
+ }
- /**
- * get registerEventType option value. If not present, take default
- * @return
- */
- private String getRegistrationEventType() {
- try {
- String evt = getOption("registerEventType").getValue();
- if (evt != null) {
- logger.debug("Found [registerEventType] option value
["+evt+"]");
- return evt;
- }
- } catch (Exception ex) { }
- logger.debug("Taking default [registerEventType] value
["+DEFAULT_REGISTRATION_EVENT_TYPE+"]");
- return DEFAULT_REGISTRATION_EVENT_TYPE;
+ return registrator;
- }
-
+ }
- protected LookupInformation getLookupInformation() throws
PerfSONARException {
-
- Configuration configuration =
ConfigurationManager.getInstance().getConfiguration();
- LookupInformation lookupInformation = (LookupInformation)
-
configuration.getService().getEntry(Configuration.LOOKUP_INFORMATION);
- return lookupInformation;
-
- }
-
- /**
- * Get list of URLs to Lookup Services to register to.
- * @return
- * @throws PerfSONARException
- */
- protected URL[] getLSAddresses() throws PerfSONARException {
-
- List<URL> list = new ArrayList<URL>();
-
- //get values from options lsAddress-1, lsAddress-2, lsAddress-3, ...
- //and get LSes lists from URLs
-
- for (String optName : options.keySet()) {
-
- //handles lsAddress-1, lsAddress-2, lsAddress-3, ...
- if (optName.startsWith("lsAddress")) {
-
- try {
- URL url = new URL(getOption(optName).getValue());
- list.add(url);
- } catch (Exception ex) {
- //MalformedURLException or NullPointerException
- logger.warn("Unable to convert LS address [" + optName +
"] into URL object. Exception was: "+ex.getMessage());
- }
-
- //handles lsList-1, lsList-2, ...
- } else if (optName.startsWith("lsList")) {
-
- try {
- String remoteResource = getOption(optName).getValue();
- addLSAddressesFromURL(list, remoteResource);
- } catch (Exception ex) {
- //NullPointerException
- logger.warn("Unable to read LS list from URL address ["
+ optName + "] into URL object. Exception was: "+ex.getMessage());
- }
-
- }
- }
-
- //TODO: is the result code needed?
- if (list.size() == 0)
- throw new PerfSONARException(
- "warning",
- "No LS addresses");
-
- return list.toArray(new URL[list.size()]);
-
- }
-
-
- /**
- * Populates urls collection with urls/lines downloaded from urlString
- * @param urls
- * @param urlString
- * @throws MalformedURLException
- * @throws IOException
- */
- public void addLSAddressesFromURL(Collection<URL> urls, String
urlString) throws MalformedURLException,IOException {
-
- URL rootHintsUrl = new URL(urlString);
- URLConnection rhc = rootHintsUrl.openConnection();
- BufferedReader in = new BufferedReader(
- new InputStreamReader(
- rhc.getInputStream()));
- String line;
- while ((line = in.readLine()) != null) {
- try {
- String s = line.trim();
- if ((s.length() > 0) && //not empty
- (!s.startsWith("#")) && (!s.startsWith("//"))) { //not
commentary # or //
-
- URL lsUrl = new URL(line);
- urls.add(lsUrl); //so, add it!
-
- }
- } catch (RuntimeException rex) { /* just omit this line */ }
- }
- in.close();
-
- }
-
-
- protected LSRegistrator getLSRegistrator() throws PerfSONARException {
-
- String className = getOption("registrator").getValue();
- LSRegistrator registrator = null;
-
- try {
-
- registrator = (LSRegistrator)
Class.forName(className).newInstance();
-
- } catch (Exception ex) {
-
- throw new PerfSONARException(
- "error",
- "Unable to create an object of : "
- + className + ": "
- + ex.toString());
-
- }
-
- return registrator;
-
- }
-
-
- protected int getRefreshParameter() {
-
- int refresh = DEFAULT_REFRESH;
- try {
- String refershStr = getOption("keyRefresh").getValue();
- refresh = Integer.parseInt(refershStr);
- } catch (NumberFormatException ex1) {
- logger.warn("Wrong format of register refresh parameter.");
- } catch (Exception ex2) {
- logger.debug("Default value of refresh parameter is taken.");
- }
- return refresh;
- }
-
-
- protected ServiceMessage getLSRegisterMessage() throws
PerfSONARException {
-
- ServiceMessage serviceMessage = new ServiceMessage();
-
- Message message = new Message();
- message.setType(REGISTRATION_REQUEST_MESSAGE_TYPE);
- serviceMessage.setElement(message);
-
- Metadata metadata = new Metadata();
- metadata.setId("serviceLookupInfo");
- message.setMetadata(metadata);
-
- EventType eventType = new EventType();
- eventType.setEventType(registrationEventType);
- metadata.setEventType(eventType);
-
- Subject subject = new Subject("perfsonar");
- subject.setId("commonParameters");
- metadata.setSubject(subject);
-
- Element service = new Element("service", "psservice",
NMWGNamespaceFactory.getNamespace("psservice"));
- service.setId("serviceParameters");
- subject.addChild(service);
-
- Collection<String> keys = lookupInformation.getOptions().keySet();
- for (String name: keys) {
- Element element = new Element(name, "psservice",
NMWGNamespaceFactory.getNamespace("psservice"));
- element.setText(lookupInformation.getOption(name).getValue());
- service.addChild(element);
- }
-
- Data data = null;
- Message msg = (Message)dataSource.getRegisterData().getElement();
- Collection<Metadata> c = msg.getMetadataCollection();
- for (Metadata m: c) {
- data = new Data();
- data.setMetadataIdRef(metadata.getId());
- data.addChild(m);
- message.addChild(data);
- }
-
- return serviceMessage;
-
- }
-
-
-} //LSRegistrationAction
\ No newline at end of file
+}
\ No newline at end of file
- perfsonar: r5370 - branches/new-structure-with-base2/ps-mdm-base2/src/main/java/org/perfsonar/base2/service/registration, svnlog, 12/03/2009
Archive powered by MHonArc 2.6.16.