Nuiton-web-commits
Threads by month
- ----- 2026 -----
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
October 2011
- 3 participants
- 11 discussions
r121 - in trunk/nuiton-struts2: . src/main/java/org/nuiton/web/struts2 src/main/java/org/nuiton/web/struts2/filter src/main/java/org/nuiton/web/struts2/interceptor
by bleny@users.nuiton.org 28 Oct '11
by bleny@users.nuiton.org 28 Oct '11
28 Oct '11
Author: bleny
Date: 2011-10-28 18:00:53 +0200 (Fri, 28 Oct 2011)
New Revision: 121
Url: http://nuiton.org/repositories/revision/nuiton-web/121
Log:
#1789, #1790, #1791, introduce CloseTopiaTransactionFilter and OpenTopiaTransactionInterceptor, deprecate TopiaTransactionInterceptor
Added:
trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/filter/
trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/filter/CloseTopiaTransactionFilter.java
trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/OpenTopiaTransactionInterceptor.java
Modified:
trunk/nuiton-struts2/pom.xml
trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/TopiaTransactionInterceptor.java
Modified: trunk/nuiton-struts2/pom.xml
===================================================================
--- trunk/nuiton-struts2/pom.xml 2011-09-22 17:27:59 UTC (rev 120)
+++ trunk/nuiton-struts2/pom.xml 2011-10-28 16:00:53 UTC (rev 121)
@@ -20,10 +20,10 @@
<dependencies>
- <!--dependency>
+ <dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
- </dependency-->
+ </dependency>
<dependency>
<groupId>org.apache.struts.xwork</groupId>
@@ -51,6 +51,11 @@
</dependency>
<dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
Added: trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/filter/CloseTopiaTransactionFilter.java
===================================================================
--- trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/filter/CloseTopiaTransactionFilter.java (rev 0)
+++ trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/filter/CloseTopiaTransactionFilter.java 2011-10-28 16:00:53 UTC (rev 121)
@@ -0,0 +1,73 @@
+package org.nuiton.web.struts2.filter;
+
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.nuiton.topia.TopiaContext;
+import org.nuiton.topia.TopiaException;
+import org.nuiton.topia.TopiaRuntimeException;
+import org.nuiton.web.struts2.interceptor.OpenTopiaTransactionInterceptor;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+
+/** Close a TopiaTransaction instance stored in the servlet context.
+ *
+ * The main usage is to close a transaction opened by the
+ * {@link OpenTopiaTransactionInterceptor}. A convention is used to find it,
+ * the transaction attached to the request is found at key
+ * {@link OpenTopiaTransactionInterceptor#TOPIA_TRANSACTION_REQUEST_ATTRIBUTE}
+ *
+ * @since 1.5
+ * @author bleny
+ */
+public class CloseTopiaTransactionFilter implements Filter {
+
+ /** Logger. */
+ private static final Log log = LogFactory.getLog(CloseTopiaTransactionFilter.class);
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+ try {
+ filterChain.doFilter(servletRequest, servletResponse);
+ } finally {
+ TopiaContext transaction = (TopiaContext)
+ servletRequest.getAttribute(
+ OpenTopiaTransactionInterceptor.TOPIA_TRANSACTION_REQUEST_ATTRIBUTE);
+
+ if (transaction == null) {
+ if (log.isTraceEnabled()) {
+ log.trace("no transaction to close");
+ }
+ } else if (transaction.isClosed()) {
+ if (log.isTraceEnabled()) {
+ log.trace("transaction " + transaction + " is already closed");
+ }
+ } else {
+ if (log.isDebugEnabled()) {
+ log.debug("close transaction " + transaction);
+ }
+ try {
+ transaction.closeContext();
+ } catch (TopiaException e) {
+ throw new TopiaRuntimeException(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // nothing to do
+ }
+
+ @Override
+ public void destroy() {
+ // nothing to do
+ }
+}
Added: trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/OpenTopiaTransactionInterceptor.java
===================================================================
--- trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/OpenTopiaTransactionInterceptor.java (rev 0)
+++ trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/OpenTopiaTransactionInterceptor.java 2011-10-28 16:00:53 UTC (rev 121)
@@ -0,0 +1,235 @@
+package org.nuiton.web.struts2.interceptor;
+
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts2.ServletActionContext;
+import org.nuiton.topia.TopiaContext;
+import org.nuiton.topia.TopiaException;
+import org.nuiton.topia.framework.TopiaContextImplementor;
+import org.nuiton.topia.framework.TopiaTransactionAware;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <!-- START SNIPPET: description -->
+ * <p/>
+ * The aim of this Interceptor is to manage a {@code transaction} all along
+ * a action which implements {@link TopiaTransactionAware} contract.
+ * <p/>
+ * Technicaly, the action will receive only a proxy of a transaction and a real
+ * transaction will only be created as soon as a method will be asked on it.
+ * <p/>
+ * The interceptor is abstract and let user to implement the way how to open a
+ * new transaction via the method {@link #beginTransaction()}.
+ * <p/>
+ * Note that the transaction pushed in the action can be limited using a list
+ * of methods to exclude on it. The list of methods to forbid can be customized
+ * using the interceptor parameter {@link #excludeMethods}.
+ * <p/>
+ * Note also that the transaction is <strong>not</strong> commited nor closed.
+ * If you want the transaction to be closed, you may use
+ * {@link org.nuiton.web.struts2.filter.CloseTopiaTransactionFilter} by adding
+ * it to your web.xml file.
+ * <p/>
+ * This interceptor, as it provides connection to database should be in the
+ * interceptor stack before any other interceptor requiring access to database.
+ * For example, it is a common behaviour to do such calls in a prepare method,
+ * so make sure to place this interceptor before the {@code prepare} interceptor.
+ * <!-- END SNIPPET: description -->
+ * <p/>
+ * <p/> <u>Interceptor parameters:</u>
+ * <p/>
+ * <!-- START SNIPPET: parameters -->
+ * <p/>
+ * <ul>
+ * <li>excludeMethods (optional) - Customized method names separated by coma to
+ * forbid on the proxy of the transaction given to action. By default, if this
+ * parameter is not filled, then we will use this one :
+ * {@link #DEFAULT_EXCLUDE_METHODS}.</li>
+ * </ul>
+ * <p/>
+ * <!-- END SNIPPET: parameters -->
+ *
+ * @author tchemit <chemit(a)codelutin.com>
+ * @since 1.5
+ */
+public abstract class OpenTopiaTransactionInterceptor extends AbstractInterceptor {
+
+ /** To specify on your action that you never want any commit. */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.TYPE, ElementType.METHOD})
+ public @interface NoCommit {
+ }
+
+ /** Logger. */
+ private static final Log log =
+ LogFactory.getLog(OpenTopiaTransactionInterceptor.class);
+
+ private static final long serialVersionUID = 1L;
+
+ public static final String TOPIA_TRANSACTION_REQUEST_ATTRIBUTE = "topiaTransaction";
+
+ public static final String[] DEFAULT_EXCLUDE_METHODS = {
+ "beginTransaction",
+ "closeContext",
+ "clear"
+ };
+
+ /** names of methods to forbid access while using proxy. */
+ protected Set<String> excludeMethods;
+
+ public Set<String> getExcludeMethods() {
+ return excludeMethods;
+ }
+
+ public void setExcludeMethods(String excludeMethods) {
+ this.excludeMethods =
+ TextParseUtil.commaDelimitedStringToSet(excludeMethods);
+ }
+
+ /**
+ * Method to open a new transaction.
+ *
+ * @return the new freshly opened transaction
+ * @throws org.nuiton.topia.TopiaException if any problem while opening a new transaction
+ */
+ protected abstract TopiaContext beginTransaction() throws TopiaException;
+
+ @Override
+ public void init() {
+ super.init();
+
+ if (getExcludeMethods() == null) {
+
+ // use default exclude methods
+ excludeMethods = new HashSet<String>(
+ Arrays.asList(DEFAULT_EXCLUDE_METHODS)
+ );
+ }
+ }
+
+ /**
+ * @deprecated to be removed in the same time as TopiaTransactionInterceptor
+ */
+ @Deprecated
+ protected void closeTransaction(TopiaContext transaction, Object action,
+ ActionInvocation invocation) throws TopiaException {
+ // do nothing by contract
+ }
+
+ @Override
+ public String intercept(ActionInvocation invocation) throws Exception {
+
+ TopiaTransactionAware transactionAware = null;
+
+ Object action = invocation.getProxy().getAction();
+
+ if (action instanceof TopiaTransactionAware) {
+ transactionAware = (TopiaTransactionAware) action;
+ }
+
+ if (transactionAware == null) {
+
+ // not a transaction aware action, direct skip this interceptor
+ return invocation.invoke();
+ }
+
+ // creates a proxy of a lazy transaction
+
+ TopiaTransactionProxyInvocationHandler proxyInvocationHandler =
+ new TopiaTransactionProxyInvocationHandler();
+
+ TopiaContext proxy = (TopiaContext) Proxy.newProxyInstance(
+ getClass().getClassLoader(),
+ new Class<?>[]{TopiaContext.class,
+ TopiaContextImplementor.class},
+ proxyInvocationHandler
+ );
+
+ // set the transaction in the action
+ transactionAware.setTransaction(proxy);
+
+ try {
+
+ return invocation.invoke();
+
+ } finally {
+
+ closeTransaction(proxyInvocationHandler.getTransaction(), action, invocation);
+
+ }
+ }
+
+ /**
+ * Handler of a proxy on a {@link TopiaContext}.
+ *
+ * @see #excludeMethods
+ */
+ public class TopiaTransactionProxyInvocationHandler implements InvocationHandler {
+
+ /** Target to use for the proxy. */
+ protected TopiaContext transaction;
+
+ public TopiaContext getTransaction() {
+ return transaction;
+ }
+
+ @Override
+ public Object invoke(Object proxy,
+ Method method,
+ Object[] args) throws Throwable {
+
+ String methodName = method.getName();
+
+ if (getExcludeMethods().contains(methodName)) {
+
+ // not authorized
+ throw new IllegalAccessException(
+ "Not allowed to access method " + methodName + " on " +
+ proxy);
+ }
+
+ if (transaction == null) {
+
+ if (log.isTraceEnabled()) {
+ log.trace("transaction started due to a call to " + methodName);
+ }
+
+ // first time transaction is required, create its
+ transaction = beginTransaction();
+
+ // push transaction in request to make it available for closing in
+ // CloseTopiaTransactionFilter
+ ServletActionContext.getRequest().setAttribute(
+ TOPIA_TRANSACTION_REQUEST_ATTRIBUTE, transaction);
+
+ if (log.isDebugEnabled()) {
+ log.debug("Open transaction " + transaction);
+ }
+ }
+
+ // can invoke the method on the transaction
+ try {
+ Object result = method.invoke(transaction, args);
+ return result;
+ } catch (Exception eee) {
+ if (log.isErrorEnabled()) {
+ log.error("Could not execute method " + method.getName(), eee);
+ }
+ throw eee;
+ }
+ }
+ }
+}
Modified: trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/TopiaTransactionInterceptor.java
===================================================================
--- trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/TopiaTransactionInterceptor.java 2011-09-22 17:27:59 UTC (rev 120)
+++ trunk/nuiton-struts2/src/main/java/org/nuiton/web/struts2/interceptor/TopiaTransactionInterceptor.java 2011-10-28 16:00:53 UTC (rev 121)
@@ -25,206 +25,56 @@
package org.nuiton.web.struts2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
-import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
-import com.opensymphony.xwork2.util.TextParseUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.topia.TopiaContext;
import org.nuiton.topia.TopiaException;
-import org.nuiton.topia.framework.TopiaContextImplementor;
-import org.nuiton.topia.framework.TopiaTransactionAware;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-/**
- * <!-- START SNIPPET: description -->
- * <p/>
- * The aim of this Interceptor is to manage a {@code transaction} all along
- * a action which implements {@link TopiaTransactionAware}
- * contract.
- * <p/>
- * Technicaly, the action will receive only a proxy of a transaction and a real
- * transaction will only be created as soon as a method will be asked on it.
- * <p/>
- * The interceptor is abstract and let user to implement the way how to open a
- * new transaction via the method {@link #beginTransaction()}.
- * <p/>
- * Note that the transaction pushed in the action can be limited using a list
- * of methods to exclude on it. The list of methods to forbid can be customized
- * using the interceptor parameter {@link #excludeMethods}.
- * <p/>
- * Note also that the transaction is commited and closed after all stack of
- * interceptor consumed, this means that the transaction will still be opened
- * while rendering the result, this is a particular interesting thing to avoid
- * pre-loading of entities due to lazy strategy of hibernate for example.
- * With this mecanism you can feel free to just obtain the obtain from database
- * via a DAO and then really load it in the rendering result.
- * <p/>
- * If you do not want any commit for a given action, just put on the class a commit
- * <p/>
- * This interceptor, as it provides connection to database should be in the
- * interceptor stack before any other interceptor requiring access to database.
- * For example, it is a common behaviour to do such calls in a prepare method,
- * so make sure to place this interceptor before the {@code prepare} interceptor.
- * <!-- END SNIPPET: description -->
- * <p/>
- * <p/> <u>Interceptor parameters:</u>
- * <p/>
- * <!-- START SNIPPET: parameters -->
- * <p/>
- * <ul>
- * <li>excludeMethods (optional) - Customized method names separated by coma to
- * forbid on the proxy of the transaction given to action. By default, if this
- * parameter is not filled, then we will use this one :
- * {@link #DEFAULT_EXCLUDE_METHODS}.</li>
- * </ul>
- * <p/>
- * <!-- END SNIPPET: parameters -->
+/** Same interceptor as {@link OpenTopiaTransactionInterceptor} but close
+ * the transaction after action is called. The transaction will be commited
+ * (unless asked otherwise) and closed.
*
* @author tchemit <chemit(a)codelutin.com>
* @since 1.2
*/
-public abstract class TopiaTransactionInterceptor extends AbstractInterceptor {
+@Deprecated
+public abstract class TopiaTransactionInterceptor extends OpenTopiaTransactionInterceptor {
- /** To specify on your action that you never want any commit. */
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE, ElementType.METHOD})
- public @interface NoCommit {
- }
-
/** Logger. */
- private static final Log log =
- LogFactory.getLog(TopiaTransactionInterceptor.class);
+ private static final Log log = LogFactory.getLog(TopiaTransactionInterceptor.class);
- private static final long serialVersionUID = 1L;
-
- public static final String[] DEFAULT_EXCLUDE_METHODS = {
- "beginTransaction",
- "closeContext",
- "clear"
- };
-
- /** names of methods to forbid access while using proxy. */
- protected Set<String> excludeMethods;
-
- public Set<String> getExcludeMethods() {
- return excludeMethods;
- }
-
- public void setExcludeMethods(String excludeMethods) {
- this.excludeMethods =
- TextParseUtil.commaDelimitedStringToSet(excludeMethods);
- }
-
- /**
- * Method to open a new transaction.
- *
- * @return the new freshly opened transaction
- * @throws TopiaException if any problem while opening a new transaction
- */
- protected abstract TopiaContext beginTransaction() throws TopiaException;
-
@Override
- public void init() {
- super.init();
+ protected void closeTransaction(TopiaContext transaction, Object action,
+ ActionInvocation invocation) throws TopiaException {
- if (getExcludeMethods() == null) {
+ boolean commitNeeded = isCommitNeeded(action, invocation);
- // use default exclude methods
- excludeMethods = new HashSet<String>(
- Arrays.asList(DEFAULT_EXCLUDE_METHODS)
- );
- }
- }
+ if (transaction != null && ! transaction.isClosed()) {
+ try {
+ if (commitNeeded) {
- @Override
- public String intercept(ActionInvocation invocation) throws Exception {
-
- TopiaTransactionAware transactionAware = null;
-
- Object action = invocation.getProxy().getAction();
-
- if (action instanceof TopiaTransactionAware) {
- transactionAware = (TopiaTransactionAware) action;
- }
-
- if (transactionAware == null) {
-
- // not a transaction aware action, direct skip this interceptor
- return invocation.invoke();
- }
-
- // creates a proxy of a lazy transaction
-
- TopiaTransactionProxyInvocationHandler proxyInvocationHandler =
- new TopiaTransactionProxyInvocationHandler();
-
- TopiaContext proxy = (TopiaContext) Proxy.newProxyInstance(
- getClass().getClassLoader(),
- new Class<?>[]{TopiaContext.class,
- TopiaContextImplementor.class},
- proxyInvocationHandler
- );
-
- // set the transaction in the action
- transactionAware.setTransaction(proxy);
-
- boolean doCommit = isCommitNeeded(action, invocation);
- try {
- return invocation.invoke();
- } catch (Exception e) {
-
- doCommit = false;
- TopiaContext transaction = proxyInvocationHandler.getTransaction();
-
- if (transaction != null && !transaction.isClosed()) {
- if (log.isDebugEnabled()) {
- log.debug("rollback transaction " + transaction);
- }
- transaction.rollbackTransaction();
- }
-
- throw e;
- } finally {
-
- TopiaContext transaction = proxyInvocationHandler.getTransaction();
-
- if (transaction != null && !transaction.isClosed()) {
- try {
- if (doCommit) {
-
- // commit the opened transaction
- if (log.isDebugEnabled()) {
- log.debug("Commit transaction " + transaction);
- }
- transaction.commitTransaction();
- }
- } finally {
-
- // close the opened transaction
+ // commit the opened transaction
if (log.isDebugEnabled()) {
- log.debug("Close transaction " + transaction);
+ log.debug("Commit transaction " + transaction);
}
- transaction.closeContext();
+ transaction.commitTransaction();
}
+ } finally {
+ // close the opened transaction
+ if (log.isDebugEnabled()) {
+ log.debug("Close transaction " + transaction);
+ }
+ transaction.closeContext();
}
-
}
}
protected boolean isCommitNeeded(Object action,
- ActionInvocation invocation) throws NoSuchMethodException {
+ ActionInvocation invocation) {
Class<?> actionType = action.getClass();
boolean noCommit = actionType.isAnnotationPresent(NoCommit.class);
if (noCommit) {
@@ -244,7 +94,12 @@
// no methodName specify, means the execute one
methodName = "execute";
}
- Method method = actionType.getMethod(methodName);
+ Method method;
+ try {
+ method = actionType.getMethod(methodName);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
noCommit = method.isAnnotationPresent(NoCommit.class);
if (noCommit) {
if (log.isDebugEnabled()) {
@@ -255,55 +110,4 @@
return !noCommit;
}
- /**
- * Handler of a proxy on a {@link TopiaContext}.
- *
- * @see TopiaTransactionInterceptor#excludeMethods
- */
- public class TopiaTransactionProxyInvocationHandler implements InvocationHandler {
-
- /** Target to use for the proxy. */
- protected TopiaContext transaction;
-
- public TopiaContext getTransaction() {
- return transaction;
- }
-
- @Override
- public Object invoke(Object proxy,
- Method method,
- Object[] args) throws Throwable {
-
- String methodName = method.getName();
-
- if (getExcludeMethods().contains(methodName)) {
-
- // not authorized
- throw new IllegalAccessException(
- "Not allowed to access method " + methodName + " on " +
- proxy);
- }
-
- if (transaction == null) {
-
- // first time transaction is required, create its
- transaction = beginTransaction();
-
- if (log.isDebugEnabled()) {
- log.debug("Open transaction " + transaction);
- }
- }
- // can invoke the method on the transaction
- try {
- Object result = method.invoke(transaction, args);
- return result;
- } catch (Exception eee) {
- if (log.isErrorEnabled()) {
- log.error("Could not execute method " + method.getName(), eee);
- }
- throw eee;
- }
- }
- }
-
}
\ No newline at end of file
1
0