View Javadoc
1   /**
2    * This file Copyright (c) 2003-2014 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.commands;
35  
36  import info.magnolia.cms.util.AlertUtil;
37  
38  import java.util.Collections;
39  import java.util.Map;
40  
41  import org.apache.commons.beanutils.BeanUtils;
42  import org.apache.commons.beanutils.PropertyUtils;
43  import org.apache.commons.chain.Command;
44  import org.apache.commons.chain.Context;
45  import org.apache.commons.lang.exception.NestableException;
46  import org.apache.commons.pool.BasePoolableObjectFactory;
47  import org.apache.commons.pool.impl.StackObjectPool;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  
52  /**
53   * To make the coding of commands as easy as possible the default values set in the config are set and the values of the
54   * context are set as properties too if the naming matches. To get a better performance we use an inner object pool. The
55   * execution of the command gets a clone from the pool and executes the clone
56   * @author Philipp Bracher
57   * @version $Revision$ ($Author$)
58   */
59  public abstract class MgnlCommand implements Command {
60  
61      public static Logger log = LoggerFactory.getLogger(MgnlCommand.class);
62  
63      /**
64       * The default properties. Lazy bound.
65       */
66      private Map defaultProperties;
67  
68      private boolean isClone = false;
69  
70      private boolean isEnabled = true;
71  
72      /**
73       * Command factory creating clones of the master/prototype command and pooling the cloned instances.
74       * @author Philipp Bracher
75       * @version $Id$
76       */
77      class MgnlCommandFactory extends BasePoolableObjectFactory {
78  
79          /**
80           * The prototype we clone for faster execution.
81           */
82          private MgnlCommand prototype;
83  
84          /**
85           * @param prototype
86           */
87          public MgnlCommandFactory(MgnlCommand prototype) {
88              this.prototype = prototype;
89          }
90  
91          @Override
92          public Object makeObject() throws Exception {
93              MgnlCommand cmd = (MgnlCommand) BeanUtils.cloneBean(this.prototype);
94              cmd.setClone(true);
95              return cmd;
96          }
97  
98          @Override
99          public void activateObject(Object arg0) throws Exception {
100             super.activateObject(arg0);
101             // set default properties
102             BeanUtils.populate(arg0, defaultProperties);
103         }
104 
105         @Override
106         public void passivateObject(Object cmd) throws Exception {
107             ((MgnlCommand) cmd).release();
108             super.passivateObject(cmd);
109         }
110 
111     }
112 
113     /**
114      * Pool of inner commands.
115      */
116     private StackObjectPool pool;
117 
118     /**
119      * True if we can use object pooling. Else we synchronize the execution.
120      */
121     private boolean pooling = true;
122 
123     /**
124      * Make sure that the context is castable to a magnolia context.
125      * @return true on success, false otherwise
126      */
127     @Override
128     public boolean execute(Context ctx) throws Exception {
129         if (!(ctx instanceof info.magnolia.context.Context)) {
130             throw new IllegalArgumentException("context must be of type " + info.magnolia.context.Context.class);
131         }
132 
133         if (this.defaultProperties == null) {
134             initDefaultProperties();
135         }
136 
137         MgnlCommand cmd;
138 
139         if (pooling) {
140             // do not instantiate until the pool is really needed
141             // means: do not create a pool for command objects created in the pool itself
142             if (pool == null) {
143                 pool = new StackObjectPool(new MgnlCommandFactory(this));
144             }
145 
146             try {
147                 // try to use the pool
148                 cmd = (MgnlCommand) pool.borrowObject();
149             }
150             // this happens if the commons constructor is not public: anonymous classes for example
151             catch (Throwable t) {
152                 pooling = false;
153                 // start again
154                 return execute(ctx);
155             }
156         }
157         else {
158             cmd = this;
159         }
160 
161         boolean success = executePooledOrSynchronized(ctx, cmd);
162         // convert the confusing true false behavior to fit commons chain
163         return !success;
164     }
165 
166     private boolean executePooledOrSynchronized(Context ctx, MgnlCommand cmd) throws Exception {
167         boolean success = false; // break execution
168 
169         // populate the command if we are using a pool
170         if (pooling) {
171             BeanUtils.populate(cmd, ctx);
172             // cast to mgnl context class
173             try {
174                 success = cmd.execute((info.magnolia.context.Context) ctx);
175             }
176             catch (Exception e) {
177                 AlertUtil.setException(e, (info.magnolia.context.Context) ctx);
178                 throw new NestableException("exception during executing command", e);
179             }
180             finally {
181                 pool.returnObject(cmd);
182             }
183         }
184         else {
185             synchronized (cmd) {
186                 BeanUtils.populate(cmd, ctx);
187                 try {
188                     success = cmd.execute((info.magnolia.context.Context) ctx);
189                 }
190                 catch (Exception e) {
191                     AlertUtil.setException(e, (info.magnolia.context.Context) ctx);
192                     throw new NestableException("exception during executing command", e);
193                 }
194                 finally {
195                     if (pooling) {
196                         pool.returnObject(cmd);
197                     }
198                     else {
199                         cmd.release();
200                         BeanUtils.populate(cmd, defaultProperties);
201                     }
202                 }
203             }
204         }
205         return success;
206     }
207 
208     private void initDefaultProperties() {
209         try {
210             this.defaultProperties = PropertyUtils.describe(this);
211         } catch (Exception e) {
212             if (log.isDebugEnabled()) {
213                 log.warn("Failed to persist "+ this.getClass().getName()+" command properties due to "+ e.getMessage()+". Please review your configuration.", e);
214             } else {
215                 log.warn("Failed to persist {} command properties due to {}. Please review your configuration.", this.getClass().getName(), e.getMessage());
216             }
217             this.defaultProperties = Collections.EMPTY_MAP;
218         }
219     }
220 
221     public abstract boolean execute(info.magnolia.context.Context context) throws Exception;
222 
223     /**
224      * If a clone is passivated we call this method. Please clean up private properties.
225      */
226     public void release() {
227     }
228 
229     /**
230      * @return the isClone
231      */
232     protected boolean isClone() {
233         return isClone;
234     }
235 
236     /**
237      * @param isClone the isClone to set
238      */
239     protected void setClone(boolean isClone) {
240         this.isClone = isClone;
241     }
242 
243 
244     public boolean isEnabled() {
245         return this.isEnabled;
246     }
247 
248 
249     public void setEnabled(boolean isEnabled) {
250         this.isEnabled = isEnabled;
251     }
252 
253 }