package org.nightlabs.jfire.scripting.print.ui.transfer.delivery;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.nightlabs.base.ui.wizard.IWizardHopPage;
import org.nightlabs.jdo.ObjectID;
import org.nightlabs.jfire.base.jdo.cache.Cache;
import org.nightlabs.jfire.scripting.editor2d.ScriptRootDrawComponent;
import org.nightlabs.jfire.scripting.editor2d.util.VisibleScriptUtil;
import org.nightlabs.jfire.scripting.id.ScriptRegistryItemID;
import org.nightlabs.jfire.scripting.ui.ScriptingPlugin;
import org.nightlabs.jfire.store.deliver.Delivery;
import org.nightlabs.jfire.store.deliver.DeliveryData;
import org.nightlabs.jfire.store.deliver.DeliveryException;
import org.nightlabs.jfire.store.deliver.DeliveryResult;
import org.nightlabs.jfire.store.id.ProductID;
import org.nightlabs.jfire.trade.id.ArticleID;
import org.nightlabs.jfire.trade.ui.transfer.deliver.AbstractClientDeliveryProcessor;
/**
* @author Daniel.Mazurek [at] NightLabs [dot] de
*
*/
public abstract class AbstractClientDeliveryProcessorPrint
extends AbstractClientDeliveryProcessor
{
private static final Logger logger = Logger.getLogger(AbstractClientDeliveryProcessorPrint.class);
// private TicketDataProviderThread ticketDataProviderThread;
private AbstractScriptDataProviderThread abstractScriptDataProviderThread;
protected abstract AbstractScriptDataProviderThread createScriptDataProviderThread(
AbstractClientDeliveryProcessorPrint clientDeliveryProcessor);
/**
* If you inherit this class and override this method, you <b>must</b> call
* <code>super.init();</code> in your implementation as first statement!!!
*/
@Override
public void init() {
// currently nothing to do, but this might change!
// if the delivery-direction is incoming (e.g. for reversed articles), there's no need to do anything
noopAll = !Delivery.DELIVERY_DIRECTION_OUTGOING.equals(getDelivery().getDeliveryDirection());
}
/**
* It does not make sense to print tickets when we receive sth.. This can only be the case for reversed articles
* and hence, we simply ignore everything silently. This flag is initialised in {@link #init()}.
*/
private boolean noopAll = false;
private long totalTimeMeasure = 0;
/**
* When subclassing this class, you should <b>not</b> override this method
* but implement {@link #printBegin()} instead!
*/
public DeliveryResult deliverBegin() throws DeliveryException {
DeliveryProcessorPrintDebugInfo.beginMeasure();
totalTimeMeasure = System.currentTimeMillis();
if (noopAll)
return null;
if (abstractScriptDataProviderThread != null)
throw new IllegalStateException("this.ticketDataProviderThread is not null!!!"); //$NON-NLS-1$
DeliveryResult result;
// start preparing the whole document asynchronously (it will be joined in
// deliverDoWork()
// Marco: This code does not work here anymore, because some of the relevant data we need
// is generated in the beginServer phase of the delivery process. Therefore, we must
// start the data provider thread in the doWork phase below. 2007-04-02
// abstractScriptDataProviderThread = createScriptDataProviderThread(this);
// boolean interruptDataProvider = true;
// try {
// abstractScriptDataProviderThread.start();
//
// result = printBegin();
//
// interruptDataProvider = false;
// } finally {
// if (interruptDataProvider) {
// // ticketDataProviderThread.interrupt(); // TODO reactivate this line
// // once everything is working
// abstractScriptDataProviderThread = null;
// }
// }
result = printBegin();
return result;
}
protected abstract DeliveryResult printBegin() throws DeliveryException;
/**
* When subclassing this class, you should not override this method but
* implement {@link #printDocuments(List, boolean)} instead.
*/
public DeliveryResult deliverDoWork() throws DeliveryException {
if (noopAll)
return null;
long start = 0;
if (logger.isDebugEnabled()) {
start = System.currentTimeMillis();
logger.debug("deliverDoWork Begin!"); //$NON-NLS-1$
}
// start the data provider thread here, after the necessary data (e.g. VoucherKey) has
// been created in the beginServer phase.
abstractScriptDataProviderThread = createScriptDataProviderThread(this);
abstractScriptDataProviderThread.start();
// print all the tickets
if (getDelivery().isFailed() || getDelivery().isForceRollback())
throw new DeliveryException(new DeliveryResult(
DeliveryResult.CODE_FAILED,
"Delivery failed or rollback-forced. Print aborted! failed=" //$NON-NLS-1$
+ getDelivery().isFailed() + " forceRollback=" //$NON-NLS-1$
+ getDelivery().isForceRollback(), null));
if (abstractScriptDataProviderThread == null)
throw new IllegalStateException(
"this.scriptDataProviderThread is null!!!"); //$NON-NLS-1$
Throwable error = abstractScriptDataProviderThread.getError();
if (error != null)
throw new DeliveryException(new DeliveryResult(abstractScriptDataProviderThread.getError()));
try {
List<ScriptRootDrawComponent> ticketDrawComponents = new LinkedList<ScriptRootDrawComponent>();
int preferredDocumentCount = getPreferredDocumentCount();
boolean waitUntilPreferredCountFetched = preferredDocumentCount > 0;
while (true) {
if (abstractScriptDataProviderThread.getError() != null)
throw abstractScriptDataProviderThread.getError();
ArticleID articleID = abstractScriptDataProviderThread.fetchReadyArticleID();
if (articleID != null) {
ProductID productID = abstractScriptDataProviderThread.getProductID(articleID, true);
// File ticketLayoutFile = abstractScriptDataProviderThread.getLayoutFileByProductID(productID);
// ScriptRootDrawComponent ticketDrawComponent = getScriptRootDrawComponent(ticketLayoutFile);
ScriptRootDrawComponent ticketDrawComponent = getScriptRootDrawComponent(productID);
Map<ScriptRegistryItemID, Object> scriptResultMap = abstractScriptDataProviderThread.getScriptResultMap(productID, true);
// inject the data into the TicketDrawComponent
// and evaluate all local scripts
ticketDrawComponent.assignScriptResults(scriptResultMap);
// evaluate visible scripts
VisibleScriptUtil.assignVisibleScriptResults(ticketDrawComponent, scriptResultMap);
ticketDrawComponents.add(ticketDrawComponent);
}
if (waitUntilPreferredCountFetched) {
// If should wait until we have the preferred count or nothing more in the queue
if (articleID == null || ticketDrawComponents.size() >= preferredDocumentCount) {
// print the preferred count of documents or all left
if (ticketDrawComponents.size() > 0)
printDocuments(ticketDrawComponents, articleID == null);
ticketDrawComponents.clear();
}
}
else {
if (articleID == null
|| ticketDrawComponents.size() >= 5 // if we already have a few, we already start printing - even though we might still fetch more from the data-provider-thread. this way, the printing system can already start processing the job. This number might be changed later for performance tuning! Maybe we even make it configurable. Marco.
|| !abstractScriptDataProviderThread.canInvokeFetchReadyArticleIDWithoutBlocking()
)
{
printDocuments(ticketDrawComponents, articleID == null);
// imho we should remove all from this list now - otherwise it would
// be printed multiple times...
ticketDrawComponents.clear();
}
}
if (articleID == null)
break;
}
} catch (Throwable e) {
logger.error("deliverDoWork failed!", e); //$NON-NLS-1$
throw new DeliveryException(new DeliveryResult(e));
}
if (logger.isDebugEnabled()) {
long duration = System.currentTimeMillis() - start;
logger.debug("deliverDoWork took " + duration + " ms!"); //$NON-NLS-1$ //$NON-NLS-2$
}
return null; // null means OK (no special ok, but ok ;-)
}
/**
* This method may be called multiple times (between printBegin and printEnd),
* because the data retrieval and the printing are done asynchronously in
* parallel.
*
* @param ticketDrawComponents
* @param lastEntry
* determines if this is the last call for this method before
* deliverEnd
* @throws DeliveryException
*/
protected abstract void printDocuments(
List<ScriptRootDrawComponent> ticketDrawComponents,
boolean lastEntry) throws DeliveryException;
/**
* When subclassing this class, you should <b>not</b> override this method
* but implement {@link #printEnd()} instead!
*/
public DeliveryResult deliverEnd() throws DeliveryException {
if (noopAll)
return null;
DeliveryResult printEndResult = printEnd();
DeliveryProcessorPrintDebugInfo.addTime(
DeliveryProcessorPrintDebugInfo.CAT_TOTAL_TIME,
System.currentTimeMillis() - totalTimeMeasure
);
DeliveryProcessorPrintDebugInfo.print();
return printEndResult;
}
protected abstract DeliveryResult printEnd() throws DeliveryException;
public DeliveryData getDeliveryData() {
return null;
}
/**
* @return The implementation in
* <code>AbstractClientDeliveryProcessorTicketPrint</code> returns
* <code>null</code> as there is no wizard page needed.
*/
public IWizardHopPage createDeliveryWizardPage() {
return null;
}
private static final String SCOPE_SCRIPT_ROOT_DRAWCOMPONENT_BY_LAYOUT = ScriptingPlugin.class.getPackage().getName() + ".ScriptRootDrawComponents";
private static final String[] SCRIPT_ROOT_DRAWCOMPONENT_FETCH_GROUPS = new String[] {};
private ScriptRootDrawComponent getScriptRootDrawComponent(ProductID productID) {
long start = System.currentTimeMillis();
ObjectID layoutID = abstractScriptDataProviderThread.getLayoutByProductID(productID);
// Try to get the object from the cache.
ScriptRootDrawComponent rootDrawComponent = (ScriptRootDrawComponent) Cache.sharedInstance().get(
SCOPE_SCRIPT_ROOT_DRAWCOMPONENT_BY_LAYOUT,
layoutID,
SCRIPT_ROOT_DRAWCOMPONENT_FETCH_GROUPS, -1);
// If it was not in the cache, we load it from the file.
if (rootDrawComponent == null) {
File ticketLayoutFile = abstractScriptDataProviderThread.getLayoutFileByProductID(productID);
rootDrawComponent = getScriptRootDrawComponent(ticketLayoutFile);
Cache.sharedInstance().put(
SCOPE_SCRIPT_ROOT_DRAWCOMPONENT_BY_LAYOUT,
layoutID,
rootDrawComponent,
SCRIPT_ROOT_DRAWCOMPONENT_FETCH_GROUPS, -1);
}
// Cloning, because data is copied into the object during print.
// Using the same instance would cause problems, even though the data
// is overwritten every time and no structural changes happen.
// This is because certain print methods (e.g. printing on DIN-A4 with a
// TicketCarrier) collect multiple Tickets and print them at once. If
// we didn't clone, the data of one ticket would be overwritten before
// the printing happens and multiple tickets with the same contents would
// occur on the paper.
// Hence, it's essential to clone here! Marco.
// rootDrawComponent = Util.cloneSerializable(rootDrawComponent);
// Using cloneSerializable(...) is slightly slower and DrawComponent.clone() works fine again.
// Besides that, cloneSerializable(...) currently causes all images to disappear (because they're marked to be transient).
rootDrawComponent = (ScriptRootDrawComponent) rootDrawComponent.clone();
DeliveryProcessorPrintDebugInfo.addTime(
DeliveryProcessorPrintDebugInfo.CAT_PROCESS_DATA_PARSE_LAYOUT_FILE, System.currentTimeMillis() - start);
return rootDrawComponent;
}
protected abstract ScriptRootDrawComponent getScriptRootDrawComponent(File file);
/**
* This method returns the preferred count of documents that should
* be passed to {@link #printDocuments(List, boolean)} at once.
* <p>
* If this returns a value greater than 0 the loop fetching the document data
* will wait until the preferred count of documents could be fetched or no
* more documents are in the queue.
* <p>
* The implementation in {@link AbstractClientDeliveryProcessorPrint} returns <code>0</code>.
*
* @return The preferred count of documents that should be passed to {@link #printDocuments(List, boolean)}
*/
protected int getPreferredDocumentCount() {
return 0;
}
}