아래의 표는 pom.xml에 기술되어 빌드에 포함되는 연관 패키지입니다. 이 라이브러리 들은 설치후 WEB-INF/lib폴더에 저장합니다.
groupId | artifactId | version | scope | |
1 | axis | axis | 1.2.1 | compile |
2 | br.org.scadabr | dnp34j | 1.12.4 | |
3 | br.org.scadabr.protocol | iec101 | 1.12.4 | |
4 | com.atlassian | crowd-integration-client | 2.1.1 | |
5 | com.dalsemi | onewire | 1.10 | compile |
6 | com.i2msolucoes | alpha24j | 1.12.4 | |
7 | com.serotonin | spinwave | 1.12.0 | compile |
8 | com.serotonin | serotonin-util | 1.12.4 | compile |
9 | com.serotonin | modbus4J | 1.12.0 | compile |
10 | com.serotonin | backnet4j | 1.12.0 | |
11 | com.serotonin | viconics | 1.12.0 | |
12 | commons-codec | commons-codec | 1.4 | compile |
13 | commons-dbcp | commons-dbcp | 1.4 | |
14 | commons-fileupload | commons-fileupload | 1.2.2 | compile |
15 | commons-httpclient | commons-httpclient | 3.1 | compile |
16 | commons-io | commons-io | 1.4 | compile |
17 | commons-logging | commons-logging | 1.1.1 | |
18 | commons-pool | commons-pool | 1.5.5 | |
19 | javax.activation | activation | 1.1.1 | compile |
20 | javax.mail | 1.4.1 | compile | |
21 | javax.servlet | servlet-api | 2.5 | provided |
22 | javax.servlet | jstl | 1.2 | compile |
23 | javax.servlet.jsp | jsp-api | 2.1 | provided |
24 | javax.xml | jaxrpc-api | 1.1 | |
25 | jfree | jcommon | 1.0.15 | compile |
26 | jfree | jfreechart | 1.0.13 | compile |
27 | joda-time | joda-time | 1.6.2 | compile |
28 | mysql | mysql-connector-java | 5.1.13 | |
29 | net.sf.fhz4j | fhz4j-core | 0.1.4-SNAPSHOT | compile |
30 | net.sf.mbus4j | mbus4j-master | 0.1.4-SNAPSHOT | compile |
31 | net.sf.openv4j | openv4j-core | 0.1.4-SNAPSHOT | compile |
32 | org.apache.derby | derby | 10.6.1.0 | compile |
33 | org.apache.derby | derbytools | 10.6.1.0 | compile |
34 | org.directwebremoting | dwr | 2.0.3 | |
35 | org.freemarker | freemarker | 2.3.16 | compile |
36 | org.openscada | opc-driver | 0.5 | |
37 | org.openscada | openscada-utils | 0.5 | |
38 | org.openscada | opc-dcom | 0.5 | |
39 | org.openscada | opc-lib | 0.5 | |
40 | org.quartz-scheduler | quartz | 1.7.2 | |
41 | org.rxtx | rxtx | 2.2 | |
42 | org.snmp4j | snmp4j | 1.10.1 | compile |
43 | org.springframework | spring | 2.5.6 | compile |
44 | org.springframework | spring-webmvc | 2.5.6 | compile |
45 | taglibs | standard | 1.1.2 | compile |
46 | taglibs | log | 1.0 | compile |
연관 패키지를 살펴보면 다른 오픈소스 프로젝트로부터(openscada, mango, rxtx...) 드라이버 코드등을 가져왔음을 확인할 수 있고, 주의 깊게 확인할 것은 스프링 프레임워크를 적용하고 있다는 것입니다.
스프링 프레임워크에 대한 설명은 생략하고 각종 설정이 담긴 XML 파일을 기반으로 시스템의 구조를 파악해 나갑니다.
가장 핵심 역할을 하는 파일은 WEB-INF/web.xml 입니다.
web.xml의 주요 내용을 살펴보면 우선 <context-param>을 이용해서 서버에 전달할 기본 파라미터를 설정합니다.
필터는 클라이언트와 서버 사이에서 스트림의 검사나 보정 작업 등을 수행할 수 있는 것으로 <filter>로 필터를 정의하고 <filter-mapping>으로 특정 URL에 해당하는 필터를 맵핑시킵니다.
<filter><filter-name>IsLoggedIn</filter-name><filter-class>com.serotonin.mango.web.filter.NormalLoggedInFilter</filter-class><init-param><param-name>forwardUrl</param-name><param-value>/login.htm</param-value></init-param></filter><filter-mapping>
<filter-name>IsLoggedIn</filter-name>
<url-pattern>*.shtm</url-pattern>
</filter-mapping>
위의 코드 예제는 IsLoggedIn 필터를 정의하고 URL이 *.shtm으로 끝나는 것은 반드시 로그인 검사를 수행하고 비로그인 상태이면 /login.htm으로 이동시키도록 하는 설정입니다. 아래는 필터 역할을 수행하는 코드입니다.
abstract public class LoggedInFilter implements Filter {
private final Log LOGGER = LogFactory.getLog(LoggedInFilter.class);
private String forwardUrl;
public void init(FilterConfig config) {
forwardUrl = config.getInitParameter("forwardUrl");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
// Assume an http request.
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
boolean loggedIn = true;
User user = Common.getUser(request);
if (!checkAccess(user))
loggedIn = false;
if (loggedIn && CrowdUtils.isCrowdEnabled()) {
if (CrowdUtils.isCrowdAuthenticated(user))
// The user may not have been authenticated by Crowd, so only check with Crowd if it was.
loggedIn = CrowdUtils.isAuthenticated(request, response);
}
if (!loggedIn) {
LOGGER.info("Denying access to secure page for session id " + request.getSession().getId() + ", uri="
+ request.getRequestURI());
response.sendRedirect(request.getContextPath() + forwardUrl);
//request.getRequestDispatcher(forwardUrl).forward(request, response);
return;
}
// Continue with the chain.
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {
// no op
}
abstract protected boolean checkAccess(User user);
}
web.xml의 또다른 핵심 설정은 서블릿 정의로 <servlet>로 서블릿의 이름과 클래스를 설정하고 특정 URL에 해당하는 서블릿 지정은 <servlet-mapping>으로 수행합니다. 다음의 코드는 springDispatcher를 정의하고 *.htm과 *.shtm을 springDispatcher로 맵핑시킨 예제입니다.
<servlet><servlet-name>springDispatcher</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>1</load-on-startup></servlet>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>springDispatcher</servlet-name>
<url-pattern>*.shtm</url-pattern>
</servlet-mapping>
위 코드의 설정 처럼 *.htm, *.shtm으로 끝나는 URL은 실제로는 단순한 HTML 파일이 아닙니다. 스프링 프레임워크의 처리 흐름에 따라 springDispatcher로 전달되고 springDispatcher는 springDispatcher-servlet.xml에 지정한 컨트롤러로 연결합니다.
<prop key="/compound_events.shtm">compoundEventsController</prop><prop key="/data_point_details.shtm">dataPointDetailsController</prop><prop key="/data_point_edit.shtm">dataPointEditController</prop><prop key="/data_source_edit.shtm">dataSourceEditController</prop><prop key="/data_sources.shtm">dataSourceListController</prop><prop key="/emport.shtm">emportController</prop><prop key="/event_handlers.shtm">eventHandlersController</prop><prop key="/events.shtm">eventsController</prop><prop key="/help.shtm">helpController</prop><prop key="/login.htm">loginController</prop><prop key="/logout.htm">logoutController</prop><prop key="/mailing_lists.shtm">mailingListsController</prop><prop key="/maintenance_events.shtm">maintenanceEventsController</prop><prop key="/point_hierarchy.shtm">pointHierarchyController</prop><prop key="/point_links.shtm">pointLinksController</prop><prop key="/public_view.htm">publicViewController</prop><prop key="/publisher_edit.shtm">publisherEditController</prop><prop key="/publishers.shtm">publisherListController</prop><prop key="/reports.shtm">reportsController</prop><prop key="/reportChart.shtm">reportChartController</prop><prop key="/scheduled_events.shtm">scheduledEventsController</prop><prop key="/scripting.shtm">scriptingController</prop><prop key="/sql.shtm">sqlController</prop><prop key="/system_settings.shtm">systemSettingsController</prop><prop key="/users.shtm">usersController</prop><prop key="/views.shtm">viewsController</prop><prop key="/view_edit.shtm">viewEditController</prop><prop key="/watch_list.shtm">watchListController</prop><prop key="/webcam_live_feed.htm">webcamLiveFeedController</prop><prop key="/export_project.htm">projectExporterController</prop><prop key="/import_project.htm">projectImporterController</prop><!-- Mobile user URLs --><prop key="/mobile_login.htm">mobileLoginController</prop><prop key="/mobile_logout.htm">mobileLogoutController</prop><prop key="/mobile_watch_list.shtm">mobileWatchListController</prop>......<bean id="loginController" class="com.serotonin.mango.web.mvc.controller.LoginController">
<property name="commandName"><value>login</value></property>
<property name="commandClass"><value>com.serotonin.mango.web.mvc.form.LoginForm</value></property>
<property name="formView"><value>login</value></property>
<property name="successUrl"><value>watch_list.shtm</value></property>
<property name="newUserUrl"><value>help.shtm</value></property>
<property name="bindOnNewForm"><value>true</value></property>
</bean>
<bean id="logoutController" class="com.serotonin.mango.web.mvc.controller.LogoutController">
<property name="redirectUrl"><value>login.htm</value></property>
</bean>
<bean id="mobileLoginController" class="com.serotonin.mango.web.mvc.controller.LoginController">
<property name="commandName"><value>login</value></property>
<property name="commandClass"><value>com.serotonin.mango.web.mvc.form.LoginForm</value></property>
<property name="formView"><value>mobile/login</value></property>
<property name="mobile"><value>true</value></property>
<property name="successUrl"><value>mobile_watch_list.shtm</value></property>
<property name="bindOnNewForm"><value>true</value></property>
</bean>
위의 설정 예제에 있는 /login.htm 주소는 login.htm 파일을 찾아 보여주는 것이 아니라 loginController로 컨트롤이 전달됩니다. loginController 실제 클래스는 <bean>을 통해서 정의됩니다. 다음은 로그인을 처리하는 컨트롤러 코드의 일부입니다.
private ModelAndView performLogin(HttpServletRequest request, String username) {
// Check if the user is already logged in.
User user = Common.getUser(request);
if (user != null && user.getUsername().equals(username)) {
// The user is already logged in. Nothing to do.
if (logger.isDebugEnabled())
logger.debug("User is already logged in, not relogging in");
}
else {
UserDao userDao = new UserDao();
// Get the user data from the app server.
user = new UserDao().getUser(username);
// Update the last login time.
userDao.recordLogin(user.getId());
// Add the user object to the session. This indicates to the rest
// of the application whether the user is logged in or not.
Common.setUser(request, user);
if (logger.isDebugEnabled())
logger.debug("User object added to session");
}
if (!mobile) {
if (user.isFirstLogin())
return new ModelAndView(new RedirectView(newUserUrl));
if (!StringUtils.isEmpty(user.getHomeUrl()))
return new ModelAndView(new RedirectView(user.getHomeUrl()));
}
return new ModelAndView(new RedirectView(successUrl));
}
위에서 언급한 내부 처리과정은 서블릿으로 처리하고 스프링 응용의 전형적인 사용 방법처럼 SCADABR이 사용자 인터페이스를 보여주는 작업은 JSP 코드를 통해서 처리합니다. 다음은 WEB-INF/jsp 폴더의 JSP 파일 목록입니다.
│ compoundEvents.jsp
│ dataPointDetails.jsp
│ dataPointEdit.jsp
│ dataSourceEdit.jsp
│ dataSourceList.jsp
│ emport.jsp
│ eventHandlers.jsp
│ events.jsp
│ help.jsp
│ import_result.jsp
│ login.jsp
│ mailingLists.jsp
│ maintenanceEvents.jsp
│ pointHierarchy.jsp
│ pointLinks.jsp
│ publicView.jsp
│ publisherEdit.jsp
│ publisherList.jsp
│ reports.jsp
│ scheduledEvents.jsp
│ scripting.jsp
│ sql.jsp
│ systemSettings.jsp
│ users.jsp
│ viewEdit.jsp
│ views.jsp
│ watchList.jsp
│ webcamLiveFeed.jsp
│
├─dataSourceEdit
│ dsEventsFoot.jspf
│ dsFoot.jspf
│ dsHead.jspf
│ editAlpha2.jsp
│ editAsciiFile.jsp
│ editAsciiSerial.jsp
│ editBacnetIp.jsp
│ editDnp3.jsp
│ editDnp3Ip.jsp
│ editDnp3Serial.jsp
│ editDrStorageHt5b.jsp
│ editEBI25.jsp
│ editFhz4J.jsp
│ editGalil.jsp
│ editHttpImage.jsp
│ editHttpReceiver.jsp
│ editHttpRetriever.jsp
│ editIEC101.jsp
│ editIEC101Ethernet.jsp
│ editIEC101Serial.jsp
│ editInternal.jsp
│ editJmx.jsp
│ editMBus.jsp
│ editMeta.jsp
│ editModbus.jsp
│ editModbusIp.jsp
│ editModbusSerial.jsp
│ editNmea.jsp
│ editNodaveS7.jsp
│ editOneWire.jsp
│ editOpc.jsp
│ editOpenV4J.jsp
│ editPachube.jsp
│ editPersistent.jsp
│ editPop3.jsp
│ editRadiuino.jsp
│ editSerialSettings.jsp
│ editSnmp.jsp
│ editSpinwave.jsp
│ editSql.jsp
│ editViconics.jsp
│ editVirtual.jsp
│ editVMStat.jsp
│
├─include
│ compoundEditor.jsp
│ customEditor.jsp
│ graphicRendererEditor.jsp
│ settingsEditor.jsp
│ staticEditor.jsp
│ tech.jsp
│ userComment.jsp
│
├─mobile
│ login.jsp
│ watchList.jsp
│
├─pointEdit
│ buttons.jsp
│ chartRenderer.jsp
│ eventDetectors.jsp
│ loggingProperties.jsp
│ pointName.jsp
│ pointProperties.jsp
│ textRenderer.jsp
│ valuePurge.jsp
│
└─publisherEdit
editHttpSender.jsp
editPachube.jsp
editPersistent.jsp
사용자가 웹 화면을 통해서 로그인하고 결과 화면을 받아보는 과정을 web.xml, JSP, 서블릿등을 통해서 알아보았는데, SCADABR의 WatchList 화면 등의 경우처럼 서버의 데이터를 출력하고 갱신해야 하는 경우 SCADABR에서는 AJAX와 유사한 기술인 DWR(Direct Web Remote)을 사용한다. 앞서 web.xml에서는 dwr-invoker라는 서블릿 정의가 있었고, applicationContext.xml에는 다음과 같이 DWR 단위의 클래스를 정의합니다.
<bean id="CompoundEventsDwr" class="com.serotonin.mango.web.dwr.CompoundEventsDwr"/>
<bean id="CustomViewDwr" class="com.serotonin.mango.web.dwr.CustomViewDwr"/>
<bean id="DataPointDetailsDwr" class="com.serotonin.mango.web.dwr.DataPointDetailsDwr"/>
<bean id="DataPointEditDwr" class="com.serotonin.mango.web.dwr.DataPointEditDwr"/>
<bean id="DataSourceEditDwr" class="com.serotonin.mango.web.dwr.DataSourceEditDwr"/>
<bean id="DataSourceListDwr" class="com.serotonin.mango.web.dwr.DataSourceListDwr"/>
<bean id="EmportDwr" class="com.serotonin.mango.web.dwr.EmportDwr"/>
<bean id="EventHandlersDwr" class="com.serotonin.mango.web.dwr.EventHandlersDwr"/>
<bean id="EventsDwr" class="com.serotonin.mango.web.dwr.EventsDwr"/>
<bean id="MailingListsDwr" class="com.serotonin.mango.web.dwr.MailingListsDwr"/>
<bean id="MaintenanceEventsDwr" class="com.serotonin.mango.web.dwr.MaintenanceEventsDwr"/>
<bean id="MiscDwr" class="com.serotonin.mango.web.dwr.MiscDwr"/>
<bean id="PointHierarchyDwr" class="com.serotonin.mango.web.dwr.PointHierarchyDwr"/>
<bean id="PointLinksDwr" class="com.serotonin.mango.web.dwr.PointLinksDwr"/>
<bean id="PublisherEditDwr" class="com.serotonin.mango.web.dwr.PublisherEditDwr"/>
<bean id="PublisherListDwr" class="com.serotonin.mango.web.dwr.PublisherListDwr"/>
<bean id="ReportsDwr" class="com.serotonin.mango.web.dwr.ReportsDwr"/>
<bean id="ScheduledEventsDwr" class="com.serotonin.mango.web.dwr.ScheduledEventsDwr"/>
<bean id="SystemSettingsDwr" class="com.serotonin.mango.web.dwr.SystemSettingsDwr"/>
<bean id="UsersDwr" class="com.serotonin.mango.web.dwr.UsersDwr"/>
<bean id="ViewDwr" class="com.serotonin.mango.web.dwr.ViewDwr"/>
<bean id="WatchListDwr" class="com.serotonin.mango.web.dwr.WatchListDwr"/>
<bean id="ScriptsDwr" class="br.org.scadabr.web.dwr.ScriptsDwr"/>
하위의 JavaScript와의 DWR간의 연관성은 dwr.xml에 정의합니다. 다음 그림은 이클립스에서 dwr.xml을 편집하는 모습입니다.