mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merged HEAD-QA to HEAD (4.2) (including moving test classes into separate folders)
51903 to 54309 git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@54310 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2013 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2010 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -20,18 +20,24 @@ package org.alfresco.repo.admin.patch;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
import org.alfresco.repo.batch.BatchProcessWorkProvider;
|
||||
import org.alfresco.repo.batch.BatchProcessor;
|
||||
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
|
||||
import org.alfresco.repo.node.integrity.IntegrityChecker;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationContext;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
||||
import org.alfresco.repo.tenant.Tenant;
|
||||
import org.alfresco.repo.tenant.TenantAdminService;
|
||||
import org.alfresco.repo.tenant.TenantUtil;
|
||||
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.admin.PatchException;
|
||||
@@ -43,7 +49,6 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
|
||||
/**
|
||||
* Base implementation of the patch. This class ensures that the patch is thread- and transaction-safe.
|
||||
@@ -61,6 +66,7 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
|
||||
*/
|
||||
public static final String ERR_PROPERTY_NOT_SET = "patch.general.property_not_set";
|
||||
private static final String MSG_PROGRESS = "patch.progress";
|
||||
private static final String MSG_DEFERRED = "patch.genericBootstrap.result.deferred";
|
||||
|
||||
private static final long RANGE_10 = 1000 * 60 * 90;
|
||||
private static final long RANGE_5 = 1000 * 60 * 60 * 4;
|
||||
@@ -75,6 +81,7 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
|
||||
private int targetSchema;
|
||||
private boolean force;
|
||||
private String description;
|
||||
private boolean ignored;
|
||||
/** a list of patches that this one depends on */
|
||||
private List<Patch> dependsOn;
|
||||
/** a list of patches that, if already present, mean that this one should be ignored */
|
||||
@@ -87,6 +94,8 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
|
||||
/** start time * */
|
||||
long startTime;
|
||||
|
||||
private boolean deferred = false;
|
||||
|
||||
// Does the patch require an enclosing transaction?
|
||||
private boolean requiresTransaction = true;
|
||||
|
||||
@@ -114,6 +123,7 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
|
||||
this.applyToTenants = true; // by default, apply to each tenant, if tenant service is enabled
|
||||
this.dependsOn = Collections.emptyList();
|
||||
this.alternatives = Collections.emptyList();
|
||||
this.ignored = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -126,6 +136,7 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
|
||||
.append(", fixesFromSchema=").append(fixesFromSchema)
|
||||
.append(", fixesToSchema=").append(fixesToSchema)
|
||||
.append(", targetSchema=").append(targetSchema)
|
||||
.append(", ignored=").append(ignored)
|
||||
.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -190,7 +201,10 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Mandatory property not set: patchService");
|
||||
}
|
||||
patchService.registerPatch(this);
|
||||
if(false == isIgnored())
|
||||
{
|
||||
patchService.registerPatch(this);
|
||||
}
|
||||
}
|
||||
|
||||
public String getId()
|
||||
@@ -315,6 +329,22 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
|
||||
{
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ignored
|
||||
*/
|
||||
public boolean isIgnored()
|
||||
{
|
||||
return ignored;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ignored the ignored to set
|
||||
*/
|
||||
public void setIgnored(boolean ignored)
|
||||
{
|
||||
this.ignored = ignored;
|
||||
}
|
||||
|
||||
public List<Patch> getDependsOn()
|
||||
{
|
||||
@@ -404,32 +434,122 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
|
||||
{
|
||||
// downgrade integrity checking
|
||||
IntegrityChecker.setWarnInTransaction();
|
||||
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("call applyInternal for main context id:" + id);
|
||||
}
|
||||
String report = applyInternal();
|
||||
|
||||
if ((tenantAdminService != null) && tenantAdminService.isEnabled() && applyToTenants)
|
||||
if ((tenantAdminService != null) && tenantAdminService.isEnabled() && applyToTenants)
|
||||
{
|
||||
List<Tenant> tenants = tenantAdminService.getAllTenants();
|
||||
for (Tenant tenant : tenants)
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
String tenantDomain = tenant.getTenantDomain();
|
||||
String tenantReport = TenantUtil.runAsSystemTenant(new TenantRunAsWork<String>()
|
||||
{
|
||||
public String doWork() throws Exception
|
||||
{
|
||||
return applyInternal();
|
||||
}
|
||||
}, tenantDomain);
|
||||
logger.debug("call applyInternal for all tennants");
|
||||
}
|
||||
final List<Tenant> tenants = tenantAdminService.getAllTenants();
|
||||
|
||||
BatchProcessWorkProvider<Tenant> provider = new BatchProcessWorkProvider<Tenant>()
|
||||
{
|
||||
Iterator<Tenant> i = tenants.iterator();
|
||||
|
||||
report = report + "\n (also applied to " + tenants.size() + " tenants)";
|
||||
@Override
|
||||
public int getTotalEstimatedWorkSize()
|
||||
{
|
||||
return tenants.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Tenant> getNextWork()
|
||||
{
|
||||
// return chunks of 10 tenants
|
||||
ArrayList<Tenant> chunk = new ArrayList<Tenant>(100);
|
||||
|
||||
while(i.hasNext() && chunk.size() <= 100)
|
||||
{
|
||||
chunk.add(i.next());
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
};
|
||||
|
||||
BatchProcessor<Tenant> batchProcessor = new BatchProcessor<Tenant>(
|
||||
"AbstractPatch Processor for " + id,
|
||||
transactionHelper,
|
||||
provider, // collection of tenants
|
||||
10, // worker threads,
|
||||
100, // batch size 100,
|
||||
applicationEventPublisher,
|
||||
logger,
|
||||
1000);
|
||||
|
||||
BatchProcessWorker worker = new BatchProcessWorker<Tenant>()
|
||||
{
|
||||
@Override
|
||||
public String getIdentifier(Tenant entry)
|
||||
{
|
||||
return entry.getTenantDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeProcess() throws Throwable
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Tenant entry) throws Throwable
|
||||
{
|
||||
String tenantDomain = entry.getTenantDomain();
|
||||
String tenantReport = AuthenticationUtil.runAs(new RunAsWork<String>()
|
||||
{
|
||||
public String doWork() throws Exception
|
||||
{
|
||||
return applyInternal();
|
||||
}
|
||||
}, tenantAdminService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDomain));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterProcess() throws Throwable
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Now do the work
|
||||
int numberOfInvocations = batchProcessor.process(worker, true);
|
||||
|
||||
return report;
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("batch worker finished processing id:" + id);
|
||||
}
|
||||
|
||||
if(batchProcessor.getTotalErrors() > 0)
|
||||
{
|
||||
report = report + "\n" + " and failure during update of tennants total success: " + batchProcessor.getSuccessfullyProcessedEntries() + " number of errors: " +batchProcessor.getTotalErrors() + " lastError" + batchProcessor.getLastError();
|
||||
}
|
||||
else
|
||||
{
|
||||
report = report + "\n" + " and successful batch update of " + batchProcessor.getTotalResults() + "tennants";
|
||||
}
|
||||
}
|
||||
|
||||
// done?
|
||||
return report;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return apply(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the transaction and ensures thread-safety.
|
||||
@@ -438,11 +558,27 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
|
||||
*/
|
||||
public synchronized String apply() throws PatchException
|
||||
{
|
||||
// ensure that this has not been executed already
|
||||
if (applied)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("The patch has already been executed: \n" + " patch: " + this);
|
||||
}
|
||||
return apply(false);
|
||||
}
|
||||
|
||||
private String apply(boolean async)
|
||||
{
|
||||
|
||||
if(!async)
|
||||
{
|
||||
// Do we bug out of patch execution
|
||||
if (deferred)
|
||||
{
|
||||
return I18NUtil.getMessage(MSG_DEFERRED);
|
||||
}
|
||||
|
||||
// ensure that this has not been executed already
|
||||
if (applied)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("The patch has already been executed: \n" + " patch: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
// check properties
|
||||
checkProperties();
|
||||
|
||||
@@ -594,6 +730,21 @@ public abstract class AbstractPatch implements Patch, ApplicationEventPublisher
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the patch be deferred? And not run at bootstrap.
|
||||
* @param deferred
|
||||
*/
|
||||
public void setDeferred(boolean deferred) {
|
||||
this.deferred = deferred;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
public boolean isDeferred() {
|
||||
return deferred;
|
||||
}
|
||||
|
||||
private int getReportingInterval(long soFar, long toGo)
|
||||
{
|
||||
|
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2013 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.admin.patch;
|
||||
|
||||
import org.alfresco.service.descriptor.Descriptor;
|
||||
import org.alfresco.service.descriptor.DescriptorService;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
|
||||
|
||||
/**
|
||||
* @author Andy
|
||||
*/
|
||||
public class OptionalPatchApplicationCheckBootstrapBean extends AbstractLifecycleBean
|
||||
{
|
||||
PatchService patchService;
|
||||
|
||||
Patch patch;
|
||||
|
||||
DescriptorService descriptorService;
|
||||
|
||||
volatile boolean patchApplied = false;
|
||||
|
||||
/**
|
||||
* @param patchService
|
||||
* the patchService to set
|
||||
*/
|
||||
public void setPatchService(PatchService patchService)
|
||||
{
|
||||
this.patchService = patchService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param patch
|
||||
* the patch to set
|
||||
*/
|
||||
public void setPatch(Patch patch)
|
||||
{
|
||||
this.patch = patch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param descriptorService
|
||||
* the descriptorService to set
|
||||
*/
|
||||
public void setDescriptorService(DescriptorService descriptorService)
|
||||
{
|
||||
this.descriptorService = descriptorService;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.
|
||||
* ApplicationEvent)
|
||||
*/
|
||||
@Override
|
||||
protected void onBootstrap(ApplicationEvent event)
|
||||
{
|
||||
Descriptor descriptor = descriptorService.getInstalledRepositoryDescriptor();
|
||||
if (patch == null)
|
||||
{
|
||||
patchApplied = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AppliedPatch appliedPatch = patchService.getPatch(patch.getId());
|
||||
if (appliedPatch == null)
|
||||
{
|
||||
patchApplied = patch.getFixesToSchema() < descriptor.getSchema();
|
||||
}
|
||||
else
|
||||
{
|
||||
patchApplied = appliedPatch.getSucceeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown(org.springframework.context.
|
||||
* ApplicationEvent)
|
||||
*/
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent event)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Was the patch applied - or was it not applied
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean getPatchApplied()
|
||||
{
|
||||
return patchApplied;
|
||||
}
|
||||
}
|
@@ -98,4 +98,10 @@ public interface Patch
|
||||
* @throws PatchException if the patch failed to be applied
|
||||
*/
|
||||
public String apply() throws PatchException;
|
||||
|
||||
/**
|
||||
* Is this patch just ignored - never considered for application
|
||||
* @return
|
||||
*/
|
||||
public boolean isIgnored();
|
||||
}
|
||||
|
@@ -66,4 +66,12 @@ public interface PatchService
|
||||
* @return Returns all applied patches (successful or not)
|
||||
*/
|
||||
public List<AppliedPatch> getPatches(Date fromDate, Date toDate);
|
||||
|
||||
/**
|
||||
* Retrieve an existing patch
|
||||
*
|
||||
* @param id the patch unique ID
|
||||
* @return Returns the patch instance or <tt>null</tt> if one has not been persisted
|
||||
*/
|
||||
public AppliedPatch getPatch(String id);
|
||||
}
|
||||
|
@@ -587,4 +587,13 @@ public class PatchServiceImpl implements PatchService
|
||||
return i1.compareTo(i2);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.alfresco.repo.admin.patch.PatchService#getAppliedPatch(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public AppliedPatch getPatch(String id)
|
||||
{
|
||||
return appliedPatchDAO.getAppliedPatch(id);
|
||||
}
|
||||
}
|
||||
|
@@ -1,208 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2010 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.admin.patch;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationContext;
|
||||
import org.alfresco.repo.tenant.TenantAdminService;
|
||||
import org.alfresco.service.cmr.admin.PatchException;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.search.SearchService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.ApplicationContextHelper;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.admin.patch.Patch
|
||||
* @see org.alfresco.repo.admin.patch.AbstractPatch
|
||||
* @see org.alfresco.repo.admin.patch.PatchService
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class PatchTest extends TestCase
|
||||
{
|
||||
private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
|
||||
|
||||
private TransactionService transactionService;
|
||||
private NamespaceService namespaceService;
|
||||
private NodeService nodeService;
|
||||
private SearchService searchService;
|
||||
private AuthenticationContext authenticationContext;
|
||||
private TenantAdminService tenantAdminService;
|
||||
private PatchService patchService;
|
||||
|
||||
public PatchTest(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
transactionService = (TransactionService) ctx.getBean("transactionComponent");
|
||||
namespaceService = (NamespaceService) ctx.getBean("namespaceService");
|
||||
nodeService = (NodeService) ctx.getBean("nodeService");
|
||||
searchService = (SearchService) ctx.getBean("searchService");
|
||||
authenticationContext = (AuthenticationContext) ctx.getBean("authenticationContext");
|
||||
tenantAdminService = (TenantAdminService) ctx.getBean("tenantAdminService");
|
||||
|
||||
patchService = (PatchService) ctx.getBean("PatchService");
|
||||
|
||||
// get the patches to play with
|
||||
patchService.registerPatch((Patch)ctx.getBean("patch.sample.02"));
|
||||
patchService.registerPatch((Patch)ctx.getBean("patch.sample.01"));
|
||||
patchService.registerPatch((Patch)ctx.getBean("patch.sample.03"));
|
||||
}
|
||||
|
||||
public void testSetup() throws Exception
|
||||
{
|
||||
assertNotNull(transactionService);
|
||||
assertNotNull(patchService);
|
||||
}
|
||||
|
||||
private SamplePatch constructSamplePatch(boolean mustFail)
|
||||
{
|
||||
SamplePatch patch = new SamplePatch(mustFail, transactionService);
|
||||
patch.setNamespaceService(namespaceService);
|
||||
patch.setNodeService(nodeService);
|
||||
patch.setSearchService(searchService);
|
||||
patch.setAuthenticationContext(authenticationContext);
|
||||
patch.setTenantAdminService(tenantAdminService);
|
||||
patch.setApplicationEventPublisher(ctx);
|
||||
// done
|
||||
return patch;
|
||||
}
|
||||
|
||||
// private SimplePatch constructSimplePatch(boolean requiresTransaction)
|
||||
// {
|
||||
// SimplePatch patch = new SimplePatch(transactionService, requiresTransaction);
|
||||
// patch.setNamespaceService(namespaceService);
|
||||
// patch.setNodeService(nodeService);
|
||||
// patch.setSearchService(searchService);
|
||||
// patch.setAuthenticationContext(authenticationContext);
|
||||
// patch.setTenantAdminService(tenantAdminService);
|
||||
// patch.setApplicationEventPublisher(ctx);
|
||||
// // done
|
||||
// return patch;
|
||||
// }
|
||||
|
||||
public void testSimplePatchSuccess() throws Exception
|
||||
{
|
||||
Patch patch = constructSamplePatch(false);
|
||||
String report = patch.apply();
|
||||
// check that the report was generated
|
||||
assertEquals("Patch report incorrect", SamplePatch.MSG_SUCCESS, report);
|
||||
}
|
||||
|
||||
public void testPatchReapplication()
|
||||
{
|
||||
// successfully apply a patch
|
||||
Patch patch = constructSamplePatch(false);
|
||||
patch.apply();
|
||||
// check that the patch cannot be reapplied
|
||||
try
|
||||
{
|
||||
patch.apply();
|
||||
fail("AbstractPatch failed to prevent reapplication");
|
||||
}
|
||||
catch (AlfrescoRuntimeException e)
|
||||
{
|
||||
// expected
|
||||
}
|
||||
|
||||
// apply an unsuccessful patch
|
||||
patch = constructSamplePatch(true);
|
||||
try
|
||||
{
|
||||
patch.apply();
|
||||
fail("Failed patch didn't throw PatchException");
|
||||
}
|
||||
catch (PatchException e)
|
||||
{
|
||||
// expected
|
||||
}
|
||||
// repeat
|
||||
try
|
||||
{
|
||||
patch.apply();
|
||||
fail("Reapplication of failed patch didn't throw PatchException");
|
||||
}
|
||||
catch (PatchException e)
|
||||
{
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
public void testApplyOutstandingPatches() throws Exception
|
||||
{
|
||||
// apply outstanding patches
|
||||
boolean success = patchService.applyOutstandingPatches();
|
||||
assertTrue(success);
|
||||
// get applied patches
|
||||
List<AppliedPatch> appliedPatches = patchService.getPatches(null, null);
|
||||
// check that the patch application was recorded
|
||||
boolean found01 = false;
|
||||
boolean found02 = false;
|
||||
boolean found03 = false;
|
||||
for (AppliedPatch appliedPatch : appliedPatches)
|
||||
{
|
||||
if (appliedPatch.getId().equals("Sample01"))
|
||||
{
|
||||
found01 = true;
|
||||
assertTrue("Patch info didn't indicate success: " + appliedPatch, appliedPatch.getSucceeded());
|
||||
}
|
||||
else if (appliedPatch.getId().equals("Sample02"))
|
||||
{
|
||||
found02 = true;
|
||||
assertTrue("Patch info didn't indicate success: " + appliedPatch, appliedPatch.getSucceeded());
|
||||
}
|
||||
else if (appliedPatch.getId().equals("Sample03"))
|
||||
{
|
||||
found03 = true;
|
||||
assertTrue("Patch info didn't indicate success: " + appliedPatch, appliedPatch.getSucceeded());
|
||||
}
|
||||
}
|
||||
assertTrue("Sample 01 not in list of applied patches", found01);
|
||||
assertTrue("Sample 02 not in list of applied patches", found02);
|
||||
assertTrue("Sample 03 not in list of applied patches", found03);
|
||||
}
|
||||
|
||||
public void testGetPatchesByDate() throws Exception
|
||||
{
|
||||
// ensure that there are some applied patches
|
||||
testApplyOutstandingPatches();
|
||||
// get the number of applied patches
|
||||
List<AppliedPatch> appliedPatches = patchService.getPatches(null, null);
|
||||
assertTrue("Expected at least 2 applied patches", appliedPatches.size() >= 2);
|
||||
|
||||
// now requery using null dates
|
||||
List<AppliedPatch> appliedPatchesAllDates = patchService.getPatches(null, null);
|
||||
assertEquals("Applied patches by all dates doesn't match all applied patches",
|
||||
appliedPatches.size(), appliedPatchesAllDates.size());
|
||||
|
||||
// perform another query with dates that should return no results
|
||||
List<AppliedPatch> appliedPatchesFutureDates = patchService.getPatches(new Date(), new Date());
|
||||
assertEquals("Query returned results for dates when no patches should exist", 0, appliedPatchesFutureDates.size());
|
||||
}
|
||||
}
|
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2013 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.admin.patch.impl;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.query.PagingRequest;
|
||||
import org.alfresco.query.PagingResults;
|
||||
import org.alfresco.repo.admin.patch.AbstractPatch;
|
||||
import org.alfresco.repo.calendar.CalendarModel;
|
||||
import org.alfresco.service.cmr.calendar.CalendarEntry;
|
||||
import org.alfresco.service.cmr.calendar.CalendarEntryDTO;
|
||||
import org.alfresco.service.cmr.calendar.CalendarService;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.site.SiteInfo;
|
||||
import org.alfresco.service.cmr.site.SiteService;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
|
||||
/**
|
||||
* This patch adjusts dates for Calendar Events. Share application in 3.4.x versions doesn't adjust specified in form dates with time zone offset value to. Web Script which saves a
|
||||
* new event always performs correction of the dates in accordance with time zone for 'All Day' events. Date becomes on a day before, if time for date is set to '00:00' in this
|
||||
* case. Share in 4.x does this adjustment automatically before sending request to the Web Script.<br />
|
||||
* <br />
|
||||
* See "<a href="https://issues.alfresco.com/jira/browse/MNT-8977">CMIS (OpenCMIS version) is sharing security context with other functionality in Alfresco</a>" for more details
|
||||
*
|
||||
* @since 4.1.5
|
||||
* @author Dmitry Velichkevich
|
||||
*/
|
||||
public class CalendarAllDayEventDatesCorrectingPatch extends AbstractPatch
|
||||
{
|
||||
private static final String MSG_SUCCESS = "patch.calendarAllDayEventDatesCorrectingPatch.result";
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CalendarAllDayEventDatesCorrectingPatch.class);
|
||||
|
||||
private int batchSize = 1000;
|
||||
|
||||
private boolean batchEnabled = true;
|
||||
|
||||
private SiteService siteService;
|
||||
|
||||
private CalendarService calendarService;
|
||||
|
||||
public CalendarAllDayEventDatesCorrectingPatch()
|
||||
{
|
||||
}
|
||||
|
||||
public void setBatchSize(int batchSize)
|
||||
{
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public void setBatchEnabled(boolean batchEnabled)
|
||||
{
|
||||
this.batchEnabled = batchEnabled;
|
||||
}
|
||||
|
||||
public void setSiteService(SiteService siteService)
|
||||
{
|
||||
this.siteService = siteService;
|
||||
}
|
||||
|
||||
public void setCalendarService(CalendarService calendarService)
|
||||
{
|
||||
this.calendarService = calendarService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String applyInternal() throws Exception
|
||||
{
|
||||
int updatedEventsAmount = 0;
|
||||
|
||||
NodeRef siteRoot = siteService.getSiteRoot();
|
||||
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Site root: " + siteRoot);
|
||||
}
|
||||
|
||||
List<ChildAssociationRef> allSites = (null != siteRoot) ? (nodeService.getChildAssocs(siteRoot)) : (null);
|
||||
|
||||
if ((null != allSites) && !allSites.isEmpty())
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Starting processing of " + allSites.size() + " sites...");
|
||||
}
|
||||
|
||||
PagingResults<CalendarEntry> entries = null;
|
||||
String queryId = null;
|
||||
|
||||
int maxItems = (batchEnabled) ? (batchSize) : (Integer.MAX_VALUE);
|
||||
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Batching info:\n\t- batching enabled: " + batchEnabled + ";\n\t-batch size: " + batchSize);
|
||||
}
|
||||
|
||||
for (ChildAssociationRef siteAssoc : allSites)
|
||||
{
|
||||
SiteInfo site = siteService.getSite(siteAssoc.getChildRef());
|
||||
|
||||
if (null != site)
|
||||
{
|
||||
int skipCount = 0;
|
||||
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Processing a site: [short name: " + site.getShortName() + ", title: " + site.getTitle() + ", visibility: " + site.getVisibility() + "]");
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
PagingRequest paging = new PagingRequest(skipCount, maxItems, queryId);
|
||||
entries = calendarService.listCalendarEntries(site.getShortName(), paging);
|
||||
|
||||
List<CalendarEntry> page = (null != entries) ? (entries.getPage()) : (null);
|
||||
|
||||
if (null != page)
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Processing " + page.size() + " Calendar Events...");
|
||||
}
|
||||
|
||||
queryId = entries.getQueryExecutionId();
|
||||
|
||||
for (CalendarEntry entry : page)
|
||||
{
|
||||
if (isAllDay(entry))
|
||||
{
|
||||
updatedEventsAmount++;
|
||||
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("'All Day' Calendar event has been detected: [title: " + entry.getTitle() + ", start: " + entry.getStart() + ", end: "
|
||||
+ entry.getEnd() + ", isOutlook: " + entry.isOutlook() + "]");
|
||||
}
|
||||
|
||||
nodeService.setProperty(entry.getNodeRef(), CalendarModel.PROP_TO_DATE, adjustOldDate(entry.getEnd()));
|
||||
nodeService.setProperty(entry.getNodeRef(), CalendarModel.PROP_FROM_DATE, adjustOldDate(entry.getStart()));
|
||||
}
|
||||
}
|
||||
|
||||
skipCount += maxItems;
|
||||
}
|
||||
} while (batchEnabled && entries.hasMoreItems());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Not site object has been detected. Skipping...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("No one site has been found! Skipping patch execution...");
|
||||
}
|
||||
}
|
||||
|
||||
return I18NUtil.getMessage(MSG_SUCCESS, updatedEventsAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases (or decreases) <code>propertyId</code> date-property by the extracted (from the specified property value) time zone offset
|
||||
*
|
||||
* @param oldDate - {@link Date} instance, which represents not adjusted date for an 'All Day' event
|
||||
* @return {@link Date} instance, which represents adjusted date-property in accordance with time zone offset
|
||||
*/
|
||||
private Date adjustOldDate(Date oldDate)
|
||||
{
|
||||
Calendar result = Calendar.getInstance();
|
||||
result.setTime(oldDate);
|
||||
int offset = result.getTimeZone().getOffset(result.getTimeInMillis());
|
||||
result.add(Calendar.MILLISECOND, offset);
|
||||
return result.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the given {@link CalendarEntry} define an all-day
|
||||
* event?
|
||||
* An All Day Event is defined as one starting at midnight
|
||||
* on a day, and ending at midnight.
|
||||
*
|
||||
* For a single day event, the start and end dates should be
|
||||
* the same, and the times for both are UTC midnight.
|
||||
* For a multi day event, the start and end times are UTC midnight,
|
||||
* for the first and last days respectively.
|
||||
*/
|
||||
public static boolean isAllDay(CalendarEntry entry)
|
||||
{
|
||||
if (entry.getStart() == null || entry.getEnd() == null)
|
||||
{
|
||||
// One or both dates is missing
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pre-4.0, the midnights were local time...
|
||||
Calendar startLocal = Calendar.getInstance();
|
||||
Calendar endLocal = Calendar.getInstance();
|
||||
startLocal.setTime(entry.getStart());
|
||||
endLocal.setTime(entry.getEnd());
|
||||
|
||||
if (startLocal.get(Calendar.HOUR_OF_DAY) == 0 &&
|
||||
startLocal.get(Calendar.MINUTE) == 0 &&
|
||||
startLocal.get(Calendar.SECOND) == 0 &&
|
||||
endLocal.get(Calendar.HOUR_OF_DAY) == 0 &&
|
||||
endLocal.get(Calendar.MINUTE) == 0 &&
|
||||
endLocal.get(Calendar.SECOND) == 0)
|
||||
{
|
||||
// Both at midnight, counts as all day
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// In any other case, it isn't an all-day
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2011 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2013 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -18,10 +18,18 @@
|
||||
*/
|
||||
package org.alfresco.repo.admin.patch.impl;
|
||||
|
||||
import java.util.List;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.*;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.admin.patch.AbstractPatch;
|
||||
import org.alfresco.repo.admin.patch.PatchExecuter;
|
||||
import org.alfresco.repo.batch.BatchProcessWorkProvider;
|
||||
import org.alfresco.repo.batch.BatchProcessor;
|
||||
import org.alfresco.repo.importer.ImporterBootstrap;
|
||||
import org.alfresco.repo.workflow.WorkflowModel;
|
||||
import org.alfresco.service.cmr.admin.PatchException;
|
||||
@@ -31,6 +39,7 @@ import org.alfresco.service.cmr.repository.StoreRef;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
@@ -38,8 +47,8 @@ import org.springframework.extensions.surf.util.I18NUtil;
|
||||
/**
|
||||
* Patch that updates workflow package type and package items associations
|
||||
*
|
||||
* @see https://issues.alfresco.com/jira/browse/ETHREEOH-3613
|
||||
* @see https://issues.alfresco.com/jira/browse/ALF-11499
|
||||
* @see <a href=https://issues.alfresco.com/jira/browse/ETHREEOH-3613>ETHREEOH-3613</a>
|
||||
* @see <a href=https://issues.alfresco.com/jira/browse/ALF-11499>ALF-11499</a>
|
||||
* @author Arseny Kovalchuk
|
||||
* @since 3.4.7
|
||||
*/
|
||||
@@ -51,9 +60,29 @@ public class FixBpmPackagesPatch extends AbstractPatch
|
||||
private static final String ERR_MSG_EMPTY_CONTAINER = "patch.fixBpmPackages.emptyContainer";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(FixBpmPackagesPatch.class);
|
||||
private static Log progress_logger = LogFactory.getLog(PatchExecuter.class);
|
||||
|
||||
private int batchThreads = 4;
|
||||
private int batchSize = 1000;
|
||||
|
||||
private ImporterBootstrap importerBootstrap;
|
||||
|
||||
/**
|
||||
* @param batchThreads the number of threads that will write child association changes
|
||||
*/
|
||||
public void setBatchThreads(int batchThreads)
|
||||
{
|
||||
this.batchThreads = batchThreads;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param batchSize the number of child associations that will be modified per transaction
|
||||
*/
|
||||
public void setBatchSize(int batchSize)
|
||||
{
|
||||
this.batchSize = batchSize;
|
||||
}
|
||||
|
||||
public void setImporterBootstrap(ImporterBootstrap importerBootstrap)
|
||||
{
|
||||
this.importerBootstrap = importerBootstrap;
|
||||
@@ -62,122 +91,246 @@ public class FixBpmPackagesPatch extends AbstractPatch
|
||||
@Override
|
||||
protected String applyInternal() throws Exception
|
||||
{
|
||||
// Package counter for report
|
||||
int packagesCount = 0;
|
||||
StoreRef store = importerBootstrap.getStoreRef();
|
||||
if (store == null)
|
||||
|
||||
FixBpmPackagesPatchHelper helper = new FixBpmPackagesPatchHelper();
|
||||
try
|
||||
{
|
||||
throw new PatchException(ERR_MSG_INVALID_BOOTSTRAP_STORE);
|
||||
}
|
||||
|
||||
// Get root node for store
|
||||
NodeRef rootRef = nodeService.getRootNode(store);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("StoreRef:" + store + " RootNodeRef: " + rootRef);
|
||||
|
||||
// Get /sys:system container path, if it doesn't exist there is something wrong with the repo
|
||||
String sysContainer = importerBootstrap.getConfiguration().getProperty("system.system_container.childname");
|
||||
QName sysContainerQName = QName.createQName(sysContainer, namespaceService);
|
||||
|
||||
List<ChildAssociationRef> refs = nodeService.getChildAssocs(rootRef, ContentModel.ASSOC_CHILDREN, sysContainerQName);
|
||||
|
||||
if (refs == null || refs.size() == 0)
|
||||
throw new PatchException(ERR_MSG_EMPTY_CONTAINER, sysContainer);
|
||||
|
||||
NodeRef sysNodeRef = refs.get(0).getChildRef();
|
||||
|
||||
// Get /sys:system/sys:workflow container, if it doesn't exist there is something wrong with the repo
|
||||
String sysWorkflowContainer = importerBootstrap.getConfiguration().getProperty("system.workflow_container.childname");
|
||||
QName sysWorkflowQName = QName.createQName(sysWorkflowContainer, namespaceService);
|
||||
|
||||
refs = nodeService.getChildAssocs(sysNodeRef, ContentModel.ASSOC_CHILDREN, sysWorkflowQName);
|
||||
|
||||
if (refs == null || refs.size() == 0)
|
||||
throw new PatchException(ERR_MSG_EMPTY_CONTAINER, sysWorkflowContainer);
|
||||
|
||||
NodeRef workflowContainerRef = refs.get(0).getChildRef();
|
||||
|
||||
// Try to get /sys:system/sys:workflow/cm:packages, if there is no such node, then it wasn't created yet,
|
||||
// so there is nothing to convert
|
||||
refs = nodeService.getChildAssocs(workflowContainerRef, ContentModel.ASSOC_CHILDREN, RegexQNamePattern.MATCH_ALL);
|
||||
|
||||
if (refs == null || refs.size() == 0)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("There are no any packages in the container " + sysWorkflowContainer);
|
||||
return I18NUtil.getMessage(MSG_SUCCESS, packagesCount);
|
||||
}
|
||||
// Get /sys:system/sys:workflow/cm:packages container NodeRef
|
||||
NodeRef packagesContainerRef = refs.get(0).getChildRef();
|
||||
// Get workflow packages to be converted
|
||||
refs = nodeService.getChildAssocs(packagesContainerRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Found " + refs.size() + " packages to convert");
|
||||
|
||||
NodeRef packageRef = null;
|
||||
// For each package we get package items and convert their type from cm:systemfolder to bpm:package
|
||||
// Also we convert associations between packages and their items from cm:contains to bpm:packageContains
|
||||
for (ChildAssociationRef assocRef : refs)
|
||||
{
|
||||
packageRef = assocRef.getChildRef();
|
||||
QName typeQname = nodeService.getType(packageRef);
|
||||
String name = (String) nodeService.getProperty(packageRef, ContentModel.PROP_NAME);
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Package " + name + " type " + typeQname);
|
||||
|
||||
if (!nodeService.getType(packageRef).equals(WorkflowModel.TYPE_PACKAGE))
|
||||
StoreRef store = importerBootstrap.getStoreRef();
|
||||
if (store == null)
|
||||
{
|
||||
// New type of the package is bpm:package
|
||||
nodeService.setType(packageRef, WorkflowModel.TYPE_PACKAGE);
|
||||
throw new PatchException(ERR_MSG_INVALID_BOOTSTRAP_STORE);
|
||||
}
|
||||
// Get all package items
|
||||
List<ChildAssociationRef> packageItemsAssocs = nodeService.getChildAssocs(packageRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
|
||||
|
||||
for (ChildAssociationRef itemAssoc : packageItemsAssocs)
|
||||
// Get root node for store
|
||||
NodeRef rootRef = nodeService.getRootNode(store);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("StoreRef:" + store + " RootNodeRef: " + rootRef);
|
||||
|
||||
// Get /sys:system container path, if it doesn't exist there is something wrong with the repo
|
||||
String sysContainer = importerBootstrap.getConfiguration().getProperty("system.system_container.childname");
|
||||
QName sysContainerQName = QName.createQName(sysContainer, namespaceService);
|
||||
|
||||
List<ChildAssociationRef> refs = nodeService.getChildAssocs(rootRef, ContentModel.ASSOC_CHILDREN, sysContainerQName);
|
||||
|
||||
if (refs == null || refs.size() == 0)
|
||||
throw new PatchException(ERR_MSG_EMPTY_CONTAINER, sysContainer);
|
||||
|
||||
NodeRef sysNodeRef = refs.get(0).getChildRef();
|
||||
|
||||
// Get /sys:system/sys:workflow container, if it doesn't exist there is something wrong with the repo
|
||||
String sysWorkflowContainer = importerBootstrap.getConfiguration().getProperty("system.workflow_container.childname");
|
||||
QName sysWorkflowQName = QName.createQName(sysWorkflowContainer, namespaceService);
|
||||
|
||||
refs = nodeService.getChildAssocs(sysNodeRef, ContentModel.ASSOC_CHILDREN, sysWorkflowQName);
|
||||
|
||||
if (refs == null || refs.size() == 0)
|
||||
throw new PatchException(ERR_MSG_EMPTY_CONTAINER, sysWorkflowContainer);
|
||||
|
||||
NodeRef workflowContainerRef = refs.get(0).getChildRef();
|
||||
|
||||
// Try to get /sys:system/sys:workflow/cm:packages, if there is no such node, then it wasn't created yet,
|
||||
// so there is nothing to convert
|
||||
refs = nodeService.getChildAssocs(workflowContainerRef, ContentModel.ASSOC_CHILDREN, RegexQNamePattern.MATCH_ALL);
|
||||
|
||||
if (refs == null || refs.size() == 0)
|
||||
{
|
||||
NodeRef parentRef = itemAssoc.getParentRef();
|
||||
NodeRef childRef = itemAssoc.getChildRef();
|
||||
String itemName = (String) nodeService.getProperty(childRef, ContentModel.PROP_NAME);
|
||||
// To avoid unnecessary deletion of the child item, we check if the association is not primary
|
||||
// For the package item it should be not primary association.
|
||||
if (itemAssoc.isPrimary())
|
||||
{
|
||||
logger.error("Association between package: " + name + " and item: " + itemName + " is primary association, so removing this assiciation will result in child node deletion");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (itemAssoc.getTypeQName().equals(WorkflowModel.ASSOC_PACKAGE_CONTAINS))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean assocRemoved = nodeService.removeChildAssociation(itemAssoc);
|
||||
if (assocRemoved)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Association between package: " + name + " and item: " + itemName + " was removed");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger.isErrorEnabled())
|
||||
logger.error("Association between package: " + name + " and item: " + itemName + " doesn't exist");
|
||||
// If there is no association we won't create a new one
|
||||
continue;
|
||||
}
|
||||
// Recreate new association between package and particular item as bpm:packageContains
|
||||
/* ChildAssociationRef newChildAssoc = */nodeService.addChild(parentRef, childRef, WorkflowModel.ASSOC_PACKAGE_CONTAINS,
|
||||
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(itemName)));
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("New association has been created between package: " + name + " and item: " + itemName);
|
||||
}
|
||||
logger.debug("There are no any packages in the container " + sysWorkflowContainer);
|
||||
return I18NUtil.getMessage(MSG_SUCCESS, 0);
|
||||
}
|
||||
packagesCount++;
|
||||
// Get /sys:system/sys:workflow/cm:packages container NodeRef
|
||||
NodeRef packagesContainerRef = refs.get(0).getChildRef();
|
||||
// Get workflow packages to be converted
|
||||
refs = nodeService.getChildAssocs(packagesContainerRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Found " + refs.size() + " packages to convert");
|
||||
|
||||
return helper.fixBpmPackages(refs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
helper.closeWriter();
|
||||
}
|
||||
return I18NUtil.getMessage(MSG_SUCCESS, packagesCount);
|
||||
}
|
||||
|
||||
private class FixBpmPackagesPatchHelper
|
||||
{
|
||||
private File logFile;
|
||||
private FileChannel channel;
|
||||
private Integer assocCount;
|
||||
private int skipCount = 0;
|
||||
private List<ChildAssociationRef> refs;
|
||||
|
||||
private FixBpmPackagesPatchHelper() throws IOException
|
||||
{
|
||||
// put the log file into a long life temp directory
|
||||
File tempDir = TempFileProvider.getLongLifeTempDir("patches");
|
||||
logFile = new File(tempDir, "FixBpmPackagesPatch.log");
|
||||
|
||||
// open the file for appending
|
||||
RandomAccessFile outputFile = new RandomAccessFile(logFile, "rw");
|
||||
channel = outputFile.getChannel();
|
||||
// move to the end of the file
|
||||
channel.position(channel.size());
|
||||
// add a newline and it's ready
|
||||
writeLine("").writeLine("");
|
||||
writeLine("FixBpmPackagesPatch executing on " + new Date());
|
||||
}
|
||||
|
||||
private FixBpmPackagesPatchHelper write(Object obj) throws IOException
|
||||
{
|
||||
channel.write(ByteBuffer.wrap(obj.toString().getBytes("UTF-8")));
|
||||
return this;
|
||||
}
|
||||
|
||||
private FixBpmPackagesPatchHelper writeLine(Object obj) throws IOException
|
||||
{
|
||||
write(obj);
|
||||
write("\n");
|
||||
return this;
|
||||
}
|
||||
|
||||
private void closeWriter()
|
||||
{
|
||||
try
|
||||
{
|
||||
channel.close();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// Nothing we can do
|
||||
}
|
||||
}
|
||||
|
||||
public String fixBpmPackages(List<ChildAssociationRef> references) throws Exception
|
||||
{
|
||||
this.refs = references;
|
||||
this.assocCount = references.size();
|
||||
BatchProcessWorkProvider<ChildAssociationRef> workProvider = new BatchProcessWorkProvider<ChildAssociationRef>()
|
||||
{
|
||||
@Override
|
||||
public synchronized int getTotalEstimatedWorkSize()
|
||||
{
|
||||
return assocCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Collection<ChildAssociationRef> getNextWork()
|
||||
{
|
||||
int nextMaxSize = skipCount + batchSize;
|
||||
List<ChildAssociationRef> result;
|
||||
if (assocCount < skipCount)
|
||||
{
|
||||
// we are finished, return empty list
|
||||
result = Collections.emptyList();
|
||||
}
|
||||
else if (assocCount >= nextMaxSize)
|
||||
{
|
||||
// more jobs are available with full batch
|
||||
result = refs.subList(skipCount, nextMaxSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
// there are less items than batch size
|
||||
result = refs.subList(skipCount, assocCount);
|
||||
}
|
||||
// increment the counter of batches
|
||||
skipCount += batchSize;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// get the association types to check
|
||||
BatchProcessor<ChildAssociationRef> batchProcessor = new BatchProcessor<ChildAssociationRef>(
|
||||
"FixBpmPackagesPatch",
|
||||
transactionHelper,
|
||||
workProvider,
|
||||
batchThreads, batchSize,
|
||||
applicationEventPublisher,
|
||||
progress_logger, 1000);
|
||||
|
||||
BatchProcessor.BatchProcessWorker<ChildAssociationRef> worker = new BatchProcessor.BatchProcessWorker<ChildAssociationRef>()
|
||||
{
|
||||
@Override
|
||||
public String getIdentifier(ChildAssociationRef entry)
|
||||
{
|
||||
return entry.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeProcess() throws Throwable
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ChildAssociationRef assocRef) throws Throwable
|
||||
{
|
||||
NodeRef packageRef = assocRef.getChildRef();
|
||||
QName typeQname = nodeService.getType(packageRef);
|
||||
String name = (String) nodeService.getProperty(packageRef, ContentModel.PROP_NAME);
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Package " + name + " type " + typeQname);
|
||||
|
||||
if (!nodeService.getType(packageRef).equals(WorkflowModel.TYPE_PACKAGE))
|
||||
{
|
||||
// New type of the package is bpm:package
|
||||
nodeService.setType(packageRef, WorkflowModel.TYPE_PACKAGE);
|
||||
}
|
||||
// Get all package items
|
||||
List<ChildAssociationRef> packageItemsAssocs = nodeService.getChildAssocs(packageRef, ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL);
|
||||
|
||||
for (ChildAssociationRef itemAssoc : packageItemsAssocs)
|
||||
{
|
||||
NodeRef parentRef = itemAssoc.getParentRef();
|
||||
NodeRef childRef = itemAssoc.getChildRef();
|
||||
String itemName = (String) nodeService.getProperty(childRef, ContentModel.PROP_NAME);
|
||||
// To avoid unnecessary deletion of the child item, we check if the association is not primary
|
||||
// For the package item it should be not primary association.
|
||||
if (itemAssoc.isPrimary())
|
||||
{
|
||||
logger.error("Association between package: " + name + " and item: " + itemName + " is primary association, so removing this assiciation will result in child node deletion");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (itemAssoc.getTypeQName().equals(WorkflowModel.ASSOC_PACKAGE_CONTAINS))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean assocRemoved = nodeService.removeChildAssociation(itemAssoc);
|
||||
if (assocRemoved)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Association between package: " + name + " and item: " + itemName + " was removed");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger.isErrorEnabled())
|
||||
logger.error("Association between package: " + name + " and item: " + itemName + " doesn't exist");
|
||||
// If there is no association we won't create a new one
|
||||
continue;
|
||||
}
|
||||
// Recreate new association between package and particular item as bpm:packageContains
|
||||
/* ChildAssociationRef newChildAssoc = */nodeService.addChild(parentRef, childRef, WorkflowModel.ASSOC_PACKAGE_CONTAINS,
|
||||
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(itemName)));
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("New association has been created between package: " + name + " and item: " + itemName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterProcess() throws Throwable
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
int updated = batchProcessor.process(worker, true);
|
||||
|
||||
return I18NUtil.getMessage(MSG_SUCCESS, updated, logFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -41,13 +41,14 @@ import org.alfresco.service.cmr.repository.StoreRef;
|
||||
*/
|
||||
public class GenericBootstrapPatch extends AbstractPatch
|
||||
{
|
||||
private static final String MSG_EXISTS = "patch.genericBootstrap.result.exists";
|
||||
private static final String MSG_CREATED = "patch.genericBootstrap.result.created";
|
||||
private static final String ERR_MULTIPLE_FOUND = "patch.genericBootstrap.err.multiple_found";
|
||||
protected static final String MSG_EXISTS = "patch.genericBootstrap.result.exists";
|
||||
protected static final String MSG_CREATED = "patch.genericBootstrap.result.created";
|
||||
protected static final String MSG_DEFERRED = "patch.genericBootstrap.result.deferred";
|
||||
protected static final String ERR_MULTIPLE_FOUND = "patch.genericBootstrap.err.multiple_found";
|
||||
|
||||
private ImporterBootstrap importerBootstrap;
|
||||
private String checkPath;
|
||||
private Properties bootstrapView;
|
||||
protected ImporterBootstrap importerBootstrap;
|
||||
protected String checkPath;
|
||||
protected Properties bootstrapView;
|
||||
|
||||
/**
|
||||
* @param importerBootstrap the bootstrap bean that performs the user store bootstrap
|
||||
@@ -87,6 +88,7 @@ public class GenericBootstrapPatch extends AbstractPatch
|
||||
super.checkProperties();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String applyInternal() throws Exception
|
||||
{
|
||||
@@ -122,4 +124,6 @@ public class GenericBootstrapPatch extends AbstractPatch
|
||||
// done
|
||||
return I18NUtil.getMessage(MSG_CREATED, path, rootNodeRef);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2013 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.alfresco.repo.admin.patch.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.lock.JobLockService;
|
||||
import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
||||
import org.alfresco.service.cmr.admin.PatchException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.StoreRef;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The SharedFolderPatch is a Generic Bootstrap Patch with the extra ability to
|
||||
* rename an existing folder that is in the way (in a different namespace).
|
||||
* <p>
|
||||
* The first use-case is when there is a child called cm:shared and we want to patch a folder with app:shared
|
||||
*
|
||||
* @author mrogers
|
||||
*
|
||||
*/
|
||||
public class SharedFolderPatch extends GenericBootstrapPatch
|
||||
{
|
||||
private JobLockService jobLockService;
|
||||
|
||||
private long LOCK_TIME_TO_LIVE=10000;
|
||||
private long LOCK_REFRESH_TIME=5000;
|
||||
|
||||
private String renamePath;
|
||||
|
||||
private Log logger = LogFactory.getLog(SharedFolderPatch.class);
|
||||
|
||||
private static final String MSG_RENAMED = "patch.sharedFolder.result.renamed";
|
||||
|
||||
/**
|
||||
* Run the Shared Folder Patch asynchronously after bootstrap.
|
||||
*/
|
||||
public void executeAsync()
|
||||
{
|
||||
// Lock the push
|
||||
QName lockQName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "patch.sharedFolder");
|
||||
String lockToken = jobLockService.getLock(lockQName, LOCK_TIME_TO_LIVE, 0, 1);
|
||||
SharedFolderPatchCallback callback = new SharedFolderPatchCallback();
|
||||
jobLockService.refreshLock(lockToken, lockQName, LOCK_REFRESH_TIME, callback);
|
||||
|
||||
try
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("SharedFolderPatch: job lock held");
|
||||
}
|
||||
|
||||
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
|
||||
{
|
||||
public Void doWork() throws Exception
|
||||
{
|
||||
applyAsync();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("PUSH: job finished");
|
||||
}
|
||||
|
||||
// Release the locks on the job and stop refreshing
|
||||
callback.isActive = false;
|
||||
jobLockService.releaseLock(lockToken, lockQName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String applyInternal() throws Exception
|
||||
{
|
||||
StoreRef storeRef = importerBootstrap.getStoreRef();
|
||||
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
|
||||
if (getRenamePath() != null)
|
||||
{
|
||||
List<NodeRef> results = searchService.selectNodes(
|
||||
rootNodeRef,
|
||||
getRenamePath(),
|
||||
null,
|
||||
namespaceService,
|
||||
false);
|
||||
|
||||
if (results.size() > 1)
|
||||
{
|
||||
throw new PatchException(ERR_MULTIPLE_FOUND, renamePath);
|
||||
}
|
||||
else if (results.size() == 1)
|
||||
{
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("There is an existing node in the way path:" + getRenamePath());
|
||||
}
|
||||
// A node already exists that we must rename.
|
||||
NodeRef existingNodeRef = results.get(0);
|
||||
|
||||
// get the path of the parent node e.g. company_home
|
||||
LinkedList<String> folderElements = new LinkedList<String>(Arrays.asList(getRenamePath().split("/")));
|
||||
folderElements.removeLast();
|
||||
|
||||
StringBuffer parentPath = new StringBuffer();
|
||||
|
||||
for(String folder : folderElements)
|
||||
{
|
||||
parentPath.append("/");
|
||||
parentPath.append(folder);
|
||||
}
|
||||
|
||||
List<NodeRef> parentResults = searchService.selectNodes(
|
||||
rootNodeRef,
|
||||
parentPath.toString(),
|
||||
null,
|
||||
namespaceService,
|
||||
false);
|
||||
|
||||
if(parentResults.size()==1)
|
||||
{
|
||||
|
||||
NodeRef parentNodeRef = parentResults.get(0);
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Found the parent node - doing a move parentNodeRef:" + parentNodeRef);
|
||||
}
|
||||
|
||||
// rename the existing node
|
||||
nodeService.moveNode(existingNodeRef, parentNodeRef, ContentModel.ASSOC_CONTAINS, QName.createQName( NamespaceService.APP_MODEL_1_0_URI, "shared"));
|
||||
return I18NUtil.getMessage(MSG_RENAMED, renamePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something has gone horribly wrong if we get here - we have multiple parents, or none despite finding the node earlier
|
||||
throw new PatchException(ERR_MULTIPLE_FOUND, parentPath.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Else run the normal GenericBootstrapPatch implementation
|
||||
|
||||
if(logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Node does not already exist, Running the Generic Bootstrap Patch");
|
||||
}
|
||||
return super.applyInternal();
|
||||
}
|
||||
|
||||
public void setRenamePath(String renamePath) {
|
||||
this.renamePath = renamePath;
|
||||
}
|
||||
|
||||
public String getRenamePath() {
|
||||
return renamePath;
|
||||
}
|
||||
|
||||
public void setJobLockService(JobLockService jobLockService) {
|
||||
this.jobLockService = jobLockService;
|
||||
}
|
||||
|
||||
public JobLockService getJobLockService() {
|
||||
return jobLockService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Job to initiate the {@link SharedFolderPatch} if it has been deferred
|
||||
*
|
||||
* @author Mark Rogers
|
||||
* @since 4.2
|
||||
*/
|
||||
public static class SharedFolderPatchJob implements Job
|
||||
{
|
||||
public SharedFolderPatchJob()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the cleaner to do its work
|
||||
*/
|
||||
public void execute(JobExecutionContext context) throws JobExecutionException
|
||||
{
|
||||
JobDataMap jobData = context.getJobDetail().getJobDataMap();
|
||||
// extract the content cleaner to use
|
||||
Object sharedFolderPatchObj = jobData.get("sharedFolderPatch");
|
||||
if (sharedFolderPatchObj == null || !(sharedFolderPatchObj instanceof SharedFolderPatch))
|
||||
{
|
||||
throw new AlfrescoRuntimeException(
|
||||
"'sharedFolderPatch' data must contain valid 'SharedFolderPatch' reference");
|
||||
}
|
||||
|
||||
// Job Lock Here - should probably move into the patch service at some time.
|
||||
SharedFolderPatch sharedFolderPatch = (SharedFolderPatch) sharedFolderPatchObj;
|
||||
sharedFolderPatch.executeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private class SharedFolderPatchCallback implements JobLockRefreshCallback
|
||||
{
|
||||
public boolean isActive = true;
|
||||
|
||||
@Override
|
||||
public boolean isActive()
|
||||
{
|
||||
return isActive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lockReleased()
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("lock released");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user