diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml
index 254c8761af..e70400b881 100644
--- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml
+++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml
@@ -77,6 +77,12 @@
+
+
+
+
+
+
@@ -128,6 +134,11 @@
+
+
+
+
+
@@ -541,6 +552,70 @@
and np.node_id < #{maxNodeId}
+
+
+
+
+
diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties
index 67d9cbbf16..bb8b842096 100644
--- a/config/alfresco/messages/patch-service.properties
+++ b/config/alfresco/messages/patch-service.properties
@@ -517,3 +517,10 @@ patch.addGroupAuthority.result=\n\Successfully added group authority: {0}
patch.siteAdministrators.description=Adds the 'GROUP_SITE_ADMINISTRATORS' group
patch.alfrescoSearchAdministrators.description=Adds the 'GROUP_ALFRESCO_SEARCH_ADMINISTRATORS' group
+
+patch.surfConfigFolderPatch.description=Adds cm:indexControl aspect to surf-config children
+patch.surfConfigFolderPatch.result=Successfully applied ''cm:indexControl'' aspect to {0} sites'' surf-config folders and their children as well as to the shared surf-config folder(s) and its/their children.
+
+patch.genericBootstrap.result.deferred=The patch has been deferred
+
+patch.asynchrounse.checking=Checking for the asynchronous patch ...
diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml
index cb91500495..1dff82f08e 100644
--- a/config/alfresco/patch/patch-services-context.xml
+++ b/config/alfresco/patch/patch-services-context.xml
@@ -3746,5 +3746,25 @@
-
+
+
+
+ patch.surfConfigFolder
+ patch.surfConfigFolderPatch.description
+ 0
+ 7004
+ 7005
+ false
+ false
+
+ ${system.patch.surfConfigFolder.deferred}
+
+
+
+
+
+
+
+ ${system.patch.surfConfigFolder.deferred}
+
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index 05ba9ac387..382e8d32a5 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -1100,3 +1100,10 @@ system.lockTryTimeout=100
system.lockTryTimeout.DictionaryDAOImpl=2000
system.lockTryTimeout.MessageServiceImpl=${system.lockTryTimeout}
system.lockTryTimeout.PolicyComponentImpl=${system.lockTryTimeout}
+
+#
+# Do we defer running the shared folder patch?
+#
+system.patch.surfConfigFolder.deferred=true
+
+system.patchSurfConfigFolderTrigger.startDelayMinutes=2
\ No newline at end of file
diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml
index 8781906907..6022ad94db 100644
--- a/config/alfresco/scheduled-jobs-context.xml
+++ b/config/alfresco/scheduled-jobs-context.xml
@@ -342,4 +342,31 @@
+
+
+
+ org.alfresco.repo.admin.patch.AsynchronousPatch$AsynchronousPatchJob
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${system.patchSurfConfigFolderTrigger.startDelayMinutes}
+
+
+ 0
+
+
diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties
index 1ea9749996..bea1581482 100644
--- a/config/alfresco/version.properties
+++ b/config/alfresco/version.properties
@@ -23,4 +23,4 @@ version.build=r@scm-revision@-b@build-number@
# Schema number
-version.schema=7004
+version.schema=7005
diff --git a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java
index fd4b094705..337c08e809 100644
--- a/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java
+++ b/source/java/org/alfresco/repo/admin/patch/AbstractPatch.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -94,13 +94,15 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
/** start time * */
long startTime;
- private boolean deferred = false;
+ /** whether the patch must be deferred (not to be executed in bootstrap) or not */
+ private boolean deferred = false;
+
// Does the patch require an enclosing transaction?
private boolean requiresTransaction = true;
/** the service to register ourselves with */
- private PatchService patchService;
+ protected PatchService patchService;
/** used to ensure a unique transaction per execution */
protected TransactionService transactionService;
/** Use this helper to ensure that patches can execute even on a read-only system */
@@ -560,11 +562,7 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
}
/**
- * Apply the patch, regardless of the deferred flag. So if the patch has not run due to it being deferred earlier
- * then this will run it now. Also ignores the "applied" lock. So the patch can be executed many times.
- *
- * @return the patch report
- * @throws PatchException if the patch failed to be applied
+ * {@inheritDoc}
*/
public String applyAsync() throws PatchException
{
@@ -744,13 +742,13 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
this.deferred = deferred;
}
- /*
- *
+ /**
+ * {@inheritDoc}
*/
public boolean isDeferred()
{
- return deferred;
- }
+ return this.deferred;
+ }
private int getReportingInterval(long soFar, long toGo)
{
diff --git a/source/java/org/alfresco/repo/admin/patch/AsynchronousPatch.java b/source/java/org/alfresco/repo/admin/patch/AsynchronousPatch.java
new file mode 100644
index 0000000000..1bc1c2c1f2
--- /dev/null
+++ b/source/java/org/alfresco/repo/admin/patch/AsynchronousPatch.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+
+package org.alfresco.repo.admin.patch;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.lock.JobLockService;
+import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+import org.springframework.extensions.surf.util.I18NUtil;
+
+/**
+ * Base implementation of the asynchronous patch.
+ *
+ * @author Jamal Kaabi-Mofrad
+ */
+public abstract class AsynchronousPatch extends AbstractPatch
+{
+ private static final Log logger = LogFactory.getLog(AsynchronousPatch.class);
+
+ private static final String JOB_NAME = "asynchronousPatch";
+
+ private static final String MSG_CHECKING = "patch.asynchrounse.checking";
+ private static final String MSG_NO_PATCHES_REQUIRED = "patch.executer.no_patches_required";
+ private static final String MSG_SYSTEM_READ_ONLY = "patch.executer.system_readonly";
+ private static final String MSG_NOT_EXECUTED = "patch.executer.not_executed";
+ private static final String MSG_EXECUTED = "patch.executer.executed";
+ private static final String MSG_FAILED = "patch.executer.failed";
+
+ private static final long LOCK_TIME_TO_LIVE = 10000;
+ private static final long LOCK_REFRESH_TIME = 5000;
+
+ private JobLockService jobLockService;
+
+ /**
+ * @param jobLockService the jobLockService to set
+ */
+ public void setJobLockService(JobLockService jobLockService)
+ {
+ this.jobLockService = jobLockService;
+ }
+
+ @Override
+ protected void checkProperties()
+ {
+ super.checkProperties();
+ checkPropertyNotNull(jobLockService, "jobLockService");
+ }
+
+ public void executeAsynchronously()
+ {
+ // Lock the push
+ QName lockQName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, this.getId());
+ String lockToken = jobLockService.getLock(lockQName, LOCK_TIME_TO_LIVE, 0, 1);
+ AsyncPatchCallback callback = new AsyncPatchCallback();
+ jobLockService.refreshLock(lockToken, lockQName, LOCK_REFRESH_TIME, callback);
+
+ try
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(this.getId() + ": job lock held");
+ }
+ applyOutstandingPatch(this);
+ }
+ finally
+ {
+ if (logger.isTraceEnabled())
+ {
+ logger.trace(this.getId() + ": job finished");
+ }
+
+ // Release the locks on the job and stop refreshing
+ callback.isActive = false;
+ jobLockService.releaseLock(lockToken, lockQName);
+ }
+ }
+
+ private void applyOutstandingPatch(Patch patch)
+ {
+ // Apply the patch even if we are in read only mode. The system may not
+ // work safely otherwise.
+
+ if (!patchService.validatePatch(patch))
+ {
+ logger.warn(I18NUtil.getMessage(MSG_SYSTEM_READ_ONLY));
+ return;
+ }
+
+ logger.info(I18NUtil.getMessage(MSG_CHECKING));
+
+ AppliedPatch appliedPatch = patchService.getPatch(this.getId());
+ // Don't bother if the patch has already been applied successfully
+ if (appliedPatch != null && appliedPatch.getSucceeded())
+ {
+ logger.info(I18NUtil.getMessage(MSG_NO_PATCHES_REQUIRED));
+ return;
+ }
+
+ patchService.applyOutstandingPatch(this);
+
+ // get the executed patch
+ appliedPatch = patchService.getPatch(patch.getId());
+
+ if (!appliedPatch.getWasExecuted())
+ {
+ // the patch was not executed
+ logger.debug(I18NUtil.getMessage(MSG_NOT_EXECUTED, appliedPatch.getId(), appliedPatch.getReport()));
+ }
+ else if (appliedPatch.getSucceeded())
+ {
+ logger.info(I18NUtil.getMessage(MSG_EXECUTED, appliedPatch.getId(), appliedPatch.getReport()));
+ }
+ else
+ {
+ logger.error(I18NUtil.getMessage(MSG_FAILED, appliedPatch.getId(), appliedPatch.getReport()));
+ throw new AlfrescoRuntimeException("Not all patches could be applied.");
+ }
+ }
+
+ /**
+ * Job to initiate the {@link AsynchronousPatch} if it has been deferred
+ *
+ * @author Jamal Kaabi-Mofrad
+ */
+ public static class AsynchronousPatchJob implements Job
+ {
+ public AsynchronousPatchJob()
+ {
+ }
+
+ @Override
+ public void execute(JobExecutionContext context) throws JobExecutionException
+ {
+ JobDataMap jobData = context.getJobDetail().getJobDataMap();
+ // extract the object to use
+ Object asyncPatchObj = jobData.get(JOB_NAME);
+ if (asyncPatchObj == null || !(asyncPatchObj instanceof AsynchronousPatch))
+ {
+ throw new AlfrescoRuntimeException(JOB_NAME + " data must contain valid 'AsynchronousPatch' reference");
+ }
+ // Job Lock
+ AsynchronousPatch patch = (AsynchronousPatch) asyncPatchObj;
+ patch.executeAsynchronously();
+ }
+ }
+
+ /**
+ * @author Jamal Kaabi-Mofrad
+ */
+ private class AsyncPatchCallback implements JobLockRefreshCallback
+ {
+ public boolean isActive = true;
+
+ @Override
+ public boolean isActive()
+ {
+ return isActive;
+ }
+
+ @Override
+ public void lockReleased()
+ {
+ if (logger.isTraceEnabled())
+ {
+ logger.trace("lock released");
+ }
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/admin/patch/Patch.java b/source/java/org/alfresco/repo/admin/patch/Patch.java
index 7680568152..5c62b49035 100644
--- a/source/java/org/alfresco/repo/admin/patch/Patch.java
+++ b/source/java/org/alfresco/repo/admin/patch/Patch.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -99,9 +99,26 @@ public interface Patch
*/
public String apply() throws PatchException;
+ /**
+ * Apply the patch, regardless of the deferred flag. So if the patch has not
+ * run due to it being deferred earlier then this will run it now. Also
+ * ignores the "applied" lock. So the patch can be executed many times.
+ *
+ * @return the patch report
+ * @throws PatchException if the patch failed to be applied
+ */
+ public String applyAsync() throws PatchException;
+
/**
* Is this patch just ignored - never considered for application
* @return
*/
public boolean isIgnored();
+
+ /**
+ * Indicates whether the patch must be deferred (not to be executed in bootstrap) or not
+ *
+ * @return true if the patch must be deferred, false otherwise
+ */
+ public boolean isDeferred();
}
diff --git a/source/java/org/alfresco/repo/admin/patch/PatchService.java b/source/java/org/alfresco/repo/admin/patch/PatchService.java
index 28c03bb974..a571d7f3d3 100644
--- a/source/java/org/alfresco/repo/admin/patch/PatchService.java
+++ b/source/java/org/alfresco/repo/admin/patch/PatchService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -55,6 +55,15 @@ public interface PatchService
* patches could be applied.
*/
public boolean applyOutstandingPatches();
+
+ /**
+ * Apply the specified patch that is relevant to the repo.
+ *
+ * @param patch the patch object
+ * @return true if the specified patch and its dependencies were applied, or
+ * false if the process was terminated before all patches could be applied.
+ */
+ public boolean applyOutstandingPatch(Patch patch);
/**
* Retrieves all applied patches between two specific times.
@@ -74,4 +83,15 @@ public interface PatchService
* @return Returns the patch instance or null if one has not been persisted
*/
public AppliedPatch getPatch(String id);
+
+ /**
+ * Does some up-front validation on the specified patch, specifically to see
+ * if it applies to the current server version and not some future version.
+ * This is to prevent tampering with versioning information attached to a
+ * license.
+ *
+ * @param patch the patch object
+ * @return true if validation is successful. Outputs errors and returns false otherwise.
+ */
+ public boolean validatePatch(Patch patch);
}
diff --git a/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java b/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java
index 1507325c20..9e4ac9dfb1 100644
--- a/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java
+++ b/source/java/org/alfresco/repo/admin/patch/PatchServiceImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -38,6 +38,7 @@ import org.alfresco.service.descriptor.DescriptorService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ParameterCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.I18NUtil;
@@ -102,8 +103,24 @@ public class PatchServiceImpl implements PatchService
private final QName vetoName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "PatchServiceImpl");
-
+ /**
+ * {@inheritDoc}
+ */
public boolean validatePatches()
+ {
+ return validatePatchImpl(patches);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean validatePatch(Patch patch)
+ {
+ ParameterCheck.mandatory("patch", patch);
+ return validatePatchImpl(Collections.singletonList(patch));
+ }
+
+ private boolean validatePatchImpl(List patches)
{
boolean success = true;
int serverSchemaVersion = descriptorService.getServerDescriptor().getSchema();
@@ -115,7 +132,6 @@ public class PatchServiceImpl implements PatchService
.getFixesToSchema(), patch.getTargetSchema()));
success = false;
}
-
}
if (!success)
{
@@ -140,7 +156,7 @@ public class PatchServiceImpl implements PatchService
Collections.sort(sortedPatches, comparator);
// construct a list of executed patches by ID (also check the date)
- Map appliedPatchesById = new HashMap(23);
+ Map appliedPatchesById = new HashMap(250);
List appliedPatches = appliedPatchDAO.getAppliedPatches();
for (final AppliedPatch appliedPatch : appliedPatches)
{
@@ -191,6 +207,55 @@ public class PatchServiceImpl implements PatchService
return success;
}
+ /**
+ * {@inheritDoc}
+ */
+ public boolean applyOutstandingPatch(Patch patch)
+ {
+ boolean success = true;
+ try
+ {
+ // Disable rules whilst processing the patches
+ this.ruleService.disableRules();
+ try
+ {
+ Map appliedPatchesById = null;
+ if (patch.getDependsOn().isEmpty())
+ {
+ AppliedPatch appliedPatch = appliedPatchDAO.getAppliedPatch(patch.getId());
+ appliedPatchesById = new HashMap(1);
+ if (appliedPatch != null)
+ {
+ appliedPatchesById.put(appliedPatch.getId(), appliedPatch);
+ }
+ }
+ else
+ {
+ appliedPatchesById = new HashMap(250);
+ List appliedPatches = appliedPatchDAO.getAppliedPatches();
+ for (final AppliedPatch appliedPatch : appliedPatches)
+ {
+ appliedPatchesById.put(appliedPatch.getId(), appliedPatch);
+ }
+ }
+ // apply the patch
+ success = applyPatchAndDependencies(patch, appliedPatchesById);
+ }
+ finally
+ {
+ this.ruleService.enableRules();
+ }
+ }
+ catch (Throwable exception)
+ {
+ exception.printStackTrace();
+ success = false;
+ }
+
+ // done
+ return success;
+ }
+
/**
* Reentrant method that ensures that a patch and all its dependencies get applied.
* The process terminates on the first failure.
@@ -488,7 +553,7 @@ public class PatchServiceImpl implements PatchService
patch.getId(),
I18NUtil.getMessage(patch.getDescription()));
logger.info(msg);
- report = patch.apply();
+ report = (patch.isDeferred()) ? patch.applyAsync() : patch.apply();
state = STATE.APPLIED;
}
catch (PatchException e)
diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AddGroupAuthorityPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AddGroupAuthorityPatch.java
index 7935f811cf..fbea9e773c 100644
--- a/source/java/org/alfresco/repo/admin/patch/impl/AddGroupAuthorityPatch.java
+++ b/source/java/org/alfresco/repo/admin/patch/impl/AddGroupAuthorityPatch.java
@@ -1,3 +1,21 @@
+/*
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
package org.alfresco.repo.admin.patch.impl;
diff --git a/source/java/org/alfresco/repo/admin/patch/impl/SurfConfigFolderPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/SurfConfigFolderPatch.java
new file mode 100644
index 0000000000..eea668fc3f
--- /dev/null
+++ b/source/java/org/alfresco/repo/admin/patch/impl/SurfConfigFolderPatch.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+
+package org.alfresco.repo.admin.patch.impl;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.admin.patch.AsynchronousPatch;
+import org.alfresco.repo.admin.patch.PatchExecuter;
+import org.alfresco.repo.batch.BatchProcessWorkProvider;
+import org.alfresco.repo.batch.BatchProcessor;
+import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
+import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorkerAdaptor;
+import org.alfresco.repo.domain.node.NodeDAO;
+import org.alfresco.repo.domain.patch.PatchDAO;
+import org.alfresco.repo.domain.qname.QNameDAO;
+import org.alfresco.repo.policy.BehaviourFilter;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.site.SiteModel;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.rule.RuleService;
+import org.alfresco.service.namespace.RegexQNamePattern;
+import org.alfresco.util.Pair;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.extensions.surf.util.I18NUtil;
+
+/**
+ * Patch to add cm:indexControl aspect to sites' surf-config folders and
+ * their children as well as to the shared surf-config folder(s) and its/their children.
+ *
+ * @author Jamal Kaabi-Mofrad
+ */
+public class SurfConfigFolderPatch extends AsynchronousPatch
+{
+ private static final Log logger = LogFactory.getLog(SurfConfigFolderPatch.class);
+ private static final Log progress_logger = LogFactory.getLog(PatchExecuter.class);
+
+ private static final String MSG_SUCCESS = "patch.surfConfigFolderPatch.result";
+
+ // name of the surf config folder
+ private static final String SURF_CONFIG = "surf-config";
+ private static final String COMPONENTS = "components"; // cm:surf-config/cm:components
+ private static final String PAGES = "pages"; // cm:surf-config/cm:pages
+ private static final String SITE = "site"; // cm:surf-config/cm:pages/cm:site
+
+ private static final int SITE_BATCH_THREADS = 2;
+ private static final int SHARED_SURF_CONFIG_BATCH_THREADS = 2;
+ private static final int BATCH_SIZE = 1000;
+ private static final int SHARED_SURF_CONFIG_BATCH_MAX_QUERY_RANGE = 1000;
+ private static final int SITE_BATCH_MAX_QUERY_RANGE = 1000;
+
+ private PatchDAO patchDAO;
+ private NodeDAO nodeDAO;
+ private QNameDAO qnameDAO;
+ private BehaviourFilter behaviourFilter;
+ private RuleService ruleService;
+
+ /**
+ * @param patchDAO the patchDAO to set
+ */
+ public void setPatchDAO(PatchDAO patchDAO)
+ {
+ this.patchDAO = patchDAO;
+ }
+
+ /**
+ * @param nodeDAO the nodeDAO to set
+ */
+ public void setNodeDAO(NodeDAO nodeDAO)
+ {
+ this.nodeDAO = nodeDAO;
+ }
+
+ /**
+ * @param qnameDAO the qnameDAO to set
+ */
+ public void setQnameDAO(QNameDAO qnameDAO)
+ {
+ this.qnameDAO = qnameDAO;
+ }
+
+ /**
+ * @param behaviourFilter the behaviourFilter to set
+ */
+ public void setBehaviourFilter(BehaviourFilter behaviourFilter)
+ {
+ this.behaviourFilter = behaviourFilter;
+ }
+
+ /**
+ * @param ruleService the ruleService to set
+ */
+ public void setRuleService(RuleService ruleService)
+ {
+ this.ruleService = ruleService;
+ }
+
+ @Override
+ protected void checkProperties()
+ {
+ super.checkProperties();
+ checkPropertyNotNull(patchDAO, "patchDAO");
+ checkPropertyNotNull(nodeDAO, "nodeDAO");
+ checkPropertyNotNull(qnameDAO, "qnameDAO");
+ checkPropertyNotNull(ruleService, "ruleService");
+ checkPropertyNotNull(behaviourFilter, "behaviourFilter");
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.admin.patch.AbstractPatch#applyInternal()
+ */
+ @Override
+ protected String applyInternal() throws Exception
+ {
+ long start = System.currentTimeMillis();
+
+ // get user names that will be used within RunAs
+ final String systemUser = AuthenticationUtil.getSystemUserName();
+
+ // Instance to provide raw data to process
+ BatchProcessWorkProvider siteWorkProvider = new SiteWorkProvider();
+
+ // Instance to handle each item of work
+ BatchProcessWorker siteWorker = new BatchProcessWorkerAdaptor()
+ {
+ @Override
+ public void beforeProcess() throws Throwable
+ {
+ // Run as the systemuser
+ AuthenticationUtil.setRunAsUser(systemUser);
+ }
+
+ @Override
+ public void process(Long entry) throws Throwable
+ {
+ // Disable auditable aspect
+ behaviourFilter.disableBehaviour();
+ // Disable rules
+ ruleService.disableRules();
+ try
+ {
+ SurfConfigFolderPatch.this.process(entry);
+ }
+ finally
+ {
+ ruleService.enableRules();
+ behaviourFilter.enableBehaviour();
+ }
+ }
+
+ @Override
+ public void afterProcess() throws Throwable
+ {
+ AuthenticationUtil.clearCurrentSecurityContext();
+ }
+ };
+
+ BatchProcessor siteBatchProcessor = new BatchProcessor("SurfConfigFolderPatch",
+ transactionService.getRetryingTransactionHelper(), siteWorkProvider, SITE_BATCH_THREADS, BATCH_SIZE, null,
+ progress_logger, 1000);
+
+ int updatedSiteSurfConfig = siteBatchProcessor.process(siteWorker, true);
+
+ // shared surf-config folder
+ // Instance to provide raw data to process
+ BatchProcessWorkProvider surfConfigWorkProvider = new SharedSurfConfigWorkProvider();
+
+ // Instance to handle each item of work
+ BatchProcessWorker surfConfigWorker = new BatchProcessWorkerAdaptor()
+ {
+ @Override
+ public void beforeProcess() throws Throwable
+ {
+ // Run as the systemuser
+ AuthenticationUtil.setRunAsUser(systemUser);
+ }
+
+ @Override
+ public void process(NodeRef entry) throws Throwable
+ {
+ // Disable auditable aspect
+ behaviourFilter.disableBehaviour();
+ // Disable rules
+ ruleService.disableRules();
+ try
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("\tP: Processing [company_home/sites/surf-config/pages/user] children");
+ }
+ // add aspect to
+ // app:company_home/st:sites/cm:surf-config/cm:pages/cm:user/{userName}
+ addIndexControlAspectIfNotExist(entry);
+ }
+ finally
+ {
+ ruleService.enableRules();
+ behaviourFilter.enableBehaviour();
+ }
+ }
+
+ @Override
+ public void afterProcess() throws Throwable
+ {
+ AuthenticationUtil.clearCurrentSecurityContext();
+ }
+ };
+
+ BatchProcessor surfConfigBatchProcessor = new BatchProcessor("SurfConfigFolderPatch",
+ transactionService.getRetryingTransactionHelper(), surfConfigWorkProvider, SHARED_SURF_CONFIG_BATCH_THREADS,
+ BATCH_SIZE, null, progress_logger, 1000);
+
+ surfConfigBatchProcessor.process(surfConfigWorker, true);
+
+ int numOfSites = updatedSiteSurfConfig / 12;
+ String msg = I18NUtil.getMessage(MSG_SUCCESS, numOfSites);
+
+ long end = System.currentTimeMillis();
+ logger.info(msg + " in [" + (end - start) + " ms]");
+ return msg;
+ }
+
+ private void process(long siteId)
+ {
+ String siteName = (String) nodeDAO.getNodeProperty(siteId, ContentModel.PROP_NAME);
+
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("\tP: Processing surf-config folder for the site: [" + siteName + ']');
+ }
+
+ // {siteName}/cm:surf-config/
+ Pair surfConfigPair = nodeDAO.getChildAssoc(siteId, ContentModel.ASSOC_CONTAINS, SURF_CONFIG);
+
+ if (surfConfigPair == null)
+ {
+ logger.info("WARNING: unable to find surf-config folder for site: [" + siteName + ']');
+ return;
+ }
+ NodeRef surfConfigNodeRef = surfConfigPair.getSecond().getChildRef();
+ // apply the aspect to suef-config folder
+ addIndexControlAspectIfNotExist(surfConfigNodeRef);
+
+ // cm:surf-config/cm:components
+ NodeRef componentsNodeRef = nodeService.getChildByName(surfConfigNodeRef, ContentModel.ASSOC_CONTAINS, COMPONENTS);
+ if (componentsNodeRef != null)
+ {
+ // {siteName}/cm:surf-config/cm:components nodeRef
+ addIndexControlAspectIfNotExist(componentsNodeRef);
+
+ List listOfComponents = nodeService.getChildAssocs(componentsNodeRef,
+ ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
+ // apply the aspect to all of the children (6 in total)
+ for (ChildAssociationRef comp : listOfComponents)
+ {
+ addIndexControlAspectIfNotExist(comp.getChildRef());
+ }
+ }
+ else
+ {
+ logger.info("WARNING: unable to find surf-config/components folder for site: [" + siteName + ']');
+ }
+
+ // cm:surf-config/cm:pages folder
+ NodeRef pagesNodeRef = nodeService.getChildByName(surfConfigNodeRef, ContentModel.ASSOC_CONTAINS, PAGES);
+ if (pagesNodeRef == null)
+ {
+ logger.info("WARNING: unable to find surf-config/pages folder for site: [" + siteName + ']');
+ return;
+ }
+ // add aspect to cm:pages
+ addIndexControlAspectIfNotExist(pagesNodeRef);
+
+ // cm:surf-config/cm:pages/cm:site folder
+ NodeRef siteNodeRef = nodeService.getChildByName(pagesNodeRef, ContentModel.ASSOC_CONTAINS, SITE);
+ if (siteNodeRef == null)
+ {
+ logger.info("WARNING: unable to find surf-config/pages/site folder for site: [" + siteName + ']');
+ return;
+ }
+ // add aspect to cm:pages/cm:site folder
+ addIndexControlAspectIfNotExist(siteNodeRef);
+
+ // cm:surf-config/cm:pages/cm:site/{siteName}
+ NodeRef siteChildNodeRef = nodeService.getChildByName(siteNodeRef, ContentModel.ASSOC_CONTAINS, siteName);
+
+ if (siteChildNodeRef == null)
+ {
+ logger.info("WARNING: unable to find surf-config/pages/site/" + siteName + " folder for site: [" + siteName + ']');
+ return;
+ }
+ // add aspect to cm:surf-config/cm:pages/cm:site/{siteName}
+ addIndexControlAspectIfNotExist(siteChildNodeRef);
+
+ List listOfComponents = nodeService.getChildAssocs(siteChildNodeRef, ContentModel.ASSOC_CONTAINS,
+ RegexQNamePattern.MATCH_ALL);
+ // apply the aspect to all of the children
+ for (ChildAssociationRef comp : listOfComponents)
+ {
+ addIndexControlAspectIfNotExist(comp.getChildRef());
+ }
+ }
+
+ private void addIndexControlAspectIfNotExist(NodeRef nodeRef)
+ {
+ // We need to check the property rather than the aspect, as the node
+ // might have the aspect but not the correct property.
+ Serializable indexProp = nodeService.getProperty(nodeRef, ContentModel.PROP_IS_INDEXED);
+ if (indexProp == null || ((Boolean) indexProp))
+ {
+ nodeService.addAspect(nodeRef, ContentModel.ASPECT_INDEX_CONTROL,
+ Collections.singletonMap(ContentModel.PROP_IS_INDEXED, (Serializable) false));
+
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("\tP: Adding cm:indexControl aspect to node: [" + nodeRef + ']');
+ }
+ }
+ }
+
+ /**
+ * Work provider which performs incremental queries to find site nodes.
+ *
+ * @author Jamal Kaabi-Mofrad
+ */
+ private class SiteWorkProvider implements BatchProcessWorkProvider
+ {
+ private long maxId = Long.MAX_VALUE;
+ private long workCount = Long.MAX_VALUE;
+ private long currentId = 0L;
+ private final long siteTypeQNameId;
+
+ private SiteWorkProvider()
+ {
+ this.siteTypeQNameId = qnameDAO.getQName(SiteModel.TYPE_SITE).getFirst();
+ }
+
+ @Override
+ public synchronized int getTotalEstimatedWorkSize()
+ {
+ if (maxId == Long.MAX_VALUE)
+ {
+ maxId = patchDAO.getMaxAdmNodeID();
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("\tQ: Max node id: " + maxId);
+ }
+ }
+ if (workCount == Long.MAX_VALUE)
+ {
+ // get the sites count
+ workCount = patchDAO.getCountNodesWithTypId(SiteModel.TYPE_SITE);
+ // Each site has 12 children (we care only about surf-config
+ // itself and its children)
+ workCount *= 12;
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("\tQ: Work count: " + workCount);
+ }
+ }
+ return (int) workCount;
+ }
+
+ @Override
+ public synchronized Collection getNextWork()
+ {
+ // Record the site node IDs
+ final List siteNodeIDs = new ArrayList(SITE_BATCH_MAX_QUERY_RANGE);
+
+ // Keep querying until we have enough results to give back
+ int minResults = SITE_BATCH_MAX_QUERY_RANGE / 2;
+ while (currentId <= maxId && siteNodeIDs.size() < minResults)
+ {
+ List nodeIds = patchDAO.getNodesByTypeQNameId(siteTypeQNameId, currentId, currentId
+ + SITE_BATCH_MAX_QUERY_RANGE);
+ siteNodeIDs.addAll(nodeIds);
+ // Increment the minimum ID
+ currentId += SITE_BATCH_MAX_QUERY_RANGE;
+ }
+ // Done
+ return siteNodeIDs;
+ }
+ }
+
+ /**
+ * Work provider which performs incremental queries to find shared
+ * Surf-Config folders and their children.
+ *
+ * @author Jamal Kaabi-Mofrad
+ */
+ private class SharedSurfConfigWorkProvider implements BatchProcessWorkProvider
+ {
+ private long maxId = Long.MAX_VALUE;
+ private long currentId = 0L;
+
+ private SharedSurfConfigWorkProvider()
+ {
+ }
+
+ @Override
+ public synchronized int getTotalEstimatedWorkSize()
+ {
+ if (maxId == Long.MAX_VALUE)
+ {
+ maxId = patchDAO.getMaxAdmNodeID();
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("\tQ: Max node id: " + maxId);
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public synchronized Collection getNextWork()
+ {
+ // Record the user folder node IDs
+ final List folderNodes = new ArrayList(SHARED_SURF_CONFIG_BATCH_MAX_QUERY_RANGE);
+
+ // Keep querying until we have enough results to give back
+ int minResults = SHARED_SURF_CONFIG_BATCH_MAX_QUERY_RANGE / 2;
+ while (currentId <= maxId && folderNodes.size() < minResults)
+ {
+
+ List nodeIds = patchDAO.getChildrenOfTheSharedSurfConfigFolder(currentId, currentId
+ + SHARED_SURF_CONFIG_BATCH_MAX_QUERY_RANGE);
+ folderNodes.addAll(nodeIds);
+ // Increment the minimum ID
+ currentId += SHARED_SURF_CONFIG_BATCH_MAX_QUERY_RANGE;
+ }
+ // Preload the nodes for quicker access
+ nodeDAO.cacheNodes(folderNodes);
+ // Done
+ return folderNodes;
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/patch/PatchDAO.java b/source/java/org/alfresco/repo/domain/patch/PatchDAO.java
index 13e0dd062f..908b5ac841 100644
--- a/source/java/org/alfresco/repo/domain/patch/PatchDAO.java
+++ b/source/java/org/alfresco/repo/domain/patch/PatchDAO.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -280,5 +280,27 @@ public interface PatchDAO
* @return
*/
public List getNodesByContentPropertyMimetypeId(Long mimetypeId, Long minNodeId, Long maxNodeId);
+
+ /**
+ * Gets the total number of nodes which match the given Type QName.
+ *
+ * @param typeQName the qname to search for
+ * @return count of nodes that match the typeQName
+ */
+ public long getCountNodesWithTypId(QName typeQName);
+
+ /**
+ * Finds folders of the shared surf-config (for all tenants):
+ *
+ *
company_home/sites/surf-config/components
+ *
company_home/sites/surf-config/pages
+ *
company_home/sites/surf-config/pages/user
+ *
company_home/sites/surf-config/pages/user{userId}
+ *
+ * @param minNodeId - min node id in the result set - inclusive
+ * @param maxNodeId - max node id in the result set - exclusive
+ * @return list of children nodeRefs
+ */
+ public List getChildrenOfTheSharedSurfConfigFolder(Long minNodeId, Long maxNodeId);
}
diff --git a/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java b/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java
index e314abe414..bee4ef09ff 100644
--- a/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java
+++ b/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -108,6 +108,9 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl
private static final String SELECT_NODES_BY_TYPE_URI = "alfresco.patch.select_NodesByTypeUriId";
private static final String SELECT_NODES_BY_ASPECT_QNAME = "alfresco.patch.select_NodesByAspectQName";
private static final String SELECT_NODES_BY_CONTENT_MIMETYPE = "alfresco.patch.select_NodesByContentMimetype";
+
+ private static final String SELECT_COUNT_NODES_WITH_TYPE_ID = "alfresco.patch.select_CountNodesWithTypeId";
+ private static final String SELECT_CHILDREN_OF_THE_SHARED_SURFCONFIG_FOLDER = "alfresco.patch.select_ChildrenOfTheSharedSurfConfigFolder";
private LocaleDAO localeDAO;
@@ -703,4 +706,52 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl
params.put("maxNodeId", maxNodeId);
return (List) template.selectList(SELECT_NODES_BY_CONTENT_MIMETYPE, params);
}
+
+ @Override
+ public long getCountNodesWithTypId(QName typeQName)
+ {
+ // Resolve the QName
+ Pair qnameId = qnameDAO.getQName(typeQName);
+ if (qnameId == null)
+ {
+ return 0L;
+ }
+ IdsEntity params = new IdsEntity();
+ params.setIdOne(qnameId.getFirst());
+ Long count = (Long) template.selectOne(SELECT_COUNT_NODES_WITH_TYPE_ID, params);
+ if (count == null)
+ {
+ return 0L;
+ }
+ else
+ {
+ return count;
+ }
+ }
+
+
+ @Override
+ public List getChildrenOfTheSharedSurfConfigFolder(Long minNodeId, Long maxNodeId)
+ {
+ Map params = new HashMap(2);
+ params.put("minNodeId", minNodeId);
+ params.put("maxNodeId", maxNodeId);
+
+ final List results = new ArrayList(1000);
+ ResultHandler resultHandler = new ResultHandler()
+ {
+ @SuppressWarnings("unchecked")
+ public void handleResult(ResultContext context)
+ {
+ Map row = (Map) context.getResultObject();
+ String protocol = (String) row.get("protocol");
+ String identifier = (String) row.get("identifier");
+ String uuid = (String) row.get("uuid");
+ NodeRef nodeRef = new NodeRef(new StoreRef(protocol, identifier), uuid);
+ results.add(nodeRef);
+ }
+ };
+ template.select(SELECT_CHILDREN_OF_THE_SHARED_SURFCONFIG_FOLDER, params, resultHandler);
+ return results;
+ }
}
diff --git a/source/java/org/alfresco/service/cmr/model/FileFolderUtil.java b/source/java/org/alfresco/service/cmr/model/FileFolderUtil.java
index 4754a9a2bd..f710c19021 100644
--- a/source/java/org/alfresco/service/cmr/model/FileFolderUtil.java
+++ b/source/java/org/alfresco/service/cmr/model/FileFolderUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2013 Alfresco Software Limited.
+ * Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -18,13 +18,18 @@
*/
package org.alfresco.service.cmr.model;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
/**
@@ -70,34 +75,61 @@ public class FileFolderUtil
* @param parentBehavioursToDisable
* @return
*/
- public static FileInfo makeFolders(FileFolderService service,
- NodeRef parentNodeRef, List pathElements,
- QName folderTypeQName,
- BehaviourFilter behaviourFilter,
- Set parentBehavioursToDisable)
+ public static FileInfo makeFolders(FileFolderService service, NodeRef parentNodeRef, List pathElements,
+ QName folderTypeQName, BehaviourFilter behaviourFilter, Set parentBehavioursToDisable)
{
- if (pathElements.size() == 0)
+ validate(pathElements, service, folderTypeQName);
+
+ List list = new ArrayList<>(pathElements.size());
+ for (String pathElement : pathElements)
{
- throw new IllegalArgumentException("Path element list is empty");
+ list.add(new PathElementDetails(pathElement, null));
}
- // make sure that the folder is correct
- boolean isFolder = service.getType(folderTypeQName) == FileFolderServiceType.FOLDER;
- if (!isFolder)
- {
- throw new IllegalArgumentException(
- "Type is invalid to make folders with: " + folderTypeQName);
- }
+ FileInfo fileInfo = makeFolders(service, null, parentNodeRef, list, folderTypeQName, behaviourFilter,
+ parentBehavioursToDisable);
+
+ // Should we check the type?
+ return fileInfo;
+ }
+
+
+ /**
+ * Checks for the presence of, and creates as necessary, the folder
+ * structure in the provided paths with the following options:
+ *
+ *
Option to disable parent behaviour(s) when creating sub-folder.
+ *
Each folder has the option to have its own set of aspects
+ *
+ *
+ * @param service the FileFolderService object
+ * @param nodeService the NodeService object
+ * @param parentNodeRef the node under which the path will be created
+ * @param pathElementDetails the list of folder hierarchy where each folder
+ * can have its own set of aspects - may not be empty
+ * @param folderTypeQName the types of nodes to create. This must be a valid
+ * subtype of {@link org.alfresco.model.ContentModel#TYPE_FOLDER
+ * they folder type}
+ * @param behaviourFilter the BehaviourFilter object
+ * @param parentBehavioursToDisable the set of behaviours that must be
+ * disabled
+ * @return Returns the {@code FileInfo} of the last folder in the path.
+ */
+ public static FileInfo makeFolders(FileFolderService service, NodeService nodeService, NodeRef parentNodeRef,
+ List pathElementDetails, QName folderTypeQName, BehaviourFilter behaviourFilter,
+ Set parentBehavioursToDisable)
+ {
+ validate(pathElementDetails, service, folderTypeQName);
NodeRef currentParentRef = parentNodeRef;
// just loop and create if necessary
- for (String pathElement : pathElements)
+ for (PathElementDetails pathElement : pathElementDetails)
{
// does it exist?
// Navigation should not check permissions
- NodeRef nodeRef = AuthenticationUtil.runAs(new SearchAsSystem(
- service, currentParentRef, pathElement), AuthenticationUtil
- .getSystemUserName());
+ NodeRef nodeRef = AuthenticationUtil.runAs(
+ new SearchAsSystem(service, currentParentRef, pathElement.getFolderName()),
+ AuthenticationUtil.getSystemUserName());
if (nodeRef == null)
{
@@ -108,15 +140,23 @@ public class FileFolderUtil
behaviourFilter.disableBehaviour(currentParentRef, parentBehaviourToDisable);
}
}
-
+
try
{
// not present - make it
// If this uses the public service it will check create
// permissions
- FileInfo createdFileInfo = service.create(currentParentRef,
- pathElement, folderTypeQName);
+ FileInfo createdFileInfo = service.create(currentParentRef, pathElement.getFolderName(), folderTypeQName);
currentParentRef = createdFileInfo.getNodeRef();
+
+ Map> requireddAspects = pathElement.getAspects();
+ if (requireddAspects != null && nodeService != null)
+ {
+ for (QName aspect : requireddAspects.keySet())
+ {
+ nodeService.addAspect(currentParentRef, aspect, requireddAspects.get(aspect));
+ }
+ }
}
finally
{
@@ -144,6 +184,21 @@ public class FileFolderUtil
return fileInfo;
}
+ private static void validate(List pathElements, FileFolderService service, QName folderTypeQName)
+ {
+ if (pathElements.size() == 0)
+ {
+ throw new IllegalArgumentException("Path element list is empty");
+ }
+
+ // make sure that the folder is correct
+ boolean isFolder = service.getType(folderTypeQName) == FileFolderServiceType.FOLDER;
+ if (!isFolder)
+ {
+ throw new IllegalArgumentException("Type is invalid to make folders with: " + folderTypeQName);
+ }
+ }
+
private static class SearchAsSystem implements RunAsWork
{
FileFolderService service;
@@ -163,5 +218,37 @@ public class FileFolderUtil
}
}
+
+ /**
+ * A simple POJO to hold information about the folder which will be created.
+ *
+ * @author Jamal Kaabi-Mofrad
+ */
+ public static class PathElementDetails
+ {
+ private final String folderName;
+ private final Map> aspects;
+ public PathElementDetails(String folderName, Map> aspects)
+ {
+ this.folderName = folderName;
+ this.aspects = Collections.unmodifiableMap(aspects);
+ }
+
+ /**
+ * @return the folderName
+ */
+ public String getFolderName()
+ {
+ return this.folderName;
+ }
+
+ /**
+ * @return the aspects
+ */
+ public Map> getAspects()
+ {
+ return this.aspects;
+ }
+ }
}