Optional<BigInteger> loadStores(Function<String, EntityStores> entityStoresByStoreName,
                                    BiFunction<SerializableSnapshot, String, SerializableSnapshot> snapshotPostProcessor) {
        Optional<BigInteger> latestSnapshotTxId;
        try {
            latestSnapshotTxId = m_snapshotStore.listSnapshots().stream().max(BigInteger::compareTo);
        } catch (IOException e) {
            throw new UnrecoverableStoreException("Error occurred when recovering from latest snapshot", e);
        }

        latestSnapshotTxId.ifPresent(lastTx -> {
            LOG.info("Recovering store from snapshot", args -> args.add("transactionId", lastTx));
            var postProcess = new SnapshotPostProcessor(snapshotPostProcessor);
            try {
                Flowable.fromPublisher(m_snapshotStore.createSnapshotReader(lastTx))  //
                        .blockingForEach(reader -> {
                            String storeName = reader.storeName();
                            EntityStores entityStores = entityStoresByStoreName.apply(storeName);

                            SerializableSnapshot serializableSnapshot;
                            try (InputStream is = reader.inputStream()) {
                                serializableSnapshot = m_snapshotSerializer.deserializeSnapshot(storeName, is);
                            }
                            if (serializableSnapshot.getSnapshotModelVersion() != SNAPSHOT_MODEL_VERSION) {
                                throw new UnrecoverableStoreException("Snapshot serializable model version is not supported",
                                                                      args -> args.add("version", serializableSnapshot.getSnapshotModelVersion())
                                                                              .add("expectedVersion", SNAPSHOT_MODEL_VERSION));
                            }

                            if (!lastTx.equals(serializableSnapshot.getTransactionId())) {
                                throw new UnrecoverableStoreException("Snapshot transaction id  mismatch with request transaction id",
                                                                      args -> args.add("snapshotTransactionId", serializableSnapshot.getTransactionId())
                                                                              .add("requestTransactionId", lastTx));
                            }

                            SerializableSnapshot finalSnapshot = postProcess.apply(storeName, serializableSnapshot);

                            finalSnapshot.getEntities().forEach(serializableEntityInstances -> {
                                String entityName = serializableEntityInstances.getEntityName();
                                EntityStore<?> entityStore = entityStores.getEntityStore(entityName);
                                checkArgument(entityStore != null, "Entity has not be registered in the store", args -> args.add("entityName", entityName));

                                entityStore.recover(serializableEntityInstances);
                            });
                        });
            } catch (Exception e) {
                throw new UnrecoverableStoreException("Error occurred when recovering from latest snapshot", e);
            }

            // update the applicationModelVersion if any consistent load/update
            m_applicationModelVersion = postProcess.getConsistentApplicationModelVersion();
        });

        if (!latestSnapshotTxId.isPresent()) {
            LOG.info("Store has no snapshot, store is empty, creating it's first snapshot");
        }

        return latestSnapshotTxId;
    }

Load the stores.

@param entityStoresByStoreName the map store name to entity stores
@return latestSnapshotTxId the transaction id of the last snapshot


void triggerWildcardExpander(final Collection<File> allFiles, final WildcardContext wildcardContext)
      throws IOException {
    LOG.debug("wildcard resources: {}", allFiles);
    if (allFiles.isEmpty()) {
      final String message = String.format("No resource found for wildcard: %s", wildcardContext.getWildcard());
      LOG.warn(message);
      throw new IOException(message);
    }
    if (wildcardExpanderHandler != null) {
      try {
        wildcardExpanderHandler.apply(allFiles);
      } catch(final IOException e) {
        // preserve exception type if the exception is already an IOException
        throw e;
      } catch (final Exception e) {
        LOG.debug("wildcard expanding error. Reporting original exception", e);
        throw new IOException("Exception during expanding wildcard: " + e.getMessage());
      }
    }
  }

Uses the wildcardExpanderHandler to process all found files and directories.

@param files
a collection of all files and folders found during wildcard matching.
@VisibleForTestOnly

Uses the wildcardExpanderHandler to process all found files and directories .


private static String replace(final String inString, final String oldPattern,
      final String newPattern) {
    if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) {
      return inString;
    }
    final StringBuffer sbuf = new StringBuffer();
    // output StringBuffer we'll build up
    int pos = 0; // our position in the old string
    int index = inString.indexOf(oldPattern);
    // the index of an occurrence we've found, or -1
    final int patLen = oldPattern.length();
    while (index >= 0) {
      sbuf.append(inString.substring(pos, index));
      sbuf.append(newPattern);
      pos = index + patLen;
      index = inString.indexOf(oldPattern, pos);
    }
    sbuf.append(inString.substring(pos));
    // remember to append any characters to the right of a match
    return sbuf.toString();
  }

Replace all occurrences of a substring within a string with another string.

@param inString
String to examine
@param oldPattern
String to replace
@param newPattern
String to insert
@return a String with the replacements

Replace all occurrences of a substring within a string with another string .


private static String deleteAny(final String inString,
      final String charsToDelete) {
    if (!hasLength(inString) || !hasLength(charsToDelete)) {
      return inString;
    }
    final StringBuffer out = new StringBuffer();
    for (int i = 0; i < inString.length(); i++) {
      final char c = inString.charAt(i);
      if (charsToDelete.indexOf(c) == -1) {
        out.append(c);
      }
    }
    return out.toString();
  }

private static String deleteAny ( final String inString , final String charsToDelete ) { if ( ! hasLength ( inString ) || ! hasLength ( charsToDelete ) ) { return inString ; } final StringBuffer out = new StringBuffer ( ) ; for ( int i = 0 ; i < inString . length ( ) ; i ++ ) { final char c = inString . charAt ( i ) ; if ( charsToDelete . indexOf ( c ) == - 1 ) { out . append ( c ) ; } } return out . toString ( ) ; }

Delete any character in a given String.

@param inString
the original String
@param charsToDelete
a set of characters to delete. E.g. "az\n" will delete 'a's, 'z's
and new lines.
@return the resulting String

Delete any character in a given String .


public LintReport<T> addReport(final ResourceLintReport<T> resourceLintReport) {
    Validate.notNull(resourceLintReport);
    reports.add(resourceLintReport);
    return this;
  }

public LintReport < T > addReport ( final ResourceLintReport < T > resourceLintReport ) { Validate . notNull ( resourceLintReport ) ; reports . add ( resourceLintReport ) ; return this ; }

Add a single lint report to underlying collection.

@param resourceLintReport
{@link ResourceLintReport} to add.
@return reference to this {@link LintReport} object (fluent interface).

Add a single lint report to underlying collection .


private void updatePropertiesWithConfiguration(final Properties props, final String key) {
    final FilterConfig filterConfig = Context.get().getFilterConfig();
    // first, retrieve value from init-param for backward compatibility
    final String valuesAsString = filterConfig.getInitParameter(key);
    if (valuesAsString != null) {
      props.setProperty(key, valuesAsString);
    } else {
      // retrieve value from configProperties file
      final String value = getConfigProperties().getProperty(key);
      if (value != null) {
        props.setProperty(key, value);
      }
    }
  }

Add to properties a new key with value extracted either from filterConfig or from configurable properties file.
This method helps to ensure backward compatibility of the filterConfig vs configProperties configuration.

@param props
the {@link Properties} which will be populated with the value extracted from filterConfig or
configProperties for the provided key.
@param key
to read from filterConfig or configProperties and put into props.

Add to properties a new key with value extracted either from filterConfig or from configurable properties file . This method helps to ensure backward compatibility of the filterConfig vs configProperties configuration .


private Properties getConfigProperties() {
    if (configProperties == null) {
      configProperties = newConfigProperties();
      if (additionalConfigProperties != null) {
        configProperties.putAll(additionalConfigProperties);
      }
    }
    return configProperties;
  }

private Properties getConfigProperties ( ) { if ( configProperties == null ) { configProperties = newConfigProperties ( ) ; if ( additionalConfigProperties != null ) { configProperties . putAll ( additionalConfigProperties ) ; } } return configProperties ; }

Use this method rather than accessing the field directly, because it will create a default one if none is provided.

Use this method rather than accessing the field directly because it will create a default one if none is provided .


private InputStream createAutoDetectedStream(final String defaultFileName)
      throws IOException {
    try {
      Validate.notNull(wroFile, "Cannot call this method if wroFile is null!");
      if (autoDetectWroFile) {
        final File file = new File(wroFile.getParentFile(), defaultFileName);
        LOG.debug("\tloading autodetected wro file: " + file);
        return new FileInputStream(file);
      }
      LOG.debug("loading wroFile: " + wroFile);
      return new FileInputStream(wroFile);
    } catch (final FileNotFoundException e) {
      // When auto detect is turned on, do not skip trying.. because the auto detection assume that the wro file name
      // can be wrong.
      if (autoDetectWroFile) {
        throw e;
      }
      throw new WroRuntimeException("The wroFile doesn't exist. Skip trying with other wro model factories", e);
    }
  }

Handles the resource model auto detection.

Handles the resource model auto detection .


private Map<String, ResourcePostProcessor> toPostProcessors(
      final Map<String, ResourcePreProcessor> preProcessorsMap) {
    final Map<String, ResourcePostProcessor> map = new HashMap<String, ResourcePostProcessor>();
    for (final Entry<String, ResourcePreProcessor> entry : preProcessorsMap.entrySet()) {
      map.put(entry.getKey(), new ProcessorDecorator(entry.getValue()));
    }
    return map;
  }

private Map < String , ResourcePostProcessor > toPostProcessors ( final Map < String , ResourcePreProcessor > preProcessorsMap ) { final Map < String , ResourcePostProcessor > map = new HashMap < String , ResourcePostProcessor > ( ) ; for ( final Entry < String , ResourcePreProcessor > entry : preProcessorsMap . entrySet ( ) ) { map . put ( entry . getKey ( ) , new ProcessorDecorator ( entry . getValue ( ) ) ) ; } return map ; }

Creates a map of postProcessors form a map of preProcessors. This method will be removed in 1.5.0 release when
there will be no differences between pre & post processor interface.

Creates a map of postProcessors form a map of preProcessors . This method will be removed in 1 . 5 . 0 release when there will be no differences between pre & post processor interface .


public String compile(final String content, final String optionalArgument) {
    final RhinoScriptBuilder builder = initScriptBuilder();
    final String argStr = createArgStr(optionalArgument) + createArgStr(getArguments());
    final String compileScript =
      String.format("%s(%s%s);", getCompileCommand(), WroUtil.toJSMultiLineString(content), argStr);
    return (String) builder.evaluate(compileScript, getCompileCommand());
  }

public String compile ( final String content , final String optionalArgument ) { final RhinoScriptBuilder builder = initScriptBuilder ( ) ; final String argStr = createArgStr ( optionalArgument ) + createArgStr ( getArguments ( ) ) ; final String compileScript = String . format ( "%s(%s%s);" , getCompileCommand ( ) , WroUtil . toJSMultiLineString ( content ) , argStr ) ; return ( String ) builder . evaluate ( compileScript , getCompileCommand ( ) ) ; }

Compiles the javascript template into plain javascript.

@param content
the template to be compiled.
@param optionalArgument
any additional arguments used by template script.
@return the compiled javascript.

Compiles the javascript template into plain javascript .


private void reloadCacheWithNewValue(final Long newValue) {
    final long newValueAsPrimitive = newValue == null ? getCacheUpdatePeriod() : newValue;
    LOG.debug("invoking {} listeners", cacheUpdatePeriodListeners.size());
    for (final PropertyChangeListener listener : cacheUpdatePeriodListeners) {
      final PropertyChangeEvent event = new PropertyChangeEvent(this, "cache", getCacheUpdatePeriod(),
          newValueAsPrimitive);
      listener.propertyChange(event);
    }
  }

private
void
reloadCacheWithNewValue
(
final
Long
newValue
)
{
final
long
newValueAsPrimitive
=
newValue
==
getCacheUpdatePeriod
(
)
:
newValue
;
LOG
.
debug
(
"invoking {} listeners"
,
cacheUpdatePeriodListeners
.
size
(
)
)
;
for
(
final
PropertyChangeListener
listener
:
cacheUpdatePeriodListeners
)
{
final
PropertyChangeEvent
event
=
new
PropertyChangeEvent
(
this
,
"cache"
,
getCacheUpdatePeriod
(
)
,
newValueAsPrimitive
)
;
listener
.
propertyChange
(
event
)
;
}
}

Notify all listeners about cachePeriod property changed. If passed newValue is null, the oldValue is taken as new
value. This is the case when the reloadCache is invoked.

@param newValue
value to set.

Notify all listeners about cachePeriod property changed . If passed newValue is null the oldValue is taken as new value . This is the case when the reloadCache is invoked .


private void reloadModelWithNewValue(final Long newValue) {
    final long newValueAsPrimitive = newValue == null ? getModelUpdatePeriod() : newValue;
    for (final PropertyChangeListener listener : modelUpdatePeriodListeners) {
      final PropertyChangeEvent event = new PropertyChangeEvent(this, "model", getModelUpdatePeriod(),
          newValueAsPrimitive);
      listener.propertyChange(event);
    }
  }

private void reloadModelWithNewValue ( final Long newValue ) { final long newValueAsPrimitive = newValue == null ? getModelUpdatePeriod ( ) : newValue ; for ( final PropertyChangeListener listener : modelUpdatePeriodListeners ) { final PropertyChangeEvent event = new PropertyChangeEvent ( this , "model" , getModelUpdatePeriod ( ) , newValueAsPrimitive ) ; listener . propertyChange ( event ) ; } }

Notify all listeners about cachePeriod property changed. If passed newValue is null, the oldValue is taken as new
value. This is the case when the reloadModel is invoked.

@param newValue
value to set.

Notify all listeners about cachePeriod property changed . If passed newValue is null the oldValue is taken as new value . This is the case when the reloadModel is invoked .


private ServletRequest getWrappedServletRequest(final HttpServletRequest request, final String location) {
    final HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(request) {
      @Override
      public String getRequestURI() {
        return getContextPath() + location;
      }

      @Override
      public String getPathInfo() {
        return WroUtil.getPathInfoFromLocation(this, location);
      }

      @Override
      public String getServletPath() {
        return WroUtil.getServletPathFromLocation(this, location);
      }
    };
    // add an attribute to mark this request as included from wro
    wrappedRequest.setAttribute(ATTRIBUTE_INCLUDED_BY_DISPATCHER, Boolean.TRUE);
    return wrappedRequest;
  }

Build a wrapped servlet request which will be used for dispatching.

Build a wrapped servlet request which will be used for dispatching .


public static Transformer<String> baseNameSuffixTransformer(final String suffix) {
    return new Transformer<String>() {
      public String transform(final String input) {
        final String baseName = FilenameUtils.getBaseName(input);
        final String extension = FilenameUtils.getExtension(input);
        return baseName + suffix + "." + extension;
      }
    };
  }

public static Transformer < String > baseNameSuffixTransformer ( final String suffix ) { return new Transformer < String > ( ) { public String transform ( final String input ) { final String baseName = FilenameUtils . getBaseName ( input ) ; final String extension = FilenameUtils . getExtension ( input ) ; return baseName + suffix + "." + extension ; } } ; }

Appends a suffix to the source baseName.
@param suffix to append.
@return {@link Transformer} capable to append a suffix to provided baseName.

Appends a suffix to the source baseName .


private void onError(final int sc, final String msg) {
    LOG.debug("Error detected with code: {} and message: {}", sc, msg);
    final OutputStream emptyStream = new ByteArrayOutputStream();
    printWriter = new PrintWriter(emptyStream);
    servletOutputStream = new DelegatingServletOutputStream(emptyStream);
  }

Use an empty stream to avoid container writing unwanted message when a resource is missing.

@param sc
status code.
@param msg

Use an empty stream to avoid container writing unwanted message when a resource is missing .


@Override
  public void sendRedirect(final String location)
      throws IOException {
    try {
      LOG.debug("redirecting to: {}", location);
      final InputStream is = externalResourceLocator.locate(location);
      IOUtils.copy(is, servletOutputStream);
      is.close();
      servletOutputStream.close();
    } catch (final IOException e) {
      LOG.warn("{}: Invalid response for location: {}", e.getClass().getName(), location);
      throw e;
    }
  }

@
Override
public
void
sendRedirect
(
final
String
location
)
throws
IOException
{
try
{
LOG
.
debug
(
"redirecting to: {}"
,
location
)
;
final
InputStream
is
=
externalResourceLocator
.
locate
(
location
)
;
IOUtils
.
copy
(
is
,
servletOutputStream
)
;
is
.
close
(
)
;
servletOutputStream
.
close
(
)
;
}
catch
(
final
IOException
e
)
{
LOG
.
warn
(
"{}: Invalid response for location: {}"
,
e
.
getClass
(
)
.
getName
(
)
,
location
)
;
throw
e
;
}
}

By default, redirect does not allow writing to output stream its content. In order to support this use-case, we
need to open a new connection and read the content manually.

By default redirect does not allow writing to output stream its content . In order to support this use - case we need to open a new connection and read the content manually .


private void processResource(final Group group, final Resource resource) {
    final UriLocator uriLocator = locatorFactory.getInstance(resource.getUri());

    if (uriLocator instanceof WildcardUriLocatorSupport) {
      final WildcardStreamLocator wildcardStreamLocator = ((WildcardUriLocatorSupport) uriLocator).getWildcardStreamLocator();

      // TODO should we probably handle the situation when wildcard is present, but the implementation is not
      // expandedHandledAware?
      if (wildcardStreamLocator.hasWildcard(resource.getUri())
          && wildcardStreamLocator instanceof WildcardExpanderHandlerAware) {

        final WildcardExpanderHandlerAware expandedHandler = (WildcardExpanderHandlerAware) wildcardStreamLocator;
        LOG.debug("Expanding resource: {}", resource.getUri());

        final String baseNameFolder = computeBaseNameFolder(resource, uriLocator, expandedHandler);
        LOG.debug("baseNameFolder: {}", baseNameFolder);

        expandedHandler.setWildcardExpanderHandler(createExpanderHandler(group, resource, baseNameFolder));
        try {
          // trigger the wildcard replacement
          uriLocator.locate(resource.getUri());
        } catch (final IOException e) {
          // log only
          LOG.debug("[FAIL] problem while trying to expand wildcard for the following resource uri: {}",
              resource.getUri());
        } finally {
          // remove the handler, it is not needed anymore
          expandedHandler.setWildcardExpanderHandler(null);
        }
      }
    }
  }

Process each resource and replace it with a collection of resources if it contains wildcard.

Process each resource and replace it with a collection of resources if it contains wildcard .


public Function<Collection<File>, Void> createExpanderHandler(final Group group, final Resource resource,
      final String baseNameFolder) {
    LOG.debug("createExpanderHandler using baseNameFolder: {}\n for resource {}", baseNameFolder, resource);
    return new Function<Collection<File>, Void>() {
      public Void apply(final Collection<File> files) {
        if (baseNameFolder == null) {
          // replacing group with empty list since the original uri has no associated resources.
          // No BaseNameFolder found
          LOG.warn("The resource {} is probably invalid, removing it from the group.", resource);
          group.replace(resource, new ArrayList<Resource>());
        } else {
          final List<Resource> expandedResources = new ArrayList<Resource>();
          LOG.debug("baseNameFolder: {}", baseNameFolder);
          for (final File file : files) {
            final String resourcePath = getFullPathNoEndSeparator(resource);
            LOG.debug("\tresourcePath: {}", resourcePath);
            LOG.debug("\tfile path: {}", file.getPath());
            final String computedResourceUri = resourcePath
                + StringUtils.removeStart(file.getPath(), baseNameFolder).replace('\\', '/');

            final Resource expandedResource = Resource.create(computedResourceUri, resource.getType());
            LOG.debug("\texpanded resource: {}", expandedResource);
            expandedResources.add(expandedResource);
          }
          LOG.debug("\treplace resource {}", resource);
          group.replace(resource, expandedResources);
        }
        return null;
      }

      /**
       * This method fixes the problem when a resource in a group uses deep wildcard and starts at the root.
       *

       * Find more details here.
       */
      private String getFullPathNoEndSeparator(final Resource resource1) {
        final String result = FilenameUtils.getFullPathNoEndSeparator(resource1.getUri());
        if (result != null && 1 == result.length() && 0 == FilenameUtils.indexOfLastSeparator(result)) {
          return "";
        }

        return result;
      }
    };
  }

create the handler which expand the resources containing wildcard.

create the handler which expand the resources containing wildcard .


public final InputStream locate(final String uri)
    throws IOException {
    final UriLocator uriLocator = getInstance(uri);
    if (uriLocator == null) {
      throw new WroRuntimeException("No locator is capable of handling uri: " + uri);
    }
    LOG.debug("[OK] locating {} using locator: {}", uri, uriLocator.getClass().getSimpleName());
    return new AutoCloseInputStream(uriLocator.locate(uri));
  }

Locates an InputStream for the given uri.

@param uri to locate.
@return {@link InputStream} of the resource.
@throws IOException if uri is invalid or resource couldn't be located.

Locates an InputStream for the given uri .


private WroConfiguration createConfiguration() {
    // Extract config from servletContext (if already configured)
    // TODO use a named helper
    final WroConfiguration configAttribute = ServletContextAttributeHelper.create(filterConfig).getWroConfiguration();
    if (configAttribute != null) {
      setConfiguration(configAttribute);
    }
    return getWroConfigurationFactory().create();
  }

Creates configuration by looking up in servletContext attributes. If none is found, a new one will be created using
the configuration factory.

@return {@link WroConfiguration} object.

Creates configuration by looking up in servletContext attributes . If none is found a new one will be created using the configuration factory .


private void registerChangeListeners() {
    wroConfiguration.registerCacheUpdatePeriodChangeListener(new PropertyChangeListener() {
      public void propertyChange(final PropertyChangeEvent event) {
        // reset cache headers when any property is changed in order to avoid browser caching
        headersConfigurer = newResponseHeadersConfigurer();
        wroManagerFactory.onCachePeriodChanged(valueAsLong(event.getNewValue()));
      }
    });
    wroConfiguration.registerModelUpdatePeriodChangeListener(new PropertyChangeListener() {
      public void propertyChange(final PropertyChangeEvent event) {
        headersConfigurer = newResponseHeadersConfigurer();
        wroManagerFactory.onModelPeriodChanged(valueAsLong(event.getNewValue()));
      }
    });
    LOG.debug("Cache & Model change listeners were registered");
  }


private void processRequest(final HttpServletRequest request, final HttpServletResponse response)
      throws ServletException, IOException {
    setResponseHeaders(response);
    // process the uri using manager
    wroManagerFactory.create().process();
  }

private
void
processRequest
(
final
HttpServletRequest
request
,
final
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
setResponseHeaders
(
response
)
;
// process the uri using manager
wroManagerFactory
.
create
(
)
.
process
(
)
;
}


public final void setConfiguration(final WroConfiguration config) {
    notNull(config);
    wroConfigurationFactory = new ObjectFactory<WroConfiguration>() {
      public WroConfiguration create() {
        return config;
      }
    };
  }

public final void setConfiguration ( final WroConfiguration config ) { notNull ( config ) ; wroConfigurationFactory = new ObjectFactory < WroConfiguration > ( ) { public WroConfiguration create ( ) { return config ; } } ; }

Once set, this configuration will be used, instead of the one built by the factory.

@param config
a not null {@link WroConfiguration} to set.

Once set this configuration will be used instead of the one built by the factory .


private void identifyDuplicateGroupNames(final Collection<Group> groups) {
    LOG.debug("identifyDuplicateGroupNames");
    final List<String> groupNames = new ArrayList<String>();
    for (final Group group : groups) {
      if (groupNames.contains(group.getName())) {
        throw new WroRuntimeException("Duplicate group name detected: " + group.getName());
      }
      groupNames.add(group.getName());
    }
  }

private
void
identifyDuplicateGroupNames
(
final
Collection
<
Group
>
groups
)
{
LOG
.
debug
(
"identifyDuplicateGroupNames"
)
;
final
List
<
String
>
groupNames
=
new
ArrayList
<
String
>
(
)
;
for
(
final
Group
group
:
groups
)
{
if
(
groupNames
.
contains
(
group
.
getName
(
)
)
)
{
throw
new
WroRuntimeException
(
"Duplicate group name detected: "
+
group
.
getName
(
)
)
;
}
groupNames
.
add
(
group
.
getName
(
)
)
;
}
}

Identify duplicate group names.

@param groups a collection of group to validate.


public void merge(final WroModel importedModel) {
    Validate.notNull(importedModel, "imported model cannot be null!");
    LOG.debug("merging importedModel: {}", importedModel);
    for (final String groupName : new WroModelInspector(importedModel).getGroupNames()) {
      if (new WroModelInspector(this).getGroupNames().contains(groupName)) {
        throw new WroRuntimeException("Duplicate group name detected: " + groupName);
      }
      final Group importedGroup = new WroModelInspector(importedModel).getGroupByName(groupName);
      addGroup(importedGroup);
    }
  }

public
void
merge
(
final
WroModel
importedModel
)
{
Validate
.
notNull
(
importedModel
,
"imported model cannot be null!"
)
;
LOG
.
debug
(
"merging importedModel: {}"
,
importedModel
)
;
for
(
final
String
groupName
:
new
WroModelInspector
(
importedModel
)
.
getGroupNames
(
)
)
{
if
(
new
WroModelInspector
(
this
)
.
getGroupNames
(
)
.
contains
(
groupName
)
)
{
throw
new
WroRuntimeException
(
"Duplicate group name detected: "
+
groupName
)
;
}
final
Group
importedGroup
=
new
WroModelInspector
(
importedModel
)
.
getGroupByName
(
groupName
)
;
addGroup
(
importedGroup
)
;
}
}

Merge this model with another model. This is useful for supporting model imports.

@param importedModel model to import.

Merge this model with another model . This is useful for supporting model imports .


public InputStream locate(final String uri)
      throws IOException {
    final UriLocator locator = getInstance(uri);
    if (locator == null) {

      return getDecoratedObject().locate(uri);
    }
    return locator.locate(uri);
  }

public InputStream locate ( final String uri ) throws IOException { final UriLocator locator = getInstance ( uri ) ; if ( locator == null ) { return getDecoratedObject ( ) . locate ( uri ) ; } return locator . locate ( uri ) ; }

This implementation shows the problem with current design of locator implementation. Needs to be changed.

This implementation shows the problem with current design of locator implementation . Needs to be changed .


private String applyPostProcessors(final CacheKey cacheKey, final String content)
      throws IOException {
    final Collection<ResourcePostProcessor> processors = processorsFactory.getPostProcessors();
    LOG.debug("appying post processors: {}", processors);
    if (processors.isEmpty()) {
      return content;
    }
    final Resource resource = Resource.create(cacheKey.getGroupName(), cacheKey.getType());

    Reader reader = new StringReader(content.toString());
    Writer writer = null;
    for (final ResourcePostProcessor processor : processors) {
      final ResourcePreProcessor decoratedProcessor = decorateProcessor(processor, cacheKey.isMinimize());
      writer = new StringWriter();
      decoratedProcessor.process(resource, reader, writer);
      reader = new StringReader(writer.toString());
    }
    return writer.toString();
  }

private
String
applyPostProcessors
(
final
CacheKey
cacheKey
,
final
String
content
)
throws
IOException
{
final
Collection
<
ResourcePostProcessor
>
processors
=
processorsFactory
.
getPostProcessors
(
)
;
LOG
.
debug
(
"appying post processors: {}"
,
processors
)
;
if
(
processors
.
isEmpty
(
)
)
{
return
content
;
}
final
Resource
resource
=
Resource
.
create
(
cacheKey
.
getGroupName
(
)
,
cacheKey
.
getType
(
)
)
;
Reader
reader
=
new
StringReader
(
content
.
toString
(
)
)
;
Writer
writer
=
;
for
(
final
ResourcePostProcessor
processor
:
processors
)
{
final
ResourcePreProcessor
decoratedProcessor
=
decorateProcessor
(
processor
,
cacheKey
.
isMinimize
(
)
)
;
writer
=
new
StringWriter
(
)
;
decoratedProcessor
.
process
(
resource
,
reader
,
writer
)
;
reader
=
new
StringReader
(
writer
.
toString
(
)
)
;
}
return
writer
.
toString
(
)
;
}

Apply resourcePostProcessors.

@param cacheKey
the {@link CacheKey} being processed.
@param content
to process with all postProcessors.
@return the post processed content.


private synchronized ProcessorDecorator decorateProcessor(final ResourcePostProcessor processor,
      final boolean minimize) {
    final ProcessorDecorator decorated = new DefaultProcessorDecorator(processor, minimize) {
      @Override
      public void process(final Resource resource, final Reader reader, final Writer writer)
          throws IOException {
        try {
          callbackRegistry.onBeforePostProcess();
          super.process(resource, reader, writer);
        } finally {
          callbackRegistry.onAfterPostProcess();
        }
      }
    };
    injector.inject(decorated);
    return decorated;
  }

private synchronized ProcessorDecorator decorateProcessor ( final ResourcePostProcessor processor , final boolean minimize ) { final ProcessorDecorator decorated = new DefaultProcessorDecorator ( processor , minimize ) { @ Override public void process ( final Resource resource , final Reader reader , final Writer writer ) throws IOException { try { callbackRegistry . onBeforePostProcess ( ) ; super . process ( resource , reader , writer ) ; } finally { callbackRegistry . onAfterPostProcess ( ) ; } } } ; injector . inject ( decorated ) ; return decorated ; }

This method is synchronized to ensure that processor is injected before it is being used by other thread.

@return a decorated processor.

This method is synchronized to ensure that processor is injected before it is being used by other thread .


private void doProcess(final String requestUri, final Reader reader, final Writer writer)
      throws IOException {
    Reader input = reader;
    Writer output = null;
    LOG.debug("processing resource: {}", requestUri);
    try {
      final StopWatch stopWatch = new StopWatch();
      final Injector injector = InjectorBuilder.create(new BaseWroManagerFactory()).build();
      final List<ResourcePreProcessor> processors = getProcessorsList();
      if (processors == null || processors.isEmpty()) {
        IOUtils.copy(reader, writer);
      } else {
        for (final ResourcePreProcessor processor : processors) {
          stopWatch.start("Using " + processor.getClass().getSimpleName());
          // inject all required properties
          injector.inject(processor);

          output = new StringWriter();
          LOG.debug("Using {} processor", processor);
          processor.process(createResource(requestUri), input, output);

          input = new StringReader(output.toString());
          stopWatch.stop();
        }
        LOG.debug(stopWatch.prettyPrint());
        if (output != null) {
          writer.write(output.toString());
        }
      }
    } finally {
      reader.close();
      writer.close();
    }
  }

private
void
doProcess
(
final
String
requestUri
,
final
Reader
reader
,
final
Writer
writer
)
throws
IOException
{
Reader
input
=
reader
;
Writer
output
=
;
LOG
.
debug
(
"processing resource: {}"
,
requestUri
)
;
try
{
final
StopWatch
stopWatch
=
new
StopWatch
(
)
;
final
Injector
injector
=
InjectorBuilder
.
create
(
new
BaseWroManagerFactory
(
)
)
.
build
(
)
;
final
List
<
ResourcePreProcessor
>
processors
=
getProcessorsList
(
)
;
if
(
processors
==
||
processors
.
isEmpty
(
)
)
{
IOUtils
.
copy
(
reader
,
writer
)
;
}
else
{
for
(
final
ResourcePreProcessor
processor
:
processors
)
{
stopWatch
.
start
(
"Using "
+
processor
.
getClass
(
)
.
getSimpleName
(
)
)
;
// inject all required properties
injector
.
inject
(
processor
)
;
output
=
new
StringWriter
(
)
;
LOG
.
debug
(
"Using {} processor"
,
processor
)
;
processor
.
process
(
createResource
(
requestUri
)
,
input
,
output
)
;
input
=
new
StringReader
(
output
.
toString
(
)
)
;
stopWatch
.
stop
(
)
;
}
LOG
.
debug
(
stopWatch
.
prettyPrint
(
)
)
;
if
(
output
!=
)
{
writer
.
write
(
output
.
toString
(
)
)
;
}
}
}
finally
{
reader
.
close
(
)
;
writer
.
close
(
)
;
}
}

Applies configured processor on the intercepted stream.

Applies configured processor on the intercepted stream .


public String[] splitOptions(final String optionAsString) {
    return optionAsString == null ? ArrayUtils.EMPTY_STRING_ARRAY : optionAsString.split("(?ims),(?![^\\[\\]]*\\])");
  }

public String [ ] splitOptions ( final String optionAsString ) { return optionAsString == null ? ArrayUtils . EMPTY_STRING_ARRAY : optionAsString . split ( "(?ims),(?![^\\[\\]]*\\])" ) ; }

Split multiple options into an array of options.

@param optionAsString string representation of multiple options.
@return an array of options.

Split multiple options into an array of options .


public Properties load(final InputStream inputStream) throws IOException {
    Validate.notNull(inputStream);
    final String rawContent = IOUtils.toString(inputStream, CharEncoding.UTF_8);
    parseProperties(rawContent.replaceAll(REGEX_COMMENTS, ""));
    return this.properties;
  }

public Properties load ( final InputStream inputStream ) throws IOException { Validate . notNull ( inputStream ) ; final String rawContent = IOUtils . toString ( inputStream , CharEncoding . UTF_8 ) ; parseProperties ( rawContent . replaceAll ( REGEX_COMMENTS , "" ) ) ; return this . properties ; }

Load the properties from the stream. The implementation will handle comments properly by removing them before
properties are loaded.

@param inputStream
@return {@link Properties} containing properties parsed from the stream.
@throws IOException

Load the properties from the stream . The implementation will handle comments properly by removing them before properties are loaded .


private void parseProperties(final String propertiesAsString) {
    //should work also \r?\n
    final String[] propertyEntries = propertiesAsString.split("\\r?\\n");
    for (final String entry : propertyEntries) {
      readPropertyEntry(entry);
    }
  }

private
void
parseProperties
(
final
String
propertiesAsString
)
{
//should work also \r?\n
final
String
[
]
propertyEntries
=
propertiesAsString
.
split
(
"\\r?\\n"
)
;
for
(
final
String
entry
:
propertyEntries
)
{
readPropertyEntry
(
entry
)
;
}
}

parse the properties from the provided string containing a raw properties


private WroManagerFactory createCustomManagerFactory()
      throws MojoExecutionException {
    WroManagerFactory factory = null;
    try {
      final Class<?> wroManagerFactoryClass = Thread.currentThread().getContextClassLoader().loadClass(
          wroManagerFactory.trim());
      factory = (WroManagerFactory) wroManagerFactoryClass.newInstance();
    } catch (final Exception e) {
      throw new MojoExecutionException("Invalid wroManagerFactoryClass, called: " + wroManagerFactory, e);
    }
    return factory;
  }

Creates a custom instance of Manager factory. The wroManagerFactory parameter value is used to identify the manager class.

Creates a custom instance of Manager factory . The wroManagerFactory parameter value is used to identify the manager class .


private void persistResourceFingerprints(final List<String> groupNames) {
    final WroModelInspector modelInspector = new WroModelInspector(getModel());
    for (final String groupName : groupNames) {
      final Group group = modelInspector.getGroupByName(groupName);
      if (group != null) {
        for (final Resource resource : group.getResources()) {
          getResourceChangeHandler().remember(resource);
        }
      }
    }
  }

private void persistResourceFingerprints ( final List < String > groupNames ) { final WroModelInspector modelInspector = new WroModelInspector ( getModel ( ) ) ; for ( final String groupName : groupNames ) { final Group group = modelInspector . getGroupByName ( groupName ) ; if ( group != null ) { for ( final Resource resource : group . getResources ( ) ) { getResourceChangeHandler ( ) . remember ( resource ) ; } } } }

Store digest for all resources contained inside the list of provided groups.

Store digest for all resources contained inside the list of provided groups .


private boolean isTargetGroup(final Group group) {
    notNull(group);
    final String targetGroups = getTargetGroups();
    // null, means all groups are target groups
    return targetGroups == null || targetGroups.contains(group.getName());
  }

Check if the provided group is a target group.

Check if the provided group is a target group .


protected final void extendPluginClasspath()
      throws MojoExecutionException {
    // this code is inspired from http://teleal.org/weblog/Extending%20the%20Maven%20plugin%20classpath.html
    final List<String> classpathElements = new ArrayList<String>();
    try {
      classpathElements.addAll(mavenProject.getRuntimeClasspathElements());
    } catch (final DependencyResolutionRequiredException e) {
      throw new MojoExecutionException("Could not get compile classpath elements", e);
    }
    final ClassLoader classLoader = createClassLoader(classpathElements);
    Thread.currentThread().setContextClassLoader(classLoader);
  }


protected InputStream getModelResourceAsStream()
      throws IOException {
    final ServletContext servletContext = context.getServletContext();
    // Don't allow NPE, throw a more detailed exception
    if (servletContext == null) {
      throw new WroRuntimeException(
          "No servletContext is available. Probably you are running this code outside of the request cycle!");
    }
    final String resourceLocation = "/WEB-INF/" + getDefaultModelFilename();
    final InputStream stream = servletContext.getResourceAsStream(resourceLocation);
    if (stream == null) {
      throw new IOException("Invalid resource requested: " + resourceLocation);
    }
    return stream;
  }

Override this method, in order to provide different xml definition file name.

@return stream of the xml representation of the model.
@throws IOException
if the stream couldn't be read.

Override this method in order to provide different xml definition file name .


private WroManagerFactory initFactory(final Properties properties) {
    WroManagerFactory factory = null;
    final String wroManagerClassName = properties.getProperty(ConfigConstants.managerFactoryClassName.name());
    if (StringUtils.isEmpty(wroManagerClassName)) {
      // If no context param was specified we return the default factory
      factory = newManagerFactory();
    } else {
      // Try to find the specified factory class
      Class<?> factoryClass = null;
      try {
        factoryClass = Thread.currentThread().getContextClassLoader().loadClass(wroManagerClassName);
        factory = (WroManagerFactory) factoryClass.newInstance();
      } catch (final Exception e) {
        throw new WroRuntimeException("Exception while loading WroManagerFactory class:" + wroManagerClassName, e);
      }
    }
    // add properties if required
    if (factory instanceof ConfigurableWroManagerFactory) {
      ((ConfigurableWroManagerFactory) factory).addConfigProperties(properties);
    }
    return factory;
  }

Initialized inner factory based on provided configuration.

Initialized inner factory based on provided configuration .


public ModelTransformerFactory setTransformers(final List<Transformer<WroModel>> modelTransformers) {
    Validate.notNull(modelTransformers);
    this.modelTransformers = modelTransformers;
    return this;
  }

public ModelTransformerFactory setTransformers ( final List < Transformer < WroModel > > modelTransformers ) { Validate . notNull ( modelTransformers ) ; this . modelTransformers = modelTransformers ; return this ; }

Set a list of transformers to apply on decorated model factory.

Set a list of transformers to apply on decorated model factory .


@Override
  public String compile(final String content, final String name) {
    final String precompiledFunction = super.compile(content, "");
    return String.format("(function() {Ember.TEMPLATES[%s] = Ember.Handlebars.template(%s)})();", name, precompiledFunction);
  }

visible for testing, the init of a HandlebarsJs template

visible for testing the init of a HandlebarsJs template


public String processAndMerge(final List<Resource> resources, final boolean minimize)
      throws IOException {
    return processAndMerge(resources, ProcessingCriteria.create(ProcessingType.ALL, minimize));
  }

public String processAndMerge ( final List < Resource > resources , final boolean minimize ) throws IOException { return processAndMerge ( resources , ProcessingCriteria . create ( ProcessingType . ALL , minimize ) ) ; }

Apply preProcessors on resources and merge them after all preProcessors are applied.

@param resources
what are the resources to merge.
@param minimize
whether minimize aware processors must be applied or not.
@return preProcessed merged content.

Apply preProcessors on resources and merge them after all preProcessors are applied .


public String processAndMerge(final List<Resource> resources, final ProcessingCriteria criteria)
      throws IOException {
    notNull(criteria);
    LOG.debug("criteria: {}", criteria);
    callbackRegistry.onBeforeMerge();
    try {
      notNull(resources);
      LOG.debug("process and merge resources: {}", resources);
      final StringBuffer result = new StringBuffer();
      if (shouldRunInParallel(resources)) {
        result.append(runInParallel(resources, criteria));
      } else {
        for (final Resource resource : resources) {
          LOG.debug("\tmerging resource: {}", resource);
          result.append(applyPreProcessors(resource, criteria));
        }
      }
      return result.toString();
    } finally {
      callbackRegistry.onAfterMerge();
    }
  }

public
String
processAndMerge
(
final
List
<
Resource
>
resources
,
final
ProcessingCriteria
criteria
)
throws
IOException
{
notNull
(
criteria
)
;
LOG
.
debug
(
"criteria: {}"
,
criteria
)
;
callbackRegistry
.
onBeforeMerge
(
)
;
try
{
notNull
(
resources
)
;
LOG
.
debug
(
"process and merge resources: {}"
,
resources
)
;
final
StringBuffer
result
=
new
StringBuffer
(
)
;
if
(
shouldRunInParallel
(
resources
)
)
{
result
.
append
(
runInParallel
(
resources
,
criteria
)
)
;
}
else
{
for
(
final
Resource
resource
:
resources
)
{
LOG
.
debug
(
"\tmerging resource: {}"
,
resource
)
;
result
.
append
(
applyPreProcessors
(
resource
,
criteria
)
)
;
}
}
return
result
.
toString
(
)
;
}
finally
{
callbackRegistry
.
onAfterMerge
(
)
;
}
}

Apply preProcessors on resources and merge them.

@param resources
what are the resources to merge.
@param criteria
{@link ProcessingCriteria} used to identify the processors to apply and those to skip.
@return preProcessed merged content.

Apply preProcessors on resources and merge them .


private String runInParallel(final List<Resource> resources, final ProcessingCriteria criteria)
      throws IOException {
    LOG.debug("Running preProcessing in Parallel");
    final StringBuffer result = new StringBuffer();
    final List<Callable<String>> callables = new ArrayList<Callable<String>>();
    for (final Resource resource : resources) {
      callables.add(new Callable<String>() {
        public String call()
            throws Exception {
          LOG.debug("Callable started for resource: {} ...", resource);
          return applyPreProcessors(resource, criteria);
        }
      });
    }
    final ExecutorService exec = getExecutorService();
    final List<Future<String>> futures = new ArrayList<Future<String>>();
    for (final Callable<String> callable : callables) {
      // decorate with ContextPropagatingCallable in order to allow spawn threads to access the Context
      final Callable<String> decoratedCallable = new ContextPropagatingCallable<String>(callable);
      futures.add(exec.submit(decoratedCallable));
    }

    for (final Future<String> future : futures) {
      try {
        result.append(future.get());
      } catch (final Exception e) {
        // propagate original cause
        final Throwable cause = e.getCause();
        if (cause instanceof WroRuntimeException) {
          throw (WroRuntimeException) cause;
        } else if (cause instanceof IOException) {
          throw (IOException) cause;
        } else {
          throw new WroRuntimeException("Problem during parallel pre processing", e);
        }
      }
    }
    return result.toString();
  }

runs the pre processors in parallel.

@return merged and pre processed content.


private String applyPreProcessors(final Resource resource, final ProcessingCriteria criteria)
      throws IOException {
    final Collection<ResourcePreProcessor> processors = processorsFactory.getPreProcessors();
    LOG.debug("applying preProcessors: {}", processors);

    String resourceContent = null;
    try {
      resourceContent = getResourceContent(resource);
    } catch (final IOException e) {
      LOG.debug("Invalid resource found: {}", resource);
      if (Context.get().getConfig().isIgnoreMissingResources()) {
        return StringUtils.EMPTY;
      } else {
        LOG.error("Cannot ignore missing resource:  {}", resource);
        throw e;
      }
    }
    if (!processors.isEmpty()) {
      Writer writer = null;
      for (final ResourcePreProcessor processor : processors) {
        final ResourcePreProcessor decoratedProcessor = decoratePreProcessor(processor, criteria);

        writer = new StringWriter();
        final Reader reader = new StringReader(resourceContent);
        // decorate and process
        decoratedProcessor.process(resource, reader, writer);
        // use the outcome for next input
        resourceContent = writer.toString();
      }
    }
    // add explicitly new line at the end to avoid unexpected comment issue
    return String.format("%s%n", resourceContent);
  }

Apply a list of preprocessors on a resource.

@param resource
the {@link Resource} on which processors will be applied
@param processors
the list of processor to apply on the resource.

Apply a list of preprocessors on a resource .


private synchronized ResourcePreProcessor decoratePreProcessor(final ResourcePreProcessor processor,
      final ProcessingCriteria criteria) {
    final ResourcePreProcessor decorated = new DefaultProcessorDecorator(processor, criteria) {
      @Override
      public void process(final Resource resource, final Reader reader, final Writer writer)
          throws IOException {
        try {
          callbackRegistry.onBeforePreProcess();
          super.process(resource, reader, writer);
        } finally {
          callbackRegistry.onAfterPreProcess();
        }
      }
    };
    injector.inject(decorated);
    return decorated;
  }

private synchronized ResourcePreProcessor decoratePreProcessor ( final ResourcePreProcessor processor , final ProcessingCriteria criteria ) { final ResourcePreProcessor decorated = new DefaultProcessorDecorator ( processor , criteria ) { @ Override public void process ( final Resource resource , final Reader reader , final Writer writer ) throws IOException { try { callbackRegistry . onBeforePreProcess ( ) ; super . process ( resource , reader , writer ) ; } finally { callbackRegistry . onAfterPreProcess ( ) ; } } } ; injector . inject ( decorated ) ; return decorated ; }

Decorates preProcessor with mandatory decorators.
This method is synchronized to ensure that processor is injected before it is being used by other thread.

Decorates preProcessor with mandatory decorators . This method is synchronized to ensure that processor is injected before it is being used by other thread .


public void persist() {
    OutputStream os = null;
    try {
      os = new FileOutputStream(fallbackStorageFile);
      fallbackStorage.store(os, "Generated");
      LOG.debug("fallback storage written to {}", fallbackStorageFile);
    } catch (final IOException e) {
      LOG.warn("Cannot persist fallback storage: {}.", fallbackStorageFile, e);
    } finally {
      IOUtils.closeQuietly(os);
    }
  }

public
void
persist
(
)
{
OutputStream
os
=
;
try
{
os
=
new
FileOutputStream
(
fallbackStorageFile
)
;
fallbackStorage
.
store
(
os
,
"Generated"
)
;
LOG
.
debug
(
"fallback storage written to {}"
,
fallbackStorageFile
)
;
}
catch
(
final
IOException
e
)
{
LOG
.
warn
(
"Cannot persist fallback storage: {}."
,
fallbackStorageFile
,
e
)
;
}
finally
{
IOUtils
.
closeQuietly
(
os
)
;
}
}

Persist the fallbackStorage to the fallbackStorageFile. This method should be invoked only once during build, since
it is relatively expensive. Not invoking it, would break the incremental build feature.

Persist the fallbackStorage to the fallbackStorageFile . This method should be invoked only once during build since it is relatively expensive . Not invoking it would break the incremental build feature .


private Collection<Field> getAllFields(final Object object) {
    final Collection<Field> fields = new ArrayList<Field>();
    fields.addAll(Arrays.asList(object.getClass().getDeclaredFields()));
    // inspect super classes
    Class<?> superClass = object.getClass().getSuperclass();
    while (superClass != null) {
      fields.addAll(Arrays.asList(superClass.getDeclaredFields()));
      superClass = superClass.getSuperclass();
    }
    return fields;
  }

private
Collection
<
Field
>
getAllFields
(
final
Object
object
)
{
final
Collection
<
Field
>
fields
=
new
ArrayList
<
Field
>
(
)
;
fields
.
addAll
(
Arrays
.
asList
(
object
.
getClass
(
)
.
getDeclaredFields
(
)
)
)
;
// inspect super classes
Class
<
>
superClass
=
object
.
getClass
(
)
.
getSuperclass
(
)
;
while
(
superClass
!=
)
{
fields
.
addAll
(
Arrays
.
asList
(
superClass
.
getDeclaredFields
(
)
)
)
;
superClass
=
superClass
.
getSuperclass
(
)
;
}
return
fields
;
}

Return all fields for given object, also those from the super classes.

Return all fields for given object also those from the super classes .


public String rewrite(final String cssUri, final String imageUrl) {
    notNull(cssUri);
    notNull(imageUrl);
    if (StringUtils.isEmpty(imageUrl)) {
       return imageUrl;
    }
    if (ServletContextUriLocator.isValid(cssUri)) {
      if (ServletContextUriLocator.isValid(imageUrl)) {
        return prependContextPath(imageUrl);
      }
      // Treat WEB-INF special case
      if (ServletContextUriLocator.isProtectedResource(cssUri)) {
        return context.proxyPrefix + computeNewImageLocation(cssUri, imageUrl);
      }
      // Compute the folder where the final css is located. This is important for computing image location after url
      // rewriting.
      // Prefix of the path to the overwritten image url. This will be of the following type: "../" or "../.." depending
      // on the depth of the aggregatedFolderPath.
      final String aggregatedPathPrefix = computeAggregationPathPrefix(context.aggregatedFolderPath);
      LOG.debug("computed aggregatedPathPrefix {}", aggregatedPathPrefix);
      String newImageLocation = computeNewImageLocation(aggregatedPathPrefix + cssUri, imageUrl);
      if (newImageLocation.startsWith(ServletContextUriLocator.PREFIX)) {
        newImageLocation = prependContextPath(newImageLocation);
      }
      LOG.debug("newImageLocation: {}", newImageLocation);
      return newImageLocation;
    }
    if (ClasspathUriLocator.isValid(cssUri)) {
      final String proxyUrl = context.proxyPrefix + computeNewImageLocation(cssUri, imageUrl);
      final String contextRelativeUrl = prependContextPath(imageUrl);
      //final String contextRelativeUrl = context.contextPath + imageUrl;
      // leave imageUrl unchanged if it is a servlet context relative resource
      return (ServletContextUriLocator.isValid(imageUrl) ? contextRelativeUrl : proxyUrl);
    }
    if (UrlUriLocator.isValid(cssUri)) {
      final String computedCssUri = ServletContextUriLocator.isValid(imageUrl) ? computeCssUriForExternalServer(cssUri)
          : cssUri;
      return computeNewImageLocation(computedCssUri, imageUrl);
    }
    throw new WroRuntimeException("Could not replace imageUrl: " + imageUrl + ", contained at location: " + cssUri);
  }

Computes the url of the image to be replaced in a css resource.

@param cssUri
the uri of the css where the image is located.
@param imageUrl
the url of the image (relative or absolute).
@return replaced url of the image.

Computes the url of the image to be replaced in a css resource .


private String computeNewImageLocation(final String cssUri, final String imageUrl) {
    LOG.debug("cssUri: {}, imageUrl {}", cssUri, imageUrl);
    final String cleanImageUrl = cleanImageUrl(imageUrl);
    // TODO move to ServletContextUriLocator as a helper method?
    // for the following input: /a/b/c/1.css => /a/b/c/
    int idxLastSeparator = cssUri.lastIndexOf(ServletContextUriLocator.PREFIX);
    if (idxLastSeparator == -1) {
      if (ClasspathUriLocator.isValid(cssUri)) {
        idxLastSeparator = cssUri.lastIndexOf(ClasspathUriLocator.PREFIX);
        // find the index of ':' character used by classpath prefix
        if (idxLastSeparator >= 0) {
          idxLastSeparator += ClasspathUriLocator.PREFIX.length() - 1;
        }
      }
      if (idxLastSeparator < 0) {
        throw new IllegalStateException("Invalid cssUri: " + cssUri + ". Should contain at least one '/' character!");
      }
    }
    final String cssUriFolder = cssUri.substring(0, idxLastSeparator + 1);
    // remove '/' from imageUrl if it starts with one.
    final String processedImageUrl = cleanImageUrl.startsWith(ServletContextUriLocator.PREFIX) ? cleanImageUrl.substring(1)
        : cleanImageUrl;
    final String computedImageLocation = cleanPath(cssUriFolder + processedImageUrl);
    LOG.debug("computedImageLocation: {}", computedImageLocation);
    return computedImageLocation;
  }

Concatenates cssUri and imageUrl after few changes are applied to both input parameters.

@param cssUri
the URI of css resource.
@param imageUrl
the URL of image referred in css.
@return processed new location of image url.

Concatenates cssUri and imageUrl after few changes are applied to both input parameters .


public BaseWroManagerFactory addModelTransformer(final Transformer<WroModel> modelTransformer) {
    if (modelTransformers == null) {
      modelTransformers = new ArrayList<Transformer<WroModel>>();
    }
    this.modelTransformers.add(modelTransformer);
    return this;
  }

public BaseWroManagerFactory addModelTransformer ( final Transformer < WroModel > modelTransformer ) { if ( modelTransformers == null ) { modelTransformers = new ArrayList < Transformer < WroModel > > ( ) ; } this . modelTransformers . add ( modelTransformer ) ; return this ; }


public void serveProcessedBundle()
      throws IOException {
    final WroConfiguration configuration = context.getConfig();
    final HttpServletRequest request = context.getRequest();
    final HttpServletResponse response = context.getResponse();

    OutputStream os = null;
    try {

      final CacheKey cacheKey = getSafeCacheKey(request);
      initAggregatedFolderPath(request, cacheKey.getType());
      final CacheValue cacheValue = cacheStrategy.get(cacheKey);

      // TODO move ETag check in wroManagerFactory
      final String ifNoneMatch = request.getHeader(HttpHeader.IF_NONE_MATCH.toString());

      // enclose etag value in quotes to be compliant with the RFC
      final String etagValue = String.format("\"%s\"", cacheValue.getHash());

      if (etagValue != null && etagValue.equals(ifNoneMatch)) {
        LOG.debug("ETag hash detected: {}. Sending {} status code", etagValue, HttpServletResponse.SC_NOT_MODIFIED);
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        // because we cannot return null, return a stream containing nothing.
        // TODO close output stream?
        return;
      }
      /**
       * Set contentType before actual content is written, solves

       * issue341
       */
      response.setContentType(cacheKey.getType().getContentType() + "; charset=" + configuration.getEncoding());
      // set ETag header
      response.setHeader(HttpHeader.ETAG.toString(), etagValue);

      os = response.getOutputStream();
      if (cacheValue.getRawContent() != null) {
        // use gziped response if supported & Set content length based on gzip flag
        if (isGzipAllowed()) {
          response.setContentLength(cacheValue.getGzippedContent().length);
          // add gzip header and gzip response
          response.setHeader(HttpHeader.CONTENT_ENCODING.toString(), "gzip");
          response.setHeader("Vary", "Accept-Encoding");
          IOUtils.write(cacheValue.getGzippedContent(), os);
        } else {
          //using getRawContent().length() is not the same and can return 2Bytes smaller size.
          response.setContentLength(cacheValue.getRawContent().getBytes(configuration.getEncoding()).length);
          IOUtils.write(cacheValue.getRawContent(), os, configuration.getEncoding());
        }
      }
    } finally {
      if (os != null) {
        IOUtils.closeQuietly(os);
      }
    }
  }

Write to stream the content of the processed resource bundle.

Write to stream the content of the processed resource bundle .


private void initAggregatedFolderPath(final HttpServletRequest request, final ResourceType type) {
    if (ResourceType.CSS == type && context.getAggregatedFolderPath() == null) {
      final String requestUri = request.getRequestURI();
      final String cssFolder = StringUtils.removeEnd(requestUri, FilenameUtils.getName(requestUri));
      final String aggregatedFolder = StringUtils.removeStart(cssFolder, request.getContextPath());
      LOG.debug("set aggregatedFolderPath: {}", aggregatedFolder);
      Context.get().setAggregatedFolderPath(aggregatedFolder);
    }
  }

private
void
initAggregatedFolderPath
(
final
HttpServletRequest
request
,
final
ResourceType
type
)
{
if
(
ResourceType
.
CSS
==
type
&&
context
.
getAggregatedFolderPath
(
)
==
)
{
final
String
requestUri
=
request
.
getRequestURI
(
)
;
final
String
cssFolder
=
StringUtils
.
removeEnd
(
requestUri
,
FilenameUtils
.
getName
(
requestUri
)
)
;
final
String
aggregatedFolder
=
StringUtils
.
removeStart
(
cssFolder
,
request
.
getContextPath
(
)
)
;
LOG
.
debug
(
"set aggregatedFolderPath: {}"
,
aggregatedFolder
)
;
Context
.
get
(
)
.
setAggregatedFolderPath
(
aggregatedFolder
)
;
}
}

Set the aggregatedFolderPath if required.

Set the aggregatedFolderPath if required .


private Map<String, String> extractVariables(final String variablesBody) {
    final Map<String, String> map = new HashMap<String, String>();
    final Matcher m = PATTERN_VARIABLES_BODY.matcher(variablesBody);
    LOG.debug("parsing variables body");
    while (m.find()) {
      final String key = m.group(1);
      final String value = m.group(2);
      if (map.containsKey(key)) {
        LOG.warn("A duplicate variable name found with name: {} and value: {}.", key, value);
      }
      map.put(key, value);
    }
    return map;
  }

Extract variables map from variables body.

@param variablesBody
string containing variables mappings.
@return map with extracted variables.

Extract variables map from variables body .


private String parseCss(final String css) {
    // map containing variables & their values
    final Map<String, String> map = new HashMap<String, String>();
    final StringBuffer sb = new StringBuffer();
    final Matcher m = PATTERN_VARIABLES_DEFINITION.matcher(css);
    while (m.find()) {
      final String variablesBody = m.group(1);
      // LOG.debug("variables body: " + variablesBody);
      // extract variables
      map.putAll(extractVariables(variablesBody));
      // remove variables definition
      m.appendReplacement(sb, "");
    }
    m.appendTail(sb);

    // LOG.debug("replaced variables: " + result);
    return replaceVariables(sb.toString(), map);
  }

Parse css, find all defined variables & replace them.

@param css
to parse.

Parse css find all defined variables & replace them .


private String replaceVariables(final String css, final Map<String, String> variables) {
    final StringBuffer sb = new StringBuffer();
    final Matcher m = PATTERN_VARIABLE_HOLDER.matcher(css);
    while (m.find()) {
      final String oldMatch = m.group();
      final String variableName = m.group(1);
      final String variableValue = variables.get(variableName);
      if (variableValue != null) {
        final String newReplacement = oldMatch.replace(oldMatch, variableValue);
        m.appendReplacement(sb, newReplacement.trim());
      } else {
        LOG.warn("No variable with name " + variableName + " was found!");
      }
    }
    m.appendTail(sb);
    return sb.toString();
  }

private
String
replaceVariables
(
final
String
css
,
final
Map
<
String
,
String
>
variables
)
{
final
StringBuffer
sb
=
new
StringBuffer
(
)
;
final
Matcher
m
=
PATTERN_VARIABLE_HOLDER
.
matcher
(
css
)
;
while
(
m
.
find
(
)
)
{
final
String
oldMatch
=
m
.
group
(
)
;
final
String
variableName
=
m
.
group
(
1
)
;
final
String
variableValue
=
variables
.
get
(
variableName
)
;
if
(
variableValue
!=
)
{
final
String
newReplacement
=
oldMatch
.
replace
(
oldMatch
,
variableValue
)
;
m
.
appendReplacement
(
sb
,
newReplacement
.
trim
(
)
)
;
}
else
{
LOG
.
warn
(
"No variable with name "
+
variableName
+
" was found!"
)
;
}
}
m
.
appendTail
(
sb
)
;
return
sb
.
toString
(
)
;
}

Replace variables from css with provided variables map.

@param cleanCss
used to replace variable placeholders.
@param variables
map of variables used for substitution.
@return css with all variables replaced.

Replace variables from css with provided variables map .


private static ResourcePreProcessor toPreProcessor(final ResourcePostProcessor postProcessor) {
    return new AbstractProcessorDecoratorSupport<ResourcePostProcessor>(postProcessor) {
      public void process(final Resource resource, final Reader reader, final Writer writer)
          throws IOException {
        postProcessor.process(reader, writer);
      }

      @Override
      protected boolean isMinimizeInternal() {
        return isMinimizeForProcessor(postProcessor);
      }

      @Override
      protected SupportedResourceType getSupportedResourceTypeInternal() {
        return getSupportedResourceTypeForProcessor(postProcessor);
      }

      @Override
      public String toString() {
        return postProcessor.toString();
      }
    };
  }

private static ResourcePreProcessor toPreProcessor ( final ResourcePostProcessor postProcessor ) { return new AbstractProcessorDecoratorSupport < ResourcePostProcessor > ( postProcessor ) { public void process ( final Resource resource , final Reader reader , final Writer writer ) throws IOException { postProcessor . process ( reader , writer ) ; } @ Override protected boolean isMinimizeInternal ( ) { return isMinimizeForProcessor ( postProcessor ) ; } @ Override protected SupportedResourceType getSupportedResourceTypeInternal ( ) { return getSupportedResourceTypeForProcessor ( postProcessor ) ; } @ Override public String toString ( ) { return postProcessor . toString ( ) ; } } ; }

Transforms a post processor into pre processor.

Transforms a post processor into pre processor .


public final boolean isEligible(final boolean minimize, final ResourceType searchedType) {
    Validate.notNull(searchedType);

    final SupportedResourceType supportedType = getSupportedResourceType();
    final boolean isTypeSatisfied = supportedType == null
        || (supportedType != null && searchedType == supportedType.value());
    final boolean isMinimizedSatisfied = minimize == true || !isMinimize();
    return isTypeSatisfied && isMinimizedSatisfied;
  }

public final boolean isEligible ( final boolean minimize , final ResourceType searchedType ) { Validate . notNull ( searchedType ) ; final SupportedResourceType supportedType = getSupportedResourceType ( ) ; final boolean isTypeSatisfied = supportedType == null || ( supportedType != null && searchedType == supportedType . value ( ) ) ; final boolean isMinimizedSatisfied = minimize == true || ! isMinimize ( ) ; return isTypeSatisfied && isMinimizedSatisfied ; }

Indicates if the processor is eligible for usage based on provided criteria.

@param minimize
- when true the processor should be minimize aware.
@param searchedType
- the type of the accepted processor. If the processor will have no type specified it will still be
eligible.
@return true if this processor is of searchedType.

Indicates if the processor is eligible for usage based on provided criteria .


private void doGzipResponse(final HttpServletRequest req, final HttpServletResponse response, final FilterChain chain)
      throws IOException, ServletException {
    LOG.debug("Applying gzip on resource: " + req.getRequestURI());
    response.setHeader(HttpHeader.CONTENT_ENCODING.toString(), "gzip");
    final ByteArrayOutputStream baos = new ByteArrayOutputStream();
    final CountingOutputStream countingStream = new CountingOutputStream(new GZIPOutputStream(new BufferedOutputStream(
        baos)));
    // final GZIPOutputStream gzout = new GZIPOutputStream(new BufferedOutputStream(baos));
    // Perform gzip operation in-memory before sending response
    final HttpServletResponseWrapper wrappedResponse = new RedirectedStreamServletResponseWrapper(countingStream,
        response);
    chain.doFilter(req, wrappedResponse);
    // close underlying stream
    countingStream.close();
    response.setContentLength(countingStream.getCount());
    // avoid NO CONTENT error thrown by jetty when gzipping empty response
    if (countingStream.getCount() > 0) {
      IOUtils.write(baos.toByteArray(), response.getOutputStream());
    }
  }

Performs actual gzip of the filtered content.

Performs actual gzip of the filtered content .


public static PathPatternProcessorDecorator include(final Object processor, final String... patterns) {
    return new PathPatternProcessorDecorator(processor, true, patterns);
  }

public static PathPatternProcessorDecorator include ( final Object processor , final String ... patterns ) { return new PathPatternProcessorDecorator ( processor , true , patterns ) ; }

Decorates a processor which will be applied on provided patterns.

@param processor
The processor to decorate.
@param patterns
an array of patterns on which the processor will be applied.
@return decorated processor.

Decorates a processor which will be applied on provided patterns .


public static PathPatternProcessorDecorator exclude(final Object processor, final String... patterns) {
    return new PathPatternProcessorDecorator(processor, false, patterns);
  }

public static PathPatternProcessorDecorator exclude ( final Object processor , final String ... patterns ) { return new PathPatternProcessorDecorator ( processor , false , patterns ) ; }

Decorates a processor which will not be applied on provided patterns.

@param processor
The processor to decorate.
@param patterns
an array of patterns on which the processor will not be applied.
@return decorated processor.

Decorates a processor which will not be applied on provided patterns .


public static ResourceChangeHandler create(final WroManagerFactory managerFactory, final Log log) {
    notNull(managerFactory, "WroManagerFactory was not set");
    notNull(log, "Log was not set");
    return new ResourceChangeHandler().setManagerFactory(managerFactory).setLog(log);
  }

public
static
ResourceChangeHandler
create
(
final
WroManagerFactory
managerFactory
,
final
Log
log
)
{
notNull
(
managerFactory
,
"WroManagerFactory was not set"
)
;
notNull
(
log
,
"Log was not set"
)
;
return
new
ResourceChangeHandler
(
)
.
setManagerFactory
(
managerFactory
)
.
setLog
(
log
)
;
}

Factory method which requires all mandatory fields.

Factory method which requires all mandatory fields .


public void remember(final Resource resource) {
    final WroManager manager = getManagerFactory().create();
    final HashStrategy hashStrategy = manager.getHashStrategy();
    final UriLocatorFactory locatorFactory = manager.getUriLocatorFactory();

    if (rememberedSet.contains(resource.getUri())) {
      // only calculate fingerprints and check imports if not already done
      getLog().debug("Resource with uri '" + resource.getUri() + "' has already been updated in this run.");
    } else {
      try {
        final String fingerprint = hashStrategy.getHash(locatorFactory.locate(resource.getUri()));
        getBuildContextHolder().setValue(resource.getUri(), fingerprint);
        rememberedSet.add(resource.getUri());
        getLog().debug("Persist fingerprint for resource '" + resource.getUri() + "' : " + fingerprint);
        if (resource.getType() == ResourceType.CSS) {
          final Reader reader = new InputStreamReader(locatorFactory.locate(resource.getUri()));
          getLog().debug("Check @import directive from " + resource);
          // persist fingerprints in imported resources.
          persistFingerprintsForCssImports(resource, reader);
        }
      } catch (final IOException e) {
        getLog().debug("could not check fingerprint of resource: " + resource);
      }
    }
  }

Will persist the information regarding the provided resource in some internal store. This information will be used
later to check if the resource is changed.

@param resource
{@link Resource} to touch.

Will persist the information regarding the provided resource in some internal store . This information will be used later to check if the resource is changed .


private void forEachCssImportApply(final Function<String, ChangeStatus> func, final Resource resource, final Reader reader)
      throws IOException {
    final ResourcePreProcessor processor = createCssImportProcessor(func);
    InjectorBuilder.create(getManagerFactory()).build().inject(processor);
    processor.process(resource, reader, new StringWriter());
  }

private void forEachCssImportApply ( final Function < String , ChangeStatus > func , final Resource resource , final Reader reader ) throws IOException { final ResourcePreProcessor processor = createCssImportProcessor ( func ) ; InjectorBuilder . create ( getManagerFactory ( ) ) . build ( ) . inject ( processor ) ; processor . process ( resource , reader , new StringWriter ( ) ) ; }

Invokes the provided function for each detected css import.

@param func
a function (closure) invoked for each found import. It will be provided as argument the uri of imported
css.

Invokes the provided function for each detected css import .


private List<T> filter(final Collection<T> collection) {
    final List<T> nullFreeList = new ArrayList<T>();
    if (collection != null) {
      for (final T item : collection) {
        if (item != null) {
          nullFreeList.add(item);
        }
      }
    }
    return nullFreeList;
  }

private List < T > filter ( final Collection < T > collection ) { final List < T > nullFreeList = new ArrayList < T > ( ) ; if ( collection != null ) { for ( final T item : collection ) { if ( item != null ) { nullFreeList . add ( item ) ; } } } return nullFreeList ; }

This filtering is required, in order to ensure that no nulls are passed (which happens when using gson for
deserializing json collection.

@param collection
to filter.
@return a null free collection of item by filtering found nulls.

This filtering is required in order to ensure that no nulls are passed ( which happens when using gson for deserializing json collection .


public boolean isMinimized(final HttpServletRequest request) {
    Validate.notNull(request);
    final String minimizeAsString = request.getParameter(PARAM_MINIMIZE);
    return !(Context.get().getConfig().isDebug() && "false".equalsIgnoreCase(minimizeAsString));
  }

public boolean isMinimized ( final HttpServletRequest request ) { Validate . notNull ( request ) ; final String minimizeAsString = request . getParameter ( PARAM_MINIMIZE ) ; return ! ( Context . get ( ) . getConfig ( ) . isDebug ( ) && "false" . equalsIgnoreCase ( minimizeAsString ) ) ; }

The minimization is can be switched off only in debug mode.

@return false if the request contains parameter {@link DefaultGroupExtractor#PARAM_MINIMIZE} with value false,
otherwise returns true.

The minimization is can be switched off only in debug mode .


private List<Resource> findImportedResources(final String resourceUri, final String cssContent)
    throws IOException {
    // it should be sorted
    final List<Resource> imports = new ArrayList<Resource>();
    final String css = cssContent;
    final List<String> foundImports = findImports(css);
    for (final String importUrl : foundImports) {
      final Resource importedResource = createImportedResource(resourceUri, importUrl);
      // check if already exist
      if (imports.contains(importedResource)) {
        LOG.debug("[WARN] Duplicate imported resource: {}", importedResource);
      } else {
        imports.add(importedResource);
        onImportDetected(importedResource.getUri());
      }
    }
    return imports;
  }

Find a set of imported resources inside a given resource.

Find a set of imported resources inside a given resource .


private String computeAbsoluteUrl(final String relativeResourceUri, final String importUrl) {
    final String folder = WroUtil.getFullPath(relativeResourceUri);
    // remove '../' & normalize the path.
    return StringUtils.cleanPath(folder + importUrl);
  }

private
String
computeAbsoluteUrl
(
final
String
relativeResourceUri
,
final
String
importUrl
)
{
final
String
folder
=
WroUtil
.
getFullPath
(
relativeResourceUri
)
;
// remove '../' & normalize the path.
return
StringUtils
.
cleanPath
(
folder
+
importUrl
)
;
}

Computes absolute url of the imported resource.

@param relativeResourceUri uri of the resource containing the import statement.
@param importUrl found import url.
@return absolute url of the resource to import.

Computes absolute url of the imported resource .


public static String createItemsAsString(final String... items) {
    final StringBuffer sb = new StringBuffer();
    for (int i = 0; i < items.length; i++) {
      sb.append(items[i]);
      if (i < items.length - 1) {
        sb.append(TOKEN_DELIMITER);
      }
    }
    return sb.toString();
  }

public static String createItemsAsString ( final String ... items ) { final StringBuffer sb = new StringBuffer ( ) ; for ( int i = 0 ; i < items . length ; i ++ ) { sb . append ( items [ i ] ) ; if ( i < items . length - 1 ) { sb . append ( TOKEN_DELIMITER ) ; } } return sb . toString ( ) ; }

Creates a comma separated list of items.

@VisibleForTesting

Creates a comma separated list of items .


private List<String> getAliasList(final String aliasCsv) {
    LOG.debug("configured aliases: {}", aliasCsv);
    final List<String> list = new ArrayList<String>();
    if (!StringUtils.isEmpty(aliasCsv)) {
      final String[] tokens = aliasCsv.split(TOKEN_DELIMITER);
      for (final String token : tokens) {
        list.add(token.trim());
      }
    }
    return list;
  }

private
List
<
String
>
getAliasList
(
final
String
aliasCsv
)
{
LOG
.
debug
(
"configured aliases: {}"
,
aliasCsv
)
;
final
List
<
String
>
list
=
new
ArrayList
<
String
>
(
)
;
if
(
!
StringUtils
.
isEmpty
(
aliasCsv
)
)
{
final
String
[
]
tokens
=
aliasCsv
.
split
(
TOKEN_DELIMITER
)
;
for
(
final
String
token
:
tokens
)
{
list
.
add
(
token
.
trim
(
)
)
;
}
}
return
list
;
}

Creates a list of aliases based on provided string containing comma separated values of aliases.

@param aliasCsv
string representation of tokens separated by ',' character.
@return a list of non empty strings.

Creates a list of aliases based on provided string containing comma separated values of aliases .


private ScriptableObject createContext(final ScriptableObject initialScope) {
    final Context context = getContext();
    context.setOptimizationLevel(-1);
    // TODO redirect errors from System.err to LOG.error()
    context.setErrorReporter(new ToolErrorReporter(false));
    context.setLanguageVersion(Context.VERSION_1_8);
    InputStream script = null;
    final ScriptableObject scriptCommon = (ScriptableObject) context.initStandardObjects(initialScope);
    try {
      script = new AutoCloseInputStream(getClass().getResourceAsStream("commons.js"));
      context.evaluateReader(scriptCommon, new InputStreamReader(script), "commons.js", 1, null);
    } catch (final IOException e) {
      throw new RuntimeException("Problem while evaluationg commons script.", e);
    } finally {
      IOUtils.closeQuietly(script);
    }
    return scriptCommon;
  }


public Object evaluate(final Reader reader, final String sourceName)
    throws IOException {
    notNull(reader);
    try {
      return evaluate(IOUtils.toString(reader), sourceName);
    } finally {
      reader.close();
    }
  }

public Object evaluate ( final Reader reader , final String sourceName ) throws IOException { notNull ( reader ) ; try { return evaluate ( IOUtils . toString ( reader ) , sourceName ) ; } finally { reader . close ( ) ; } }

Evaluates a script from a reader.

@param reader {@link Reader} of the script to evaluate.
@param sourceName the name of the evaluated script.
@return evaluated object.
@throws IOException if the script couldn't be retrieved.


public Object evaluate(final String script, final String sourceName) {
    notNull(script);
    // make sure we have a context associated with current thread
    try {
      return getContext().evaluateString(scope, script, sourceName, 1, null);
    } catch (final RhinoException e) {
      final String message = RhinoUtils.createExceptionMessage(e);
      LOG.error("JavaScriptException occured: {}", message);
      throw new WroRuntimeException(message);
    } finally {
      // Rhino throws an exception when trying to exit twice. Make sure we don't get any exception
      if (Context.getCurrentContext() != null) {
        Context.exit();
      }
    }
  }

Evaluates a script.

@param script string representation of the script to evaluate.
@param sourceName the name of the evaluated script.
@return evaluated object.
@throws IOException if the script couldn't be retrieved.


public final void process()
      throws IOException {
    // reschedule cache & model updates
    final WroConfiguration config = Context.get().getConfig();
    cacheSchedulerHelper.scheduleWithPeriod(config.getCacheUpdatePeriod());
    modelSchedulerHelper.scheduleWithPeriod(config.getModelUpdatePeriod());
    resourceBundleProcessor.serveProcessedBundle();
  }

public
final
void
process
(
)
throws
IOException
{
// reschedule cache & model updates
final
WroConfiguration
config
=
Context
.
get
(
)
.
getConfig
(
)
;
cacheSchedulerHelper
.
scheduleWithPeriod
(
config
.
getCacheUpdatePeriod
(
)
)
;
modelSchedulerHelper
.
scheduleWithPeriod
(
config
.
getModelUpdatePeriod
(
)
)
;
resourceBundleProcessor
.
serveProcessedBundle
(
)
;
}

Perform processing of the uri.

@throws IOException
when any IO related problem occurs or if the request cannot be processed.


private boolean isHandlerRequest(final HttpServletRequest request) {
    String apiHandlerValue = request.getParameter(PATH_API);
    return PATH_HANDLER.equals(apiHandlerValue) && retrieveCacheKey(request) != null;
  }

private boolean isHandlerRequest ( final HttpServletRequest request ) { String apiHandlerValue = request . getParameter ( PATH_API ) ; return PATH_HANDLER . equals ( apiHandlerValue ) && retrieveCacheKey ( request ) != null ; }

Checks if the provided url is a resource proxy request.

@param url
to check.
@return true if the provided url is a proxy resource.

Checks if the provided url is a resource proxy request .


public static String createHandlerRequestPath(final CacheKey cacheKey, final HttpServletRequest request) {
    final String handlerQueryPath = getRequestHandlerPath(cacheKey.getGroupName(), cacheKey.getType());
    return request.getServletPath() + handlerQueryPath;
  }

public static String createHandlerRequestPath ( final CacheKey cacheKey , final HttpServletRequest request ) { final String handlerQueryPath = getRequestHandlerPath ( cacheKey . getGroupName ( ) , cacheKey . getType ( ) ) ; return request . getServletPath ( ) + handlerQueryPath ; }

Computes the servlet context relative url to call this handler using a server-side invocation. Hides the details
about creating a valid url and providing the authorization key required to invoke this handler.

Computes the servlet context relative url to call this handler using a server - side invocation . Hides the details about creating a valid url and providing the authorization key required to invoke this handler .


private String rename(final String group, final InputStream input)
      throws Exception {
    try {
      final String newName = getManagerFactory().create().getNamingStrategy().rename(group, input);
      groupNames.setProperty(group, newName);
      return newName;
    } catch (final IOException e) {
      throw new MojoExecutionException("Error occured during renaming", e);
    }
  }

private
String
rename
(
final
String
group
,
final
InputStream
input
)
throws
Exception
{
try
{
final
String
newName
=
getManagerFactory
(
)
.
create
(
)
.
getNamingStrategy
(
)
.
rename
(
group
,
input
)
;
groupNames
.
setProperty
(
group
,
newName
)
;
return
newName
;
}
catch
(
final
IOException
e
)
{
throw
new
MojoExecutionException
(
"Error occured during renaming"
,
e
)
;
}
}

Encodes a version using some logic.

@param group
the name of the resource to encode.
@param input
the stream of the result content.
@return the name of the resource with the version encoded.


private File computeDestinationFolder(final ResourceType resourceType)
      throws MojoExecutionException {
    File folder = destinationFolder;
    if (resourceType == ResourceType.JS) {
      if (jsDestinationFolder != null) {
        folder = jsDestinationFolder;
      }
    }
    if (resourceType == ResourceType.CSS) {
      if (cssDestinationFolder != null) {
        folder = cssDestinationFolder;
      }
    }
    getLog().info("folder: " + folder);
    if (folder == null) {
      throw new MojoExecutionException("Couldn't compute destination folder for resourceType: " + resourceType
          + ". That means that you didn't define one of the following parameters: "
          + "destinationFolder, cssDestinationFolder, jsDestinationFolder");
    }
    if (!folder.exists()) {
      folder.mkdirs();
    }
    return folder;
  }

Computes the destination folder based on resource type.

@param resourceType
{@link ResourceType} to process.
@return destinationFoder where the result of resourceType will be copied.
@throws MojoExecutionException
if computed folder is null.

Computes the destination folder based on resource type .


private void processGroup(final String group, final File parentFoder)
      throws Exception {
    ByteArrayOutputStream resultOutputStream = null;
    InputStream resultInputStream = null;
    try {
      getLog().info("processing group: " + group);

      // mock request
      final HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
      Mockito.when(request.getContextPath()).thenReturn(normalizeContextPath(contextPath));
      Mockito.when(request.getRequestURI()).thenReturn(group);
      // mock response
      final HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
      resultOutputStream = new ByteArrayOutputStream();
      Mockito.when(response.getOutputStream()).thenReturn(new DelegatingServletOutputStream(resultOutputStream));

      // init context
      final WroConfiguration config = Context.get().getConfig();
      // the maven plugin should ignore empty groups, since it will try to process all types of resources.
      config.setIgnoreEmptyGroup(true);
      Context.set(Context.webContext(request, response, Mockito.mock(FilterConfig.class)), config);

      Context.get().setAggregatedFolderPath(getAggregatedPathResolver().resolve());
      // perform processing
      getManagerFactory().create().process();
      // encode version & write result to file
      resultInputStream = new UnclosableBufferedInputStream(resultOutputStream.toByteArray());
      final File destinationFile = new File(parentFoder, rename(group, resultInputStream));
      final File parentFolder = destinationFile.getParentFile();
      if (!parentFolder.exists()) {
        // make directories if required
        parentFolder.mkdirs();
      }
      destinationFile.createNewFile();
      // allow the same stream to be read again
      resultInputStream.reset();
      getLog().debug("Created file: " + destinationFile.getName());

      final OutputStream fos = new FileOutputStream(destinationFile);
      // use reader to detect encoding
      IOUtils.copy(resultInputStream, fos);
      fos.close();
      // delete empty files
      if (destinationFile.length() == 0) {
        getLog().debug("No content found for group: " + group);
        destinationFile.delete();
      } else {
        getLog().info("file size: " + destinationFile.getName() + " -> " + destinationFile.length() + " bytes");
        getLog().info(destinationFile.getAbsolutePath() + " (" + destinationFile.length() + " bytes" + ")");
      }
    } finally {
      // instruct the build about the change in context of incremental build
      if (getBuildContext() != null) {
        getBuildContext().refresh(parentFoder);
      }
      if (resultOutputStream != null) {
        resultOutputStream.close();
      }
      if (resultInputStream != null) {
        resultInputStream.close();
      }
    }
  }


public boolean checkChangeForGroup(final String uri, final String groupName) throws IOException {
    notNull(uri);
    notNull(groupName);
    LOG.debug("group={}, uri={}", groupName, uri);
    final ResourceChangeInfo resourceInfo = changeInfoMap.get(uri);
    if (resourceInfo.isCheckRequiredForGroup(groupName)) {
      final InputStream inputStream = locatorFactory.locate(uri);
      try{
        final String currentHash = hashStrategy.getHash(inputStream);
        resourceInfo.updateHashForGroup(currentHash, groupName);
      }finally{
        IOUtils.closeQuietly(inputStream);
      }
    }
    return resourceInfo.isChanged(groupName);
  }

public
boolean
checkChangeForGroup
(
final
String
uri
,
final
String
groupName
)
throws
IOException
{
notNull
(
uri
)
;
notNull
(
groupName
)
;
LOG
.
debug
(
"group={}, uri={}"
,
groupName
,
uri
)
;
final
ResourceChangeInfo
resourceInfo
=
changeInfoMap
.
get
(
uri
)
;
if
(
resourceInfo
.
isCheckRequiredForGroup
(
groupName
)
)
{
final
InputStream
inputStream
=
locatorFactory
.
locate
(
uri
)
;
try
{
final
String
currentHash
=
hashStrategy
.
getHash
(
inputStream
)
;
resourceInfo
.
updateHashForGroup
(
currentHash
,
groupName
)
;
}
finally
{
IOUtils
.
closeQuietly
(
inputStream
)
;
}
}
return
resourceInfo
.
isChanged
(
groupName
)
;
}

Check if an uri from a particular group has changed.

@param uri
the uri to check for change.
@param groupName
the name of the group where the uri belongs to.
@return true if the change is detected for the provided uri.

Check if an uri from a particular group has changed .


@Override
  public InputStream locate(final String uri)
      throws IOException {
    validState(standaloneContext != null, "Locator was not initialized properly. StandaloneContext missing.");

    Exception lastException = null;
    final String[] contextFolders = standaloneContext.getContextFolders();
    for(final String contextFolder : contextFolders) {
      try {
        return locateStreamWithContextFolder(uri, contextFolder);
      } catch(final IOException e) {
        lastException = e;
        LOG.debug("Could not locate: {} using contextFolder: {}", uri, contextFolder);
      }
    }
    final String exceptionMessage = String.format("No valid resource '%s' found inside any of contextFolders: %s", uri,
        Arrays.toString(standaloneContext.getContextFolders()));
    throw new IOException(exceptionMessage, lastException);
  }

This implementation will try to locate the provided resource inside contextFolder configured by standaloneContext.
If a resource cannot be located, the next contextFolder from the list will be tried. The first successful result
will be returned.

This implementation will try to locate the provided resource inside contextFolder configured by standaloneContext . If a resource cannot be located the next contextFolder from the list will be tried . The first successful result will be returned .


private GenericObjectPool<T> createObjectPool(final ObjectFactory<T> objectFactory) {
    final GenericObjectPool<T> pool = newObjectPool(objectFactory);
    notNull(pool);
    return pool;
  }

private GenericObjectPool < T > createObjectPool ( final ObjectFactory < T > objectFactory ) { final GenericObjectPool < T > pool = newObjectPool ( objectFactory ) ; notNull ( pool ) ; return pool ; }

Ensure that a not null pool will be created.

Ensure that a not null pool will be created .


@Override
  public InputStream locateStream(final String uri, final File folder)
      throws IOException {
    notNull(folder);
    final File jarPath = getJarFile(folder);
    if (isSupported(jarPath)) {
      return locateStreamFromJar(uri, jarPath);
    }
    return super.locateStream(uri, folder);
  }

@ Override public InputStream locateStream ( final String uri , final File folder ) throws IOException { notNull ( folder ) ; final File jarPath = getJarFile ( folder ) ; if ( isSupported ( jarPath ) ) { return locateStreamFromJar ( uri , jarPath ) ; } return super . locateStream ( uri , folder ) ; }

Finds the specified URI pattern inside a JAR file. If the specified file isn't a valid JAR default strategy will be
used instead.

Finds the specified URI pattern inside a JAR file . If the specified file isn t a valid JAR default strategy will be used instead .


JarFile open(final File jarFile) throws IOException {
    isTrue(jarFile.exists(), "The JAR file must exists.");
    return new JarFile(jarFile);
  }

JarFile
open
(
final
File
jarFile
)
throws
IOException
{
isTrue
(
jarFile
.
exists
(
)
,
"The JAR file must exists."
)
;
return
new
JarFile
(
jarFile
)
;
}

Opens the specified JAR file and returns a valid handle.

@param jarFile
Location of the valid JAR file to read. It cannot be null.
@return A valid {@link JarFile} to read resources.
@throws IllegalArgumentException
If the file cannot be opened because an {@link IOException}.
@VisibleForTestOnly

Opens the specified JAR file and returns a valid handle .


private String extractPath(final String uri) {
    return DefaultWildcardStreamLocator.stripQueryPath(uri.replace(PREFIX, StringUtils.EMPTY));
  }

private String extractPath ( final String uri ) { return DefaultWildcardStreamLocator . stripQueryPath ( uri . replace ( PREFIX , StringUtils . EMPTY ) ) ; }

Replaces the protocol specific prefix and removes the query path if it exist, since it should not be accepted.

Replaces the protocol specific prefix and removes the query path if it exist since it should not be accepted .


private boolean isMinimized(final HttpServletRequest request) {
    return context.getConfig().isMinimizeEnabled() ? groupExtractor.isMinimized(request) : false;
  }

private boolean isMinimized ( final HttpServletRequest request ) { return context . getConfig ( ) . isMinimizeEnabled ( ) ? groupExtractor . isMinimized ( request ) : false ; }

Uses isMinimizeEnabled configuration to compute minimize value.

Uses isMinimizeEnabled configuration to compute minimize value .


public final SimpleUriLocatorFactory addLocator(final UriLocator... locators) {
    for (final UriLocator locator : locators) {
      uriLocators.add(locator);
    }
    return this;
  }

public final SimpleUriLocatorFactory addLocator ( final UriLocator ... locators ) { for ( final UriLocator locator : locators ) { uriLocators . add ( locator ) ; } return this ; }

Allow adding more than one uriLocators.

@param locators list of {@link UriLocator} arguments.

Allow adding more than one uriLocators .


public static WroModelFactory decorate(final WroModelFactory decorated,
      final List<Transformer<WroModel>> modelTransformers) {
    return decorated instanceof DefaultWroModelFactoryDecorator ? decorated : new DefaultWroModelFactoryDecorator(
        decorated, modelTransformers);
  }

public static WroModelFactory decorate ( final WroModelFactory decorated , final List < Transformer < WroModel > > modelTransformers ) { return decorated instanceof DefaultWroModelFactoryDecorator ? decorated : new DefaultWroModelFactoryDecorator ( decorated , modelTransformers ) ; }

Factory method which takes care of redundant decoration.

Factory method which takes care of redundant decoration .


public void addRequire(final String require) {
    if (require != null && require.trim().length() > 0) {
      requires.add(require.trim());
    }
  }

public void addRequire ( final String require ) { if ( require != null && require . trim ( ) . length ( ) > 0 ) { requires . add ( require . trim ( ) ) ; } }

Adds a ruby require to the ruby script to be run by this RubySassEngine. It's safe to add the same require twice.

@param require
The name of the require, e.g. bourbon

Adds a ruby require to the ruby script to be run by this RubySassEngine . It s safe to add the same require twice .


public String process(final String content) {
    if (isEmpty(content)) {
      return StringUtils.EMPTY;
    }
    try {
      synchronized(this) {
        return engineInitializer.get().eval(buildUpdateScript(content)).toString();
      }
    } catch (final ScriptException e) {
      throw new WroRuntimeException(e.getMessage(), e);
    }
  }

public String process ( final String content ) { if ( isEmpty ( content ) ) { return StringUtils . EMPTY ; } try { synchronized ( this ) { return engineInitializer . get ( ) . eval ( buildUpdateScript ( content ) ) . toString ( ) ; } } catch ( final ScriptException e ) { throw new WroRuntimeException ( e . getMessage ( ) , e ) ; } }

Transforms a sass content into css using Sass ruby engine. This method is synchronized because the engine itself is
not thread-safe.

@param content
the Sass content to process.

Transforms a sass content into css using Sass ruby engine . This method is synchronized because the engine itself is not thread - safe .


public void logSummary() {
    final String message = totalFoundErrors == 0 ? "No lint errors found." : String.format(
        "Found %s errors in %s files.", totalFoundErrors, totalResourcesWithErrors);
    log.info("----------------------------------------");
    log.info(String.format("Total resources: %s", totalResources));
    log.info(message);
    log.info("----------------------------------------\n");
  }

Logs the summary as it was collected at this point.

Logs the summary as it was collected at this point .


public synchronized void onProcessingResource(final Resource resource) {
    totalResources++;
    log.debug("processing resource: " + resource.getUri());
    if (isLogRequired()) {
      log.info("Processed until now: " + getTotalResources() + ". Last processed: " + resource.getUri());
      updateLastInvocation();
    }
  }

public
synchronized
void
onProcessingResource
(
final
Resource
resource
)
{
totalResources
++
;
log
.
debug
(
"processing resource: "
+
resource
.
getUri
(
)
)
;
if
(
isLogRequired
(
)
)
{
log
.
info
(
"Processed until now: "
+
getTotalResources
(
)
+
". Last processed: "
+
resource
.
getUri
(
)
)
;
updateLastInvocation
(
)
;
}
}

A method which should be invoked on each new resource processing, having as a side effect an increment of the
counter holding the number of total processed resources.

A method which should be invoked on each new resource processing having as a side effect an increment of the counter holding the number of total processed resources .


private ReadWriteLock getLockForKey(final K key) {
    final ReadWriteLock lock = locks.putIfAbsent(key, new ReentrantReadWriteLock());
    return lock == null ? locks.get(key) : lock;
  }

private ReadWriteLock getLockForKey ( final K key ) { final ReadWriteLock lock = locks . putIfAbsent ( key , new ReentrantReadWriteLock ( ) ) ; return lock == null ? locks . get ( key ) : lock ; }

Ensure that the returned lock will never be null.

Ensure that the returned lock will never be null .