First Touch

SEARCH Application

Do you remember the small example about querying keywords from earlier? Let’s load the SEARCH application and take a look.

After deploying the SEARCH application, let’s open http://localhost:7241/user/index.html locally. Note that the default port is 7241.

Regarding the ports for the HTTP(S) service, the following startup parameters can be set:

Parameter Meaning Example Additional Notes
org.apache.felix.https.enable Enable HTTPS protocol org.apache.felix.https.enable=true Disabled by default; when enabled, the default port is 7240.
org.apache.felix.http.enable Enable HTTP protocol org.apache.felix.http.enable=false Enabled by default; can be disabled.
org.osgi.service.http.port.secure HTTPS service port org.osgi.service.http.port.secure=9999 Default is 7240; can be set manually.
org.osgi.service.http.port HTTP service port org.osgi.service.http.port=8888 Default is 7241; can be set manually.

Subsequently, we can see the UI of this small SEARCH application.

search demo

The component structure of the SEARCH system is as follows:

graph LR;
    A[userinterface] --> B[formatter]
    A --> C[search]
    A --> D[regex]
    C --> E[documents]
    D --> E            

The calling orders between components is:

  • The userinterface component is the UI interface, which is the operation interface we see in the figure above.
  • The userinterface selects to use search or regex for string retrieval based on the configuration, with different matching modes for the two algorithms.
  • The search and regex components both connect to the documents component, which is responsible for providing the text needed by the former.
  • The search and regex components return the retrieval results to userinterface.
  • userinterface uses formatter to convert the result format and provide it to the user.

Let’s look at the SEARCH system in the application management, where we can see the one-to-one correspondence of components.

search components

We can also look at the configurations, where some are instances (runtime of components) and some are connections (linkers between instances). Components correspond to the component mentioned in the previous chapter, instances correspond to the instance, and connections correspond to the linkers.

search configuration

As seen, besides instances and connections, we can also configure the system environment, such as opening two HTTP ports.

By clicking on the configuration parameter of documents, we can see that the default test.documents.base parameter is set to scores. This is actually a configuration item exposed by the documents component, specifying a file directory. When configured as scores, it designates the scores directory under the runtime directory. We place text files in this directory, and these files will be searched and counted. You can change the directory name and create the directory under the runtime directory.

Note that due to update frequency limitations, these changes will take effect within 5 minutes. At that time, you will see the output of component shutdown and restart in the console.

Changing Output Format

After creating the directory and placing the files, we can try a keyword, for example:

search demo

The result of 1 is output in XML format. As mentioned earlier, the formatter component is responsible for formatting in this application. Let’s modify the configuration of the instance corresponding to this component:

formatter configuration

This instance has a configuration item framework.formatter.mode which is false by default. Let’s change it to true and see the effect.

search demo

The same result is now output in JSON format.

Changing Algorithm Module

Besides changing instance configuration parameters to alter system behavior, we can also change connections at runtime, such as pointing it from search to regex.

search demo

At this point, the user interface changes, and the query results are different. This is due to the change in the system structure, with the system now using the regex algorithm module for queries.

Changing Service Port

We can access https://localhost:9696/user/index.html and https://localhost:9898/user/index.html to use this application because we have enabled two HTTPS ports. The demo system does not provide operations to change port configurations, but we can enable and disable http-9696 and http-9898 to operate it.

When we disable one of these instances, we will find that the corresponding port is no longer accessible.

Instance Scripts

Finally, the demo system also opens up some scripts. Scripts can be attached to instances and connections. Currently, instances have 10 script trigger points corresponding to various states and events of the instance, while connections also have 10 trigger points. Each trigger point can bind an event handling module, which can be a compiled class or jar package, or a script (currently supports Groovy).

The demo system provides only one instance and one connection script, corresponding to the interface call event. The demo system currently does not support uploading class and jar files but allows direct writing of Groovy scripts.

We can write a script for any instance, such as:

package eight.service;
import net.yeeyaa.eight.ITriProcessor;
import net.yeeyaa.eight.IProcessor;

class Proxy implements ITriProcessor {
    IProcessor context
	
	def operate(Object first, Object second, Object third) { 
		println "instance[" + context.process("hookid") + "] input: " + first + " " + second + " " + third;
		def ret = ((ITriProcessor)context.process("next")).operate(first, second, third)
		println "instance[" + context.process("hookid") + "] output: " + ret
		ret
	}
}

This script is very simple; it prints the input parameters and output results before and after the service call, similar to the functionality of AOP (Aspect-Oriented Programming). Therefore, when needed, we can configure this script for the corresponding modules to view the input and output of certain components.

In the figure below, we have configured scripts for documents and formatter in the application management configuration to view the input and output of these two parts during each call. Of course, other instances can also be configured.

search script

After configuration, no changes are visible, but once a request occurs, we can see the input and output printed in the console or log (if the output is redirected to a log file).

search script output

From the output, we can see not only the parameters accepted and the output provided by documents and formatter respectively but also intuitively understand the call sequence of various parts of the system.

In principle, this script can do anything during the proxy interface access, even replacing the original module functionality. However, generally speaking, this point of action is used for logging, tracking, debugging, and triggering associated operations, and also for providing stub modules during system development.

Connection Scripts

Scripts for connections (linkers) have extraordinary significance. For a linker, the interfaces it proxies are not fixed, depending on the usage of instances at both ends of the linker. It appears more flexible and harder to grasp. But this is precisely the most important role of the linker.

Remember the changes in the connections between substances mentioned in the previous chapter? If a substance is to maintain its own stable core, the connection needs to be established at runtime. However, the connections between different substances are variable, and the connection should smooth out the incompatibilities between substances for them to work together.

Recall the previous example about search. If the components change and an additional parameter dir is required to specify the directory to be queried, what should be done? From the previous analysis, we concluded that this dir parameter cannot be passed from the userinterface nor provided by search. This parameter exists within their connection.

Here, we finally have a suitable place to accommodate this change.

Suppose our search component is developed by another team, and they designed the query directory parameter not as a configuration parameter as we introduced above but required the upstream component to provide it. Then, we can attach the following script in the linker:

package eight.service;
import net.yeeyaa.eight.IBiProcessor;
import net.yeeyaa.eight.IProcessor;

class Proxy implements IProcessor {
    IProcessor context
	
	def process(Object keyword) { 
		def dir = "searchPath"
		def ret = ((IBiProcessor)context.process("next")).perfrom(keyword, dir)
		ret
	}
}

A very intuitive transformation connects two incompatible yet interrelated components. These two substances are incompatible simply because the latter requires the query directory parameter by default during design, so it uses a binary process interface. The linker proxies as a unary process, only inputting the keyword. During the runtime connection, our real-time script provides the dir parameter value searchPath to call downwards.

This small example is of extraordinary significance. It means we can integrate countless independently developed, mutually unaware components into a unified system, and it also means that a large-scale distributed development component repository becomes possible.

In the next section, let’s look at a relatively complex system.