001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.lenya.cms.usecase;
019:
020: import java.util.ArrayList;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.Set;
027:
028: import org.apache.avalon.framework.activity.Initializable;
029: import org.apache.avalon.framework.configuration.Configurable;
030: import org.apache.avalon.framework.configuration.Configuration;
031: import org.apache.avalon.framework.configuration.ConfigurationException;
032: import org.apache.avalon.framework.context.Context;
033: import org.apache.avalon.framework.context.ContextException;
034: import org.apache.avalon.framework.context.Contextualizable;
035: import org.apache.avalon.framework.logger.AbstractLogEnabled;
036: import org.apache.avalon.framework.service.ServiceException;
037: import org.apache.avalon.framework.service.ServiceManager;
038: import org.apache.avalon.framework.service.Serviceable;
039: import org.apache.cocoon.components.ContextHelper;
040: import org.apache.cocoon.environment.Request;
041: import org.apache.cocoon.servlet.multipart.Part;
042: import org.apache.lenya.cms.publication.DocumentFactory;
043: import org.apache.lenya.cms.publication.DocumentUtil;
044: import org.apache.lenya.cms.repository.Node;
045: import org.apache.lenya.cms.repository.RepositoryException;
046: import org.apache.lenya.cms.repository.RepositoryUtil;
047: import org.apache.lenya.cms.repository.Session;
048: import org.apache.lenya.transaction.ConcurrentModificationException;
049: import org.apache.lenya.transaction.LockException;
050: import org.apache.lenya.transaction.TransactionLock;
051:
052: /**
053: * Abstract usecase implementation.
054: *
055: * @version $Id: AbstractUsecase.java 571548 2007-08-31 19:16:21Z rfrovarp $
056: */
057: public class AbstractUsecase extends AbstractLogEnabled implements
058: Usecase, Configurable, Contextualizable, Serviceable,
059: Initializable {
060:
061: protected static final String EVENT_CHECK_POSTCONDITIONS = "checkPostconditions";
062:
063: protected static final String EVENT_EXECUTE = "execute";
064:
065: protected static final String EVENT_CHECK_PRECONDITIONS = "checkPreconditions";
066:
067: protected static final String EVENT_CHECK_EXECUTION_CONDITIONS = "checkExecutionConditions";
068:
069: protected static final String ERROR_OBJECTS_CHECKED_OUT = "objects-checked-out";
070:
071: protected static final StateMachine.Transition[] TRANSITIONS = {
072: new StateMachine.Transition("start", "preChecked",
073: EVENT_CHECK_PRECONDITIONS),
074: new StateMachine.Transition("preChecked", "preChecked",
075: EVENT_CHECK_PRECONDITIONS),
076: new StateMachine.Transition("preChecked", "nodesLocked",
077: "lockInvolvedObjects"),
078: new StateMachine.Transition("nodesLocked", "execChecked",
079: EVENT_CHECK_EXECUTION_CONDITIONS),
080: new StateMachine.Transition("execChecked", "execChecked",
081: EVENT_CHECK_EXECUTION_CONDITIONS),
082: new StateMachine.Transition("nodesLocked", "preChecked",
083: EVENT_CHECK_PRECONDITIONS),
084: new StateMachine.Transition("execChecked", "executed",
085: EVENT_EXECUTE),
086: new StateMachine.Transition("executed", "postChecked",
087: EVENT_CHECK_POSTCONDITIONS) };
088:
089: protected static final StateMachine.Model MODEL = new StateMachine.Model(
090: "start", TRANSITIONS);
091:
092: protected static final String PARAMETER_STATE_MACHINE = "private.stateMachine";
093: protected static final String PARAMETER_SESSION = "private.session";
094: protected static final String PARAMETER_FACTORY = "private.factory";
095: protected static final String PARAMETER_CHECKOUT_RESTRICTED_TO_SESSION = "checkoutRestrictedToSession";
096:
097: protected static final String PARAMETERS_INITIALIZED = "private.parametersInitialized";
098:
099: /**
100: * Override to initialize parameters.
101: */
102: protected void initParameters() {
103: }
104:
105: /**
106: * Advance the usecase state machine to the next state. This method has to be called at the end
107: * of the corresponding method to ensure that the subsequent methods can only be invoked if
108: * nothing went wrong.
109: * @param event The vent to invoke.
110: */
111: protected void advanceState(String event) {
112: getStateMachine().invoke(event);
113: }
114:
115: protected StateMachine getStateMachine() {
116: StateMachine machine = (StateMachine) getParameter(PARAMETER_STATE_MACHINE);
117: return machine;
118: }
119:
120: protected void checkEvent(String event) {
121: getStateMachine().checkEvent(event);
122: }
123:
124: protected String SOURCE_URL = "private.sourceUrl";
125:
126: /**
127: * @see org.apache.lenya.cms.usecase.Usecase#getSourceURL() We don't use getParameterAsString()
128: * because this will typically cause stack overflows or NPEs in connection with
129: * initParameters().
130: */
131: public String getSourceURL() {
132: return (String) this .parameters.get(SOURCE_URL);
133: }
134:
135: /**
136: * Returns the context.
137: * @return A context.
138: */
139: protected Context getContext() {
140: return this .context;
141: }
142:
143: /**
144: * Determine if the usecase has error messages. Provides a way of checking for errors without
145: * actually retrieving them.
146: * @return true if the usecase resulted in error messages.
147: */
148: public boolean hasErrors() {
149: boolean ret = false;
150: if (this .errorMessages != null)
151: ret = !this .errorMessages.isEmpty();
152:
153: if (getLogger().isDebugEnabled())
154: getLogger().debug(
155: "AbstractUsecase::hasErrors() called, returning "
156: + ret);
157:
158: return ret;
159: }
160:
161: /**
162: * Determine if the usecase has info messages. Provides a way of checking for info messages
163: * without actually retrieving them.
164: * @return true if the usecase resulted in info messages being generated.
165: */
166: public boolean hasInfoMessages() {
167: boolean ret = false;
168: if (this .infoMessages != null)
169: ret = !this .infoMessages.isEmpty();
170: return ret;
171: }
172:
173: /**
174: * Checks if the operation can be executed and returns the error messages. Error messages
175: * prevent the operation from being executed.
176: * @return A boolean value.
177: */
178: public List getErrorMessages() {
179: return Collections.unmodifiableList(new ArrayList(
180: this .errorMessages));
181: }
182:
183: /**
184: * Returns the information messages to show on the confirmation screen.
185: * @return An array of strings. Info messages do not prevent the operation from being executed.
186: */
187: public List getInfoMessages() {
188: return Collections.unmodifiableList(new ArrayList(
189: this .infoMessages));
190: }
191:
192: private List errorMessages = new ArrayList();
193: private List infoMessages = new ArrayList();
194:
195: /**
196: * Adds an error message.
197: * @param message The message.
198: */
199: public void addErrorMessage(String message) {
200: this .errorMessages.add(new UsecaseMessage(message));
201: }
202:
203: /**
204: * Adds an error message.
205: * @param message The message.
206: * @param _params parameters
207: */
208: public void addErrorMessage(String message, String[] _params) {
209: this .errorMessages.add(new UsecaseMessage(message, _params));
210: }
211:
212: /**
213: * Adds an error message.
214: * @param messages The messages.
215: */
216: public void addErrorMessages(String[] messages) {
217: for (int i = 0; i < messages.length; i++) {
218: addErrorMessage(messages[i]);
219: }
220: }
221:
222: /**
223: * Adds an info message.
224: * @param message The message.
225: * @param _params parameters
226: */
227: public void addInfoMessage(String message, String[] _params) {
228: this .infoMessages.add(new UsecaseMessage(message, _params));
229: }
230:
231: /**
232: * Adds an info message.
233: * @param message The message.
234: */
235: public void addInfoMessage(String message) {
236: this .infoMessages.add(new UsecaseMessage(message));
237: }
238:
239: /**
240: * @see org.apache.lenya.cms.usecase.Usecase#checkExecutionConditions()
241: */
242: public final void checkExecutionConditions()
243: throws UsecaseException {
244: checkEvent(EVENT_CHECK_EXECUTION_CONDITIONS);
245: try {
246: clearErrorMessages();
247: clearInfoMessages();
248: doCheckExecutionConditions();
249: dumpErrorMessages();
250: } catch (Exception e) {
251: getLogger().error(e.getMessage(), e);
252: addErrorMessage(e.getMessage()
253: + " - Please consult the logfiles.");
254: if (getLogger().isDebugEnabled()) {
255: throw new UsecaseException(e);
256: }
257: }
258: if (!hasErrors()) {
259: advanceState(EVENT_CHECK_EXECUTION_CONDITIONS);
260: }
261: }
262:
263: /**
264: * Checks the execution conditions.
265: * @throws Exception if an error occurs.
266: */
267: protected void doCheckExecutionConditions() throws Exception {
268: // do nothing
269: }
270:
271: /**
272: * @see org.apache.lenya.cms.usecase.Usecase#checkPreconditions()
273: */
274: public final void checkPreconditions() throws UsecaseException {
275: checkEvent(EVENT_CHECK_PRECONDITIONS);
276: try {
277: clearErrorMessages();
278: clearInfoMessages();
279:
280: Node[] nodes = getNodesToLock();
281: if (!canCheckOut(nodes)) {
282: addErrorMessage(ERROR_OBJECTS_CHECKED_OUT);
283: }
284: doCheckPreconditions();
285:
286: List _errorMessages = getErrorMessages();
287: for (int i = 0; i < _errorMessages.size(); i++) {
288: getLogger().info(_errorMessages.get(i).toString());
289: }
290: } catch (Exception e) {
291: getLogger().error(e.getMessage(), e);
292: addErrorMessage(e.getMessage()
293: + " - Please consult the logfiles.");
294: if (getLogger().isDebugEnabled()) {
295: throw new UsecaseException(e);
296: }
297: }
298: if (!hasErrors()) {
299: advanceState(EVENT_CHECK_PRECONDITIONS);
300: }
301: }
302:
303: /**
304: * Checks the preconditions.
305: * @throws Exception if an error occurs.
306: */
307: protected void doCheckPreconditions() throws Exception {
308: // do nothing
309: }
310:
311: /**
312: * Clears the error messages.
313: */
314: protected void clearErrorMessages() {
315: this .errorMessages.clear();
316: }
317:
318: /**
319: * Clears the info messages.
320: */
321: protected void clearInfoMessages() {
322: this .infoMessages.clear();
323: }
324:
325: /**
326: * @see org.apache.lenya.cms.usecase.Usecase#execute()
327: */
328: public final void execute() throws UsecaseException {
329: checkEvent(EVENT_EXECUTE);
330: Exception exception = null;
331: try {
332: clearErrorMessages();
333: clearInfoMessages();
334: doExecute();
335: dumpErrorMessages();
336: } catch (LockException e) {
337: exception = e;
338: addErrorMessage("The operation could not be completed because an involved object was changed by another user.");
339: } catch (Exception e) {
340: exception = e;
341: getLogger().error(e.getMessage(), e);
342: addErrorMessage(e.getMessage()
343: + " - Please consult the logfiles.");
344: throw new UsecaseException(e);
345: } finally {
346: try {
347: if (this .commitEnabled && getErrorMessages().isEmpty()
348: && exception == null) {
349: getSession().commit();
350: } else {
351: getSession().rollback();
352: }
353: } catch (ConcurrentModificationException e) {
354: getLogger().error(
355: "Could not commit usecase [" + getName()
356: + "]: " + e.getMessage());
357: addErrorMessage(e.getMessage());
358: } catch (Exception e1) {
359: getLogger().error(
360: "Could not commit/rollback usecase ["
361: + getName() + "]: ", e1);
362: addErrorMessage("Exception during commit or rollback: "
363: + e1.getMessage()
364: + " (see logfiles for details)");
365: }
366: }
367: if (!hasErrors()) {
368: advanceState(EVENT_EXECUTE);
369: }
370: }
371:
372: /**
373: * Dumps the error messages to the log.
374: */
375: protected void dumpErrorMessages() {
376: List _errorMessages = getErrorMessages();
377: for (int i = 0; i < _errorMessages.size(); i++) {
378: getLogger().error(_errorMessages.get(i).toString());
379: }
380: }
381:
382: /**
383: * @see org.apache.lenya.cms.usecase.Usecase#checkPostconditions()
384: */
385: public void checkPostconditions() throws UsecaseException {
386: checkEvent(EVENT_CHECK_POSTCONDITIONS);
387: try {
388: clearErrorMessages();
389: clearInfoMessages();
390: doCheckPostconditions();
391: dumpErrorMessages();
392: } catch (Exception e) {
393: getLogger().error(e.getMessage(), e);
394: addErrorMessage(e.getMessage()
395: + " - Please consult the logfiles.");
396: if (getLogger().isDebugEnabled()) {
397: throw new UsecaseException(e);
398: }
399: }
400: if (!hasErrors()) {
401: advanceState(EVENT_CHECK_POSTCONDITIONS);
402: }
403: }
404:
405: /**
406: * Checks the post conditions.
407: * @throws Exception if an error occured.
408: */
409: protected void doCheckPostconditions() throws Exception {
410: // do nothing
411: }
412:
413: /**
414: * Executes the operation.
415: * @throws Exception when something went wrong.
416: */
417: protected void doExecute() throws Exception {
418: // do nothing
419: }
420:
421: private Map parameters = new HashMap();
422:
423: /**
424: * @see org.apache.lenya.cms.usecase.Usecase#setParameter(java.lang.String, java.lang.Object)
425: */
426: public void setParameter(String name, Object value) {
427: if (getLogger().isDebugEnabled()) {
428: getLogger().debug(
429: "Setting parameter [" + name + "] = [" + value
430: + "]");
431: }
432: this .parameters.put(name, value);
433: // set any exit parameters that are missing values
434: if (this .exitUsecaseParameters.containsKey(name)
435: && this .exitUsecaseParameters.get(name) == null) {
436: setExitParameter(name, value.toString());
437: }
438: }
439:
440: /**
441: * @see org.apache.lenya.cms.usecase.Usecase#getParameter(java.lang.String)
442: */
443: public Object getParameter(String name) {
444: if (!this .parameters.containsKey(name)) {
445: initializeParametersIfNotDone();
446: }
447: return this .parameters.get(name);
448: }
449:
450: /**
451: * @see org.apache.lenya.cms.usecase.Usecase#getParameter(java.lang.String, java.lang.Object)
452: */
453: public Object getParameter(String name, Object defaultValue) {
454: Object value = getParameter(name);
455: if (value == null) {
456: value = defaultValue;
457: }
458: return value;
459: }
460:
461: /**
462: * @see org.apache.lenya.cms.usecase.Usecase#getParameterAsString(java.lang.String)
463: */
464: public String getParameterAsString(String name) {
465: String valueString = null;
466: Object value = getParameter(name);
467: if (value != null) {
468: valueString = value.toString();
469: }
470: return valueString;
471: }
472:
473: /**
474: * Returns a parameter as string. If the parameter does not exist, a default value is returned.
475: * @param name The parameter name.
476: * @param defaultValue The default value.
477: * @return A string.
478: */
479: public String getParameterAsString(String name, String defaultValue) {
480: String valueString = defaultValue;
481: Object value = getParameter(name);
482: if (value != null) {
483: valueString = value.toString();
484: }
485: return valueString;
486: }
487:
488: /**
489: * Returns a parameter as integer. If the parameter does not exist, a default value is returned.
490: * @param name The parameter name.
491: * @param defaultValue The default value.
492: * @return An integer.
493: */
494: public int getParameterAsInteger(String name, int defaultValue) {
495: int valueInt = defaultValue;
496: Object value = getParameter(name);
497: if (value != null) {
498: valueInt = Integer.valueOf(value.toString()).intValue();
499: }
500: return valueInt;
501: }
502:
503: /**
504: * Returns a parameter as boolean. If the parameter does not exist, a default value is returned.
505: * @param name The parameter name.
506: * @param defaultValue The default value.
507: * @return A boolean value..
508: */
509: public boolean getParameterAsBoolean(String name,
510: boolean defaultValue) {
511: boolean valueBoolean = defaultValue;
512: Object value = getParameter(name);
513: if (value != null) {
514: if (value instanceof String) {
515: valueBoolean = Boolean.valueOf((String) value)
516: .booleanValue();
517: } else if (value instanceof Boolean) {
518: valueBoolean = ((Boolean) value).booleanValue();
519: } else {
520: throw new IllegalArgumentException(
521: "Cannot get boolean value of parameter ["
522: + name + "] (class "
523: + value.getClass().getName() + ")");
524: }
525: }
526: return valueBoolean;
527: }
528:
529: /**
530: * Return a map of all parameters
531: * @return the map
532: */
533: public Map getParameters() {
534: initializeParametersIfNotDone();
535: return Collections.unmodifiableMap(this .parameters);
536: }
537:
538: /**
539: * Returns one of the strings "true" or "false" depending on whether the corresponding checkbox
540: * was checked.
541: * @param name The parameter name.
542: * @return A string.
543: */
544: public String getBooleanCheckboxParameter(String name) {
545: String value = "false";
546: if (getParameter(name) != null
547: && getParameter(name).equals("on")) {
548: value = "true";
549: }
550: return value;
551: }
552:
553: private String EXIT_URI = "lenya.exitUri";
554: private String DEFAULT_TARGET_URL = "private.defaultTargetUrl";
555:
556: /**
557: * Sets the default target URL which should be used if no explicit target URL is set.
558: * @param url A URL string.
559: */
560: protected void setDefaultTargetURL(String url) {
561: setParameter(DEFAULT_TARGET_URL, url);
562: }
563:
564: /**
565: * If {@link #setDefaultTargetURL(String)}was not called, the source document (
566: * {@link #getSourceURL()}) is returned.
567: * @see org.apache.lenya.cms.usecase.Usecase#getTargetURL(boolean)
568: */
569: public String getTargetURL(boolean success) {
570: String url = getParameterAsString(EXIT_URI);
571: if (url == null) {
572: url = getParameterAsString(DEFAULT_TARGET_URL);
573: }
574: if (url == null) {
575: url = getSourceURL();
576: }
577: return url + getExitQueryString();
578: }
579:
580: /**
581: * @see org.apache.lenya.cms.usecase.Usecase#setPart(java.lang.String,
582: * org.apache.cocoon.servlet.multipart.Part)
583: */
584: public void setPart(String name, Part value) {
585: if (!Part.class.isInstance(value)) {
586: String className = "";
587: if (value != null) {
588: className = value.getClass().getName();
589: }
590: throw new RuntimeException(
591: "["
592: + name
593: + "] = ("
594: + className
595: + ") ["
596: + value
597: + "] is not a part object. Maybe you have to enable uploads?");
598: }
599: setParameter(name, value);
600: }
601:
602: /**
603: * @see org.apache.lenya.cms.usecase.Usecase#getPart(java.lang.String)
604: */
605: public Part getPart(String name) {
606: return (Part) getParameter(name);
607: }
608:
609: protected DocumentFactory getDocumentFactory() {
610: DocumentFactory factory = (DocumentFactory) getParameter(PARAMETER_FACTORY);
611: Session session = getSession();
612: if (factory == null || factory.getSession() != session) {
613: factory = DocumentUtil.createDocumentFactory(this .manager,
614: session);
615: setParameter(PARAMETER_FACTORY, factory);
616: }
617: return factory;
618: }
619:
620: /**
621: * @see org.apache.avalon.framework.activity.Initializable#initialize()
622: */
623: public final void initialize() throws Exception {
624: Request request = ContextHelper.getRequest(this .context);
625: Session session = RepositoryUtil.getSession(this .manager,
626: request);
627: setSession(session);
628: setParameter(PARAMETER_STATE_MACHINE, new StateMachine(MODEL));
629: }
630:
631: /**
632: * Does the actual initialization. Template method.
633: */
634: protected final void doInitialize() {
635: }
636:
637: /**
638: * @see org.apache.lenya.cms.usecase.Usecase#advance()
639: */
640: public void advance() throws UsecaseException {
641: // do nothing
642: }
643:
644: /**
645: * Deletes a parameter.
646: * @param name The parameter name.
647: */
648: protected void deleteParameter(String name) {
649: this .parameters.remove(name);
650: }
651:
652: private String name;
653:
654: /**
655: * @see org.apache.lenya.cms.usecase.Usecase#setName(java.lang.String)
656: */
657: public void setName(String name) {
658: this .name = name;
659: }
660:
661: /**
662: * @see org.apache.lenya.cms.usecase.Usecase#getName()
663: */
664: public String getName() {
665: return this .name;
666: }
667:
668: /**
669: * @see org.apache.lenya.cms.usecase.Usecase#getParameterNames()
670: */
671: public String[] getParameterNames() {
672: initializeParametersIfNotDone();
673: Set keys = this .parameters.keySet();
674: return (String[]) keys.toArray(new String[keys.size()]);
675: }
676:
677: protected void initializeParametersIfNotDone() {
678: if (this .parameters.get(PARAMETERS_INITIALIZED) == null) {
679: this .parameters.put(PARAMETERS_INITIALIZED, Boolean.TRUE);
680: initParameters();
681: }
682: }
683:
684: /**
685: * @see org.apache.lenya.cms.usecase.Usecase#setSourceURL(java.lang.String)
686: */
687: public void setSourceURL(String url) {
688: setParameter(SOURCE_URL, url);
689: }
690:
691: private UsecaseView view;
692:
693: /**
694: * @see org.apache.lenya.cms.usecase.Usecase#getView()
695: */
696: public UsecaseView getView() {
697: try {
698: prepareView();
699: } catch (Exception e) {
700: getLogger().error(
701: "View preparation for usecase [" + getName()
702: + "] failed: ", e);
703: addErrorMessage(e.getMessage());
704: }
705: return this .view;
706: }
707:
708: /**
709: * Override this method to prepare the view (add information messages etc.).
710: * @throws Exception If an error occurs.
711: */
712: protected void prepareView() throws Exception {
713: }
714:
715: protected static final String ELEMENT_PARAMETER = "parameter";
716: protected static final String ATTRIBUTE_NAME = "name";
717: protected static final String ATTRIBUTE_VALUE = "value";
718: protected static final String ELEMENT_VIEW = "view";
719: protected static final String ELEMENT_TRANSACTION = "transaction";
720: protected static final String ATTRIBUTE_POLICY = "policy";
721: protected static final String VALUE_OPTIMISTIC = "optimistic";
722: protected static final String VALUE_PESSIMISTIC = "pessimistic";
723: protected static final String ELEMENT_EXIT = "exit";
724: protected static final String ATTRIBUTE_USECASE = "usecase";
725:
726: private boolean isOptimistic = true;
727:
728: /**
729: * @return <code>true</code> if the transaction policy is optimistic offline lock,
730: * <code>false</code> if it is pessimistic offline lock.
731: */
732: public boolean isOptimistic() {
733: return this .isOptimistic;
734: }
735:
736: /**
737: * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
738: */
739: public void configure(Configuration config)
740: throws ConfigurationException {
741:
742: Configuration[] parameterConfigs = config
743: .getChildren(ELEMENT_PARAMETER);
744: for (int i = 0; i < parameterConfigs.length; i++) {
745: String name = parameterConfigs[i]
746: .getAttribute(ATTRIBUTE_NAME);
747: String value = parameterConfigs[i]
748: .getAttribute(ATTRIBUTE_VALUE);
749: setParameter(name, value);
750: }
751:
752: Configuration viewConfig = config.getChild(ELEMENT_VIEW, false);
753: if (viewConfig != null) {
754: this .view = new UsecaseView();
755: try {
756: view.service(this .manager);
757: } catch (ServiceException e) {
758: throw new ConfigurationException(
759: "Couldn't service view: ", e);
760: }
761: view.configure(viewConfig);
762: }
763:
764: Configuration transactionConfig = config.getChild(
765: ELEMENT_TRANSACTION, false);
766: if (transactionConfig != null) {
767: String policy = transactionConfig
768: .getAttribute(ATTRIBUTE_POLICY);
769: if (policy.equals(VALUE_PESSIMISTIC)) {
770: this .isOptimistic = false;
771: }
772: }
773:
774: Configuration exitConfig = config.getChild(ELEMENT_EXIT, false);
775: if (exitConfig != null) {
776: this .exitUsecaseName = exitConfig
777: .getAttribute(ATTRIBUTE_USECASE);
778: Configuration[] exitParameterConfigs = exitConfig
779: .getChildren(ELEMENT_PARAMETER);
780: for (int i = 0; i < exitParameterConfigs.length; i++) {
781: String name = exitParameterConfigs[i]
782: .getAttribute(ATTRIBUTE_NAME);
783: String value = null;
784: String[] attributeNames = exitParameterConfigs[i]
785: .getAttributeNames();
786: for (int j = 0; j < attributeNames.length; j++) {
787: if (attributeNames[j].equals(ATTRIBUTE_VALUE))
788: value = exitParameterConfigs[i]
789: .getAttribute(ATTRIBUTE_VALUE);
790: }
791: setExitParameter(name, value);
792: }
793: }
794: }
795:
796: /**
797: * @see org.apache.lenya.cms.usecase.Usecase#setView(org.apache.lenya.cms.usecase.UsecaseView)
798: */
799: public void setView(UsecaseView view) {
800: this .view = view;
801: }
802:
803: /**
804: * @return The objects that could be changed during the usecase.
805: * @throws UsecaseException if an error occurs.
806: */
807: protected Node[] getNodesToLock() throws UsecaseException {
808: return new Node[0];
809: }
810:
811: /**
812: * <p>
813: * This method starts the transaction and locks all involved objects immediately.
814: * This way, all changes to the objects in the session occur after the locking,
815: * avoiding overriding changes of other sessions.
816: * </p>
817: * <p>
818: * This method is locked via the class lock to avoid inter-usecase synchronization issues.
819: * </p>
820: * @see org.apache.lenya.cms.usecase.Usecase#lockInvolvedObjects()
821: */
822: public final void lockInvolvedObjects() throws UsecaseException {
823: try {
824: startTransaction();
825: } catch (RepositoryException e) {
826: throw new UsecaseException(e);
827: }
828: synchronized (TransactionLock.LOCK) {
829: lockInvolvedObjects(getNodesToLock());
830: }
831: advanceState("lockInvolvedObjects");
832: }
833:
834: /**
835: * Start a transaction by using a new, modifiable session.
836: * @throws RepositoryException if an error occurs.
837: */
838: protected void startTransaction() throws RepositoryException {
839: if (this .commitEnabled) {
840: setSession(RepositoryUtil.createSession(this .manager,
841: getSession().getIdentity(), true));
842: }
843: }
844:
845: /**
846: * <p>
847: * Lock the objects, for example when you need to change them (for example, delete). If you know
848: * when entering the usecase what these objects are, you do not need to call this, the framework
849: * will take of it if you implement getObjectsToLock(). If you do not know in advance what the
850: * objects are, you can call this method explicitly when appropriate.
851: * </p>
852: *
853: * @param objects the transactionable objects to lock
854: * @throws UsecaseException if an error occurs.
855: * @see #lockInvolvedObjects()
856: * @see #getNodesToLock()
857: */
858: public final void lockInvolvedObjects(Node[] objects)
859: throws UsecaseException {
860: try {
861: for (int i = 0; i < objects.length; i++) {
862: if (!objects[i].isLocked()) {
863: objects[i].lock();
864: }
865: if (!isOptimistic()
866: && !objects[i]
867: .isCheckedOutBySession(getSession())) {
868: objects[i].checkout(checkoutRestrictedToSession());
869: }
870: }
871: } catch (RepositoryException e) {
872: throw new UsecaseException(e);
873: }
874: }
875:
876: protected boolean canCheckOut(Node[] objects)
877: throws RepositoryException {
878: boolean canExecute = true;
879:
880: for (int i = 0; i < objects.length; i++) {
881: if (objects[i].isCheckedOut()
882: && !objects[i].isCheckedOutBySession(getSession())) {
883: if (getLogger().isDebugEnabled()) {
884: getLogger().debug(
885: "AbstractUsecase::lockInvolvedObjects() can not execute, object ["
886: + objects[i]
887: + "] is already checked out");
888: }
889: canExecute = false;
890: }
891: }
892: return canExecute;
893: }
894:
895: /**
896: * @see org.apache.lenya.cms.usecase.Usecase#cancel()
897: */
898: public void cancel() throws UsecaseException {
899: if (getSession().isModifiable()) {
900: try {
901: getSession().rollback();
902: } catch (Exception e) {
903: throw new UsecaseException(e);
904: }
905: }
906: }
907:
908: private String exitUsecaseName = null;
909: private Map exitUsecaseParameters = new HashMap();
910:
911: /**
912: * Sets a parameter to pass to the exit usecase.
913: * @param name The parameter name.
914: * @param value The parameter value.
915: */
916: protected void setExitParameter(String name, String value) {
917: this .exitUsecaseParameters.put(name, value);
918: }
919:
920: /**
921: * Returns the query string to access the exit usecase of this usecase.
922: * @return A query string of the form
923: * <code>?lenya.usecase=...&param1=foo&param2=bar</code>.
924: */
925: protected String getExitQueryString() {
926: StringBuffer queryBuffer = new StringBuffer();
927: if (this .exitUsecaseName != null) {
928: queryBuffer.append("?lenya.usecase=").append(
929: this .exitUsecaseName);
930: for (Iterator i = this .exitUsecaseParameters.keySet()
931: .iterator(); i.hasNext();) {
932: String key = (String) i.next();
933: String value = (String) this .exitUsecaseParameters
934: .get(key);
935: queryBuffer.append("&").append(key).append("=").append(
936: value);
937: }
938: } else {
939: String exitUsecase = getParameterAsString("lenya.exitUsecase");
940: if (exitUsecase != null && !"".equals(exitUsecase)) {
941: queryBuffer.append("?lenya.usecase=").append(
942: exitUsecase);
943: }
944: }
945: return queryBuffer.toString();
946: }
947:
948: public Session getSession() {
949: return (Session) getParameter(PARAMETER_SESSION);
950: }
951:
952: protected Context context;
953:
954: /**
955: * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
956: */
957: public void contextualize(Context context) throws ContextException {
958: this .context = context;
959: }
960:
961: protected ServiceManager manager;
962:
963: public void service(ServiceManager manager) throws ServiceException {
964: this .manager = manager;
965: }
966:
967: protected void setSession(
968: org.apache.lenya.cms.repository.Session session) {
969: setParameter(PARAMETER_SESSION, session);
970: }
971:
972: private boolean commitEnabled = true;
973:
974: public void setTestSession(Session session) {
975: this .commitEnabled = false;
976: setSession(session);
977: }
978:
979: protected boolean checkoutRestrictedToSession() {
980: return getParameterAsBoolean(
981: PARAMETER_CHECKOUT_RESTRICTED_TO_SESSION, true);
982: }
983:
984: }
|