| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.sling.event.impl.jobs; |
| |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Dictionary; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import org.apache.sling.api.resource.PersistenceException; |
| import org.apache.sling.api.resource.Resource; |
| import org.apache.sling.api.resource.ValueMap; |
| import org.apache.sling.event.impl.support.ResourceHelper; |
| import org.apache.sling.event.jobs.Job; |
| import org.apache.sling.event.jobs.consumer.JobConsumer; |
| import org.osgi.service.event.Event; |
| import org.slf4j.Logger; |
| |
| public abstract class Utility { |
| |
| /** SLING-9906 : silence read errors after this many logs in this VM */ |
| private static final int READ_ERROR_SILENCE_LIMIT = 1000; |
| |
| /** SLING-9906 : count read errors to be able to silence */ |
| private static final AtomicInteger readErrorWarnCount = new AtomicInteger(); |
| |
| /** |
| * Check if the job topic is a valid OSGI event name (see 113.3.1 of the OSGI spec) |
| * @return <code>null</code> if the topic is syntactically correct otherwise an error description is returned |
| */ |
| public static String checkJobTopic(final Object jobTopic) { |
| String message = null; |
| if ( jobTopic != null ) { |
| if ( jobTopic instanceof String ) { |
| try { |
| new Event((String)jobTopic, (Dictionary<String, Object>)null); |
| } catch (final IllegalArgumentException iae) { |
| message = String.format("Discarding job - job has an illegal job topic '%s'",jobTopic); |
| } |
| |
| } else { |
| message = "Discarding job - job topic is not of type string"; |
| } |
| } else { |
| message = "Discarding job - job topic is missing"; |
| } |
| return message; |
| } |
| |
| /** |
| * Check the job. |
| * @return <code>null</code> if the topic topic is correct and all properties are serializable, |
| * otherwise an error description is returned |
| */ |
| public static String checkJob(final Object jobTopic, final Map<String, Object> properties) { |
| final String msg = checkJobTopic(jobTopic); |
| if ( msg == null ) { |
| if ( properties != null ) { |
| for(final Object val : properties.values()) { |
| if ( val != null && !(val instanceof Serializable) ) { |
| return "Discarding job - properties must be serializable: " + jobTopic + " : " + properties; |
| } |
| } |
| } |
| } |
| return msg; |
| } |
| |
| /** |
| * Create an event from a job |
| * @param job The job |
| * @return New event object. |
| */ |
| public static Event toEvent(final Job job) { |
| final Map<String, Object> eventProps = new HashMap<>(); |
| eventProps.putAll(((JobImpl)job).getProperties()); |
| eventProps.put(ResourceHelper.PROPERTY_JOB_ID, job.getId()); |
| eventProps.remove(JobConsumer.PROPERTY_JOB_ASYNC_HANDLER); |
| return new Event(job.getTopic(), eventProps); |
| } |
| |
| /** |
| * Append properties to the string builder |
| */ |
| private static void appendProperties(final StringBuilder sb, |
| final Map<String, Object> properties) { |
| if ( properties != null ) { |
| sb.append(", properties="); |
| boolean first = true; |
| for(final String propName : properties.keySet()) { |
| if ( propName.equals(ResourceHelper.PROPERTY_JOB_ID) |
| || propName.equals(ResourceHelper.PROPERTY_JOB_TOPIC) ) { |
| continue; |
| } |
| if ( first ) { |
| first = false; |
| } else { |
| sb.append(","); |
| } |
| sb.append(propName); |
| sb.append('='); |
| final Object value = properties.get(propName); |
| // the toString() method of Calendar is very verbose |
| // therefore we do a toString for these objects based |
| // on a date |
| if ( value instanceof Calendar ) { |
| sb.append(value.getClass().getName()); |
| sb.append('('); |
| sb.append(((Calendar)value).getTime()); |
| sb.append(')'); |
| } else { |
| sb.append(value); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Improved toString method for a job. |
| * This method prints out the job topic and all of the properties. |
| */ |
| public static String toString(final String jobTopic, |
| final Map<String, Object> properties) { |
| final StringBuilder sb = new StringBuilder("Sling Job "); |
| sb.append("[topic="); |
| sb.append(jobTopic); |
| appendProperties(sb, properties); |
| |
| sb.append("]"); |
| return sb.toString(); |
| } |
| |
| /** |
| * Improved toString method for a job. |
| * This method prints out the job topic and all of the properties. |
| */ |
| public static String toString(final Job job) { |
| if ( job != null ) { |
| final StringBuilder sb = new StringBuilder("Sling Job "); |
| sb.append("[topic="); |
| sb.append(job.getTopic()); |
| sb.append(", id="); |
| sb.append(job.getId()); |
| appendProperties(sb, ((JobImpl)job).getProperties()); |
| sb.append("]"); |
| return sb.toString(); |
| } |
| return "<null>"; |
| } |
| |
| /** |
| * Read a job |
| */ |
| public static JobImpl readJob(final Logger logger, final Resource resource) { |
| JobImpl job = null; |
| if ( resource != null ) { |
| try { |
| final ValueMap vm = ResourceHelper.getValueMap(resource); |
| |
| // check job topic and job id |
| final String errorMessage = Utility.checkJobTopic(vm.get(ResourceHelper.PROPERTY_JOB_TOPIC)); |
| final String jobId = vm.get(ResourceHelper.PROPERTY_JOB_ID, String.class); |
| if ( errorMessage == null && jobId != null ) { |
| final String topic = vm.get(ResourceHelper.PROPERTY_JOB_TOPIC, String.class); |
| final Map<String, Object> jobProperties = ResourceHelper.cloneValueMap(vm); |
| |
| jobProperties.put(JobImpl.PROPERTY_RESOURCE_PATH, resource.getPath()); |
| // convert to integers (JCR supports only long...) |
| jobProperties.put(Job.PROPERTY_JOB_RETRIES, vm.get(Job.PROPERTY_JOB_RETRIES, Integer.class)); |
| jobProperties.put(Job.PROPERTY_JOB_RETRY_COUNT, vm.get(Job.PROPERTY_JOB_RETRY_COUNT, Integer.class)); |
| if ( vm.get(Job.PROPERTY_JOB_PROGRESS_STEPS) != null ) { |
| jobProperties.put(Job.PROPERTY_JOB_PROGRESS_STEPS, vm.get(Job.PROPERTY_JOB_PROGRESS_STEPS, Integer.class)); |
| } |
| if ( vm.get(Job.PROPERTY_JOB_PROGRESS_STEP) != null ) { |
| jobProperties.put(Job.PROPERTY_JOB_PROGRESS_STEP, vm.get(Job.PROPERTY_JOB_PROGRESS_STEP, Integer.class)); |
| } |
| @SuppressWarnings("unchecked") |
| final List<Exception> readErrorList = (List<Exception>) jobProperties.get(ResourceHelper.PROPERTY_MARKER_READ_ERROR_LIST); |
| if ( readErrorList != null ) { |
| for(final Exception e : readErrorList) { |
| final int c = readErrorWarnCount.getAndIncrement(); |
| final String prefix; |
| if ( c == READ_ERROR_SILENCE_LIMIT ) { |
| logger.warn("Too many 'Unable to read job from ' messages - silencing 99% of them from now on."); |
| continue; |
| } else if ( c > READ_ERROR_SILENCE_LIMIT && c % 100 != 0 ) { |
| // SLING-9906 : then silence the log altogether |
| continue; |
| } else if ( c > READ_ERROR_SILENCE_LIMIT ) { |
| prefix = "[unsilenced] "; |
| } else { |
| prefix = ""; |
| } |
| if ( e.getCause() != null && e.getCause() instanceof ClassNotFoundException ) { |
| // SLING-9906 : suppress exception in ClassNotFoundException case |
| // as this can happen many times in case of a not deployed class. |
| logger.warn(prefix + "Unable to read job from " + resource.getPath() + ", exception: " + e + ", cause: " + e.getCause()); |
| } else { |
| logger.warn(prefix + "Unable to read job from " + resource.getPath(), e); |
| } |
| } |
| } |
| job = new JobImpl(topic, |
| jobId, |
| jobProperties); |
| } else { |
| if ( errorMessage != null ) { |
| logger.warn("{} : {}", errorMessage, resource.getPath()); |
| } else if ( jobId == null ) { |
| logger.warn("Discarding job - no job id found : {}", resource.getPath()); |
| } |
| // remove the job as the topic is invalid anyway |
| try { |
| resource.getResourceResolver().delete(resource); |
| resource.getResourceResolver().commit(); |
| } catch ( final PersistenceException ignore) { |
| logger.debug("Unable to remove job resource.", ignore); |
| } |
| } |
| } catch (final InstantiationException ie) { |
| // something happened with the resource in the meantime |
| logger.debug("Unable to instantiate resource.", ie); |
| } catch (final RuntimeException re) { |
| logger.debug("Unable to read resource.", re); |
| } |
| |
| } |
| return job; |
| } |
| |
| private static final Comparator<Resource> RESOURCE_COMPARATOR = new Comparator<Resource>() { |
| |
| @Override |
| public int compare(final Resource o1, final Resource o2) { |
| Integer value1 = null; |
| try { |
| value1 = Integer.valueOf(o1.getName()); |
| } catch ( final NumberFormatException nfe) { |
| // ignore |
| } |
| Integer value2 = null; |
| try { |
| value2 = Integer.valueOf(o2.getName()); |
| } catch ( final NumberFormatException nfe) { |
| // ignore |
| } |
| if ( value1 != null && value2 != null ) { |
| return value1.compareTo(value2); |
| } |
| return o1.getName().compareTo(o2.getName()); |
| } |
| }; |
| |
| /** |
| * Helper method to read all children of a resource and sort them by name |
| * @param type The type of resources (for debugging) |
| * @param rsrc The parent resource |
| * @return Sorted list of children. |
| */ |
| public static List<Resource> getSortedChildren(final Logger logger, final String type, final Resource rsrc) { |
| final List<Resource> children = new ArrayList<>(); |
| final Iterator<Resource> monthIter = rsrc.listChildren(); |
| while ( monthIter.hasNext() ) { |
| final Resource monthResource = monthIter.next(); |
| children.add(monthResource); |
| logger.debug("Found {} : {}", type, monthResource.getName()); |
| } |
| Collections.sort(children, RESOURCE_COMPARATOR); |
| return children; |
| } |
| } |