/** * Copyright Microsoft Corporation * * Licensed 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 com.microsoft.azure.storage.analytics; import java.net.URISyntaxException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.EnumSet; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.TimeZone; import com.microsoft.azure.storage.LoggingOperations; import com.microsoft.azure.storage.OperationContext; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.blob.BlobListingDetails; import com.microsoft.azure.storage.blob.BlobRequestOptions; import com.microsoft.azure.storage.blob.CloudBlob; import com.microsoft.azure.storage.blob.CloudBlobDirectory; import com.microsoft.azure.storage.blob.ListBlobItem; import com.microsoft.azure.storage.core.SR; /** * RESERVED FOR INTERNAL USE. Provides an overlay on the LazySegmentedIterator class for enumerating Storage Analytics * log blobs. This handles the logic for the listLogBlobs() methods found in the <code>CloudAnalyticsClient<code> class. */ class LogBlobIterator implements Iterator<ListBlobItem> { private static final String HOUR_STRING = "yyyy/MM/dd/HH"; private static final String DAY_STRING = "yyyy/MM/dd"; private static final String MONTH_STRING = "yyyy/MM"; private static final String YEAR_STRING = "yyyy"; private static final DateFormat HOUR_FORMAT = new SimpleDateFormat(HOUR_STRING); private static final DateFormat DAY_FORMAT = new SimpleDateFormat(DAY_STRING); private static final DateFormat MONTH_FORMAT = new SimpleDateFormat(MONTH_STRING); private static final DateFormat YEAR_FORMAT = new SimpleDateFormat(YEAR_STRING); private static final int HOUR_FORMAT_LENGTH = HOUR_STRING.length(); private static final int DAY_FORMAT_LENGTH = DAY_STRING.length(); private static final int MONTH_FORMAT_LENGTH = MONTH_STRING.length(); /** * Holds a reference to the parent log CloudBlobDirectory. */ private final CloudBlobDirectory logDirectory; /** * Holds the start date and time of the log range requested. */ private Calendar startDate = null; /** * Holds the end date and time of the log range requested. */ private Calendar endDate = null; /** * Holds the value of which log types are being requested. */ private final EnumSet<LoggingOperations> operations; /** * Holds the value of which blob details are being requested. * * Note that presently we only allow none or metadata to be specified here. */ private final EnumSet<BlobListingDetails> details; /** * Holds the request options to use when making the listBlob() requests. */ private final BlobRequestOptions options; /** * Holds an object used to track the execution of the operation. */ private final OperationContext opContext; /** * Holds the iterator for the current call. */ private Iterator<ListBlobItem> currentIterator; /** * Represents the current time prefix being passed to listBlobs(). */ private String currentPrefixTime = null; /** * Represents the prefix that marks the end */ private String endPrefix; /** * Represents whether a valid log has been already retrieved. */ private Boolean isItemPending = false; /** * Represents whether this iterator has already passed the present moment or its corresponding endTime. */ private Boolean isExpired = false; /** * The valid retrieved log that isItemPending refers to. */ private ListBlobItem pendingItem; public LogBlobIterator(final CloudBlobDirectory logDirectory, final Date startDate, final Date endDate, final EnumSet<LoggingOperations> operations, final EnumSet<BlobListingDetails> details, final BlobRequestOptions options, final OperationContext opContext) { TimeZone gmtTime = TimeZone.getTimeZone("GMT"); HOUR_FORMAT.setTimeZone(gmtTime); DAY_FORMAT.setTimeZone(gmtTime); MONTH_FORMAT.setTimeZone(gmtTime); YEAR_FORMAT.setTimeZone(gmtTime); this.logDirectory = logDirectory; this.operations = operations; this.details = details; this.opContext = opContext; if (options == null) { this.options = new BlobRequestOptions(); } else { this.options = options; } if (startDate != null) { this.startDate = new GregorianCalendar(); this.startDate.setTime(startDate); this.startDate.add(GregorianCalendar.MINUTE, (-this.startDate.get(GregorianCalendar.MINUTE))); this.startDate.setTimeZone(gmtTime); } if (endDate != null) { this.endDate = new GregorianCalendar(); this.endDate.setTime(endDate); this.endDate.setTimeZone(gmtTime); this.endPrefix = this.logDirectory.getPrefix() + HOUR_FORMAT.format(this.endDate.getTime()); } } @Override public boolean hasNext() { if (this.isItemPending) { // Short circuit return true; } try { if (this.currentIterator == null) { // If this is the first time, get the first iterator before entering loop. updateIterator(); } while (!this.isExpired) { while (this.currentIterator.hasNext()) { // Go through all of the logs in this iterator and see if any are of the correct type. ListBlobItem current = this.currentIterator.next(); if (this.endDate == null || (current.getParent().getPrefix()).compareTo(this.endPrefix) <= 0) { if (isCorrectLogType(current)) { this.pendingItem = current; this.isItemPending = true; return true; } } else { // We have passed endTime this.isExpired = true; return false; } } updateIterator(); } } catch (final StorageException e) { final NoSuchElementException ex = new NoSuchElementException(SR.ENUMERATION_ERROR); ex.initCause(e); throw ex; } catch (final URISyntaxException e) { final NoSuchElementException ex = new NoSuchElementException(SR.ENUMERATION_ERROR); ex.initCause(e); throw ex; } return false; } @Override public ListBlobItem next() { if (this.isItemPending) { this.isItemPending = false; return this.pendingItem; } if (this.hasNext()) { return this.next(); } else { throw new NoSuchElementException(SR.ITERATOR_EMPTY); } } @Override public void remove() { throw new UnsupportedOperationException(); } /** * Validates that the log given is of the correct log type. * * @param current * the current log * @return whether or not the log is of the correct type. */ private boolean isCorrectLogType(ListBlobItem current) { HashMap<String, String> metadata = ((CloudBlob) current).getMetadata(); String logType = metadata.get("LogType"); if (logType == null) { return true; } if (this.operations.contains(LoggingOperations.READ) && logType.contains("read")) { return true; } if (this.operations.contains(LoggingOperations.WRITE) && logType.contains("write")) { return true; } if (this.operations.contains(LoggingOperations.DELETE) && logType.contains("delete")) { return true; } return false; } /** * Makes the next listBlob call if necessary and updates the currentIterator. * * @throws StorageException * @throws URISyntaxException */ private void updateIterator() throws StorageException, URISyntaxException { if (this.currentPrefixTime != null && this.currentPrefixTime.isEmpty()) { // If we've already called listBlobs() with an empty prefix, don't do so again. this.isExpired = true; return; } GregorianCalendar now = new GregorianCalendar(); now.add(GregorianCalendar.HOUR_OF_DAY, 1); now.setTimeZone(TimeZone.getTimeZone("GMT")); updatePrefix(); if ((this.startDate == null || this.startDate.compareTo(now) <= 0) && (this.endDate == null || ((this.logDirectory.getPrefix() + this.currentPrefixTime) .compareTo(this.endPrefix) <= 0))) { // Only make the next call if the prefix is still possible this.currentIterator = this.logDirectory.listBlobs(this.currentPrefixTime, true, this.details, this.options, this.opContext).iterator(); } else { // We are in the future. this.isExpired = true; } } /** * Updates the currentPrefixTime so that we can make a new call to listBlobs() with the next prefix. */ private void updatePrefix() { if (this.startDate == null) { // startDate not specified this.currentPrefixTime = ""; } else if (this.currentPrefixTime == null) { // First prefix this.currentPrefixTime = HOUR_FORMAT.format(this.startDate.getTime()); } else if (this.currentPrefixTime.length() == HOUR_FORMAT_LENGTH) { // Increment the hour this.startDate.add(GregorianCalendar.HOUR_OF_DAY, 1); if (this.startDate.get(GregorianCalendar.HOUR_OF_DAY) != 0) { // If we are still within the same day, use the hour format this.currentPrefixTime = HOUR_FORMAT.format(this.startDate.getTime()); } else { // If we've reached a day boundary, get the entire next day's logs this.currentPrefixTime = DAY_FORMAT.format(this.startDate.getTime()); } } else if (this.currentPrefixTime.length() == DAY_FORMAT_LENGTH) { // Increment the day this.startDate.add(GregorianCalendar.DAY_OF_MONTH, 1); if (this.startDate.get(GregorianCalendar.DAY_OF_MONTH) != 1) { // If we are still within the same month, use the day format this.currentPrefixTime = DAY_FORMAT.format(this.startDate.getTime()); } else { // If we've reached a month boundary, get the entire next month's logs this.currentPrefixTime = MONTH_FORMAT.format(this.startDate.getTime()); } } else if (this.currentPrefixTime.length() == MONTH_FORMAT_LENGTH) { // Increment the month this.startDate.add(GregorianCalendar.MONTH, 1); if (this.startDate.get(GregorianCalendar.MONTH) != 13) { // Undecember // If we are still within the same year, use the month format this.currentPrefixTime = MONTH_FORMAT.format(this.startDate.getTime()); } else { // If we've reached a year boundary, get the entire next year's logs this.currentPrefixTime = YEAR_FORMAT.format(this.startDate.getTime()); } } else { // Continue to increment year and get the next year's worth of logs. this.startDate.add(GregorianCalendar.YEAR, 1); this.currentPrefixTime = YEAR_FORMAT.format(this.startDate.getTime()); } } }