/* * JBoss, Home of Professional Open Source. * Copyright 2014, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.ejb3.timerservice.persistence.filestore; import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.CALENDAR_TIMER; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.DECLARING_CLASS; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.INFO; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.INITIAL_DATE; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.NAME; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.NEXT_DATE; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.PARAMETER; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.PREVIOUS_RUN; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.PRIMARY_KEY; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.REPEAT_INTERVAL; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.SCHEDULE_EXPR_DAY_OF_MONTH; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.SCHEDULE_EXPR_DAY_OF_WEEK; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.SCHEDULE_EXPR_END_DATE; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.SCHEDULE_EXPR_HOUR; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.SCHEDULE_EXPR_MINUTE; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.SCHEDULE_EXPR_MONTH; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.SCHEDULE_EXPR_SECOND; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.SCHEDULE_EXPR_START_DATE; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.SCHEDULE_EXPR_TIMEZONE; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.SCHEDULE_EXPR_YEAR; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.TIMED_OBJECT_ID; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.TIMEOUT_METHOD; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.TIMER; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.TIMER_ID; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.TIMER_STATE; import static org.jboss.as.ejb3.timerservice.persistence.filestore.EjbTimerXmlPersister.TYPE; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.xml.stream.XMLStreamException; import org.jboss.as.controller.parsing.ParseUtils; import org.jboss.as.ejb3.logging.EjbLogger; import org.jboss.as.ejb3.timerservice.CalendarTimer; import org.jboss.as.ejb3.timerservice.TimerImpl; import org.jboss.as.ejb3.timerservice.TimerServiceImpl; import org.jboss.as.ejb3.timerservice.TimerState; import org.jboss.as.ejb3.timerservice.persistence.TimeoutMethod; import org.jboss.marshalling.ByteBufferInput; import org.jboss.marshalling.MarshallerFactory; import org.jboss.marshalling.MarshallingConfiguration; import org.jboss.marshalling.Unmarshaller; import org.jboss.staxmapper.XMLElementReader; import org.jboss.staxmapper.XMLExtendedStreamReader; /** * Parser for persistent EJB timers that are stored in XML. * * @author Stuart Douglas */ public class EjbTimerXmlParser_1_0 implements XMLElementReader<List<TimerImpl>> { public static final String NAMESPACE = "urn:jboss:wildfly:ejb-timers:1.0"; private final TimerServiceImpl timerService; private final MarshallerFactory factory; private final MarshallingConfiguration configuration; private final ClassLoader classLoader; public EjbTimerXmlParser_1_0(TimerServiceImpl timerService, MarshallerFactory factory, MarshallingConfiguration configuration, ClassLoader classLoader) { this.timerService = timerService; this.factory = factory; this.configuration = configuration; this.classLoader = classLoader; } @Override public void readElement(XMLExtendedStreamReader reader, List<TimerImpl> timers) throws XMLStreamException { while (reader.hasNext()) { switch (reader.nextTag()) { case END_ELEMENT: { return; } case START_ELEMENT: { switch (reader.getName().getLocalPart()) { case TIMER: this.parseTimer(reader, timers); break; case CALENDAR_TIMER: this.parseCalendarTimer(reader, timers); break; default: throw ParseUtils.unexpectedElement(reader); } break; } } } } private void parseTimer(XMLExtendedStreamReader reader, List<TimerImpl> timers) throws XMLStreamException { LoadableElements loadableElements = new LoadableElements(); TimerImpl.Builder builder = TimerImpl.builder(); builder.setPersistent(true); final Set<String> required = new HashSet<>(Arrays.asList(new String[]{TIMED_OBJECT_ID, TIMER_ID, INITIAL_DATE, REPEAT_INTERVAL, TIMER_STATE})); for (int i = 0; i < reader.getAttributeCount(); ++i) { String attr = reader.getAttributeValue(i); String attrName = reader.getAttributeLocalName(i); required.remove(attrName); boolean handled = handleCommonAttributes(builder, reader, i); if (!handled) { switch (attrName) { case REPEAT_INTERVAL: builder.setRepeatInterval(Long.parseLong(attr)); break; default: throw ParseUtils.unexpectedAttribute(reader, i); } } } if (!required.isEmpty()) { throw ParseUtils.missingRequired(reader, required); } while (reader.hasNext()) { switch (reader.nextTag()) { case END_ELEMENT: { try { if (loadableElements.info != null) { builder.setInfo((Serializable) deserialize(loadableElements.info)); } if (loadableElements.primaryKey != null) { builder.setPrimaryKey(deserialize(loadableElements.primaryKey)); } timers.add(builder.build(timerService)); } catch (Exception e) { EjbLogger.EJB3_TIMER_LOGGER.timerReinstatementFailed(builder.getTimedObjectId(), builder.getId(), e); } return; } case START_ELEMENT: { boolean handled = handleCommonElements(reader, loadableElements); if (!handled) { throw ParseUtils.unexpectedElement(reader); } break; } } } } private Object deserialize(final String info) throws IOException, ClassNotFoundException { byte[] data = Base64.getDecoder().decode(info.trim()); Unmarshaller unmarshaller = factory.createUnmarshaller(configuration); unmarshaller.start(new ByteBufferInput(ByteBuffer.wrap(data))); try { return unmarshaller.readObject(); } finally { unmarshaller.close(); } } private boolean handleCommonElements(XMLExtendedStreamReader reader, LoadableElements builder) throws XMLStreamException { boolean handled = false; switch (reader.getName().getLocalPart()) { case INFO: { builder.info = reader.getElementText(); handled = true; break; } case PRIMARY_KEY: { builder.primaryKey = reader.getElementText(); handled = true; break; } } return handled; } private void parseCalendarTimer(XMLExtendedStreamReader reader, List<TimerImpl> timers) throws XMLStreamException { LoadableElements loadableElements = new LoadableElements(); CalendarTimer.Builder builder = CalendarTimer.builder(); builder.setAutoTimer(false).setPersistent(true); final Set<String> required = new HashSet<>(Arrays.asList(new String[]{ TIMED_OBJECT_ID, TIMER_ID, TIMER_STATE, SCHEDULE_EXPR_SECOND, SCHEDULE_EXPR_MINUTE, SCHEDULE_EXPR_HOUR, SCHEDULE_EXPR_DAY_OF_WEEK, SCHEDULE_EXPR_DAY_OF_MONTH, SCHEDULE_EXPR_MONTH, SCHEDULE_EXPR_YEAR})); for (int i = 0; i < reader.getAttributeCount(); ++i) { String attr = reader.getAttributeValue(i); String attrName = reader.getAttributeLocalName(i); required.remove(attrName); boolean handled = handleCommonAttributes(builder, reader, i); if (!handled) { switch (attrName) { case SCHEDULE_EXPR_SECOND: builder.setScheduleExprSecond(attr); break; case SCHEDULE_EXPR_MINUTE: builder.setScheduleExprMinute(attr); break; case SCHEDULE_EXPR_HOUR: builder.setScheduleExprHour(attr); break; case SCHEDULE_EXPR_DAY_OF_WEEK: builder.setScheduleExprDayOfWeek(attr); break; case SCHEDULE_EXPR_DAY_OF_MONTH: builder.setScheduleExprDayOfMonth(attr); break; case SCHEDULE_EXPR_MONTH: builder.setScheduleExprMonth(attr); break; case SCHEDULE_EXPR_YEAR: builder.setScheduleExprYear(attr); break; case SCHEDULE_EXPR_START_DATE: builder.setScheduleExprStartDate(new Date(Long.parseLong(attr))); break; case SCHEDULE_EXPR_END_DATE: builder.setScheduleExprEndDate(new Date(Long.parseLong(attr))); break; case SCHEDULE_EXPR_TIMEZONE: builder.setScheduleExprTimezone(attr); break; default: throw ParseUtils.unexpectedAttribute(reader, i); } } } if (!required.isEmpty()) { throw ParseUtils.missingRequired(reader, required); } while (reader.hasNext()) { switch (reader.nextTag()) { case END_ELEMENT: { try { if (loadableElements.info != null) { builder.setInfo((Serializable) deserialize(loadableElements.info)); } if (loadableElements.primaryKey != null) { builder.setPrimaryKey(deserialize(loadableElements.primaryKey)); } if (loadableElements.methodName != null) { Method timeoutMethod = CalendarTimer.getTimeoutMethod(new TimeoutMethod(loadableElements.className, loadableElements.methodName, loadableElements.params.toArray(new String[loadableElements.params.size()])), classLoader); if(timeoutMethod != null) { builder.setTimeoutMethod(timeoutMethod); timers.add(builder.build(timerService)); } else { builder.setId("deleted-timer"); timers.add(builder.build(timerService)); EjbLogger.EJB3_TIMER_LOGGER.timerReinstatementFailed(builder.getTimedObjectId(), builder.getId(), null); } } else { timers.add(builder.build(timerService)); } } catch (Exception e) { EjbLogger.EJB3_TIMER_LOGGER.timerReinstatementFailed(builder.getTimedObjectId(), builder.getId(), e); } return; } case START_ELEMENT: { boolean handled = handleCommonElements(reader, loadableElements); if (!handled) { switch (reader.getName().getLocalPart()) { case TIMEOUT_METHOD: { builder.setAutoTimer(true); parseTimeoutMethod(reader, loadableElements); break; } default: throw ParseUtils.unexpectedElement(reader); } } } } } } private void parseTimeoutMethod(XMLExtendedStreamReader reader, LoadableElements loadableElements) throws XMLStreamException { final Set<String> required = new HashSet<>(Arrays.asList(new String[]{DECLARING_CLASS, NAME})); for (int i = 0; i < reader.getAttributeCount(); ++i) { String attr = reader.getAttributeValue(i); String attrName = reader.getAttributeLocalName(i); required.remove(attrName); switch (attrName) { case DECLARING_CLASS: loadableElements.className = attr; break; case NAME: loadableElements.methodName = attr; break; default: throw ParseUtils.unexpectedAttribute(reader, i); } } if (!required.isEmpty()) { throw ParseUtils.missingRequired(reader, required); } while (reader.hasNext()) { switch (reader.nextTag()) { case END_ELEMENT: { return; } case START_ELEMENT: { switch (reader.getName().getLocalPart()) { case PARAMETER: { handleParam(reader, loadableElements); break; } default: throw ParseUtils.unexpectedElement(reader); } } } } } private void handleParam(XMLExtendedStreamReader reader, LoadableElements loadableElements) throws XMLStreamException { final Set<String> required = new HashSet<>(Arrays.asList(new String[]{TYPE})); for (int i = 0; i < reader.getAttributeCount(); ++i) { String attr = reader.getAttributeValue(i); String attrName = reader.getAttributeLocalName(i); required.remove(attrName); switch (attrName) { case TYPE: loadableElements.params.add(attr); break; default: throw ParseUtils.unexpectedAttribute(reader, i); } } if (!required.isEmpty()) { throw ParseUtils.missingRequired(reader, required); } while (reader.hasNext()) { switch (reader.nextTag()) { case END_ELEMENT: { return; } case START_ELEMENT: { throw ParseUtils.unexpectedElement(reader); } } } } private boolean handleCommonAttributes(TimerImpl.Builder builder, XMLExtendedStreamReader reader, int i) { boolean handled = true; String attr = reader.getAttributeValue(i); switch (reader.getAttributeLocalName(i)) { case TIMED_OBJECT_ID: builder.setTimedObjectId(attr); break; case TIMER_ID: builder.setId(attr); break; case INITIAL_DATE: builder.setInitialDate(new Date(Long.parseLong(attr))); break; case NEXT_DATE: builder.setNextDate(new Date(Long.parseLong(attr))); break; case TIMER_STATE: builder.setTimerState(TimerState.valueOf(attr)); break; case PREVIOUS_RUN: builder.setPreviousRun(new Date(Long.parseLong(attr))); break; default: handled = false; } return handled; } private static class LoadableElements { String info; String primaryKey; String className; String methodName; final List<String> params = new ArrayList<>(); } }