/*
* Copyright 2011 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.drools.guvnor.server;
import com.google.gwt.user.client.rpc.SerializationException;
import org.apache.commons.lang.StringEscapeUtils;
import org.drools.guvnor.client.common.AssetFormats;
import org.drools.guvnor.client.rpc.*;
import org.drools.guvnor.server.builder.AssetItemValidator;
import org.drools.guvnor.server.builder.BRMSPackageBuilder;
import org.drools.guvnor.server.builder.DSLLoader;
import org.drools.guvnor.server.builder.PageResponseBuilder;
import org.drools.guvnor.server.builder.pagerow.ArchivedAssetPageRowBuilder;
import org.drools.guvnor.server.builder.pagerow.AssetPageRowBuilder;
import org.drools.guvnor.server.builder.pagerow.QuickFindPageRowBuilder;
import org.drools.guvnor.server.cache.RuleBaseCache;
import org.drools.guvnor.server.contenthandler.BPMN2ProcessHandler;
import org.drools.guvnor.server.contenthandler.ContentHandler;
import org.drools.guvnor.server.contenthandler.ContentManager;
import org.drools.guvnor.server.contenthandler.IRuleAsset;
import org.drools.guvnor.server.repository.MailboxService;
import org.drools.guvnor.server.security.CategoryPathType;
import org.drools.guvnor.server.security.PackageNameType;
import org.drools.guvnor.server.security.RoleTypes;
import org.drools.guvnor.server.util.*;
import org.drools.repository.*;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.remoting.WebRemote;
import org.jboss.seam.annotations.security.Restrict;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.security.Identity;
import java.text.DateFormat;
import java.util.*;
/**
* Handles operations for Assets
*/
@Name("org.drools.guvnor.server.RepositoryAssetOperations")
@AutoCreate
public class RepositoryAssetOperations {
private RulesRepository repository;
private static final LoggingHelper log = LoggingHelper
.getLogger(RepositoryAssetOperations.class);
public void setRulesRepository(RulesRepository repository) {
this.repository = repository;
}
public RulesRepository getRulesRepository() {
return repository;
}
public String renameAsset(String uuid,
String newName) {
return getRulesRepository().renameAsset(uuid,
newName);
}
protected BuilderResult validateAsset(RuleAsset asset) {
try {
ContentHandler handler = ContentManager
.getHandler(asset.metaData.format);
AssetItem item = getRulesRepository().loadAssetByUUID(asset.uuid);
handler.storeAssetContent(asset,
item);
AssetItemValidator assetItemValidator = new AssetItemValidator(handler, item);
return assetItemValidator.validate();
} catch (Exception e) {
log.error("Unable to build asset.",
e);
BuilderResult result = new BuilderResult();
result.getLines().add(createBuilderResultLine(asset));
return result;
}
}
private BuilderResultLine createBuilderResultLine(RuleAsset asset) {
BuilderResultLine builderResultLine = new BuilderResultLine();
builderResultLine.setAssetName(asset.name);
builderResultLine.setAssetFormat(asset.metaData.format);
builderResultLine.setMessage("Unable to validate this asset. (Check log for detailed messages).");
builderResultLine.setUuid(asset.uuid);
return builderResultLine;
}
public String checkinVersion(RuleAsset asset) throws SerializationException {
AssetItem repoAsset = getRulesRepository().loadAssetByUUID( asset.uuid );
if ( isAssetUpdatedInRepository( asset,
repoAsset ) ) {
return "ERR: Unable to save this asset, as it has been recently updated by [" + repoAsset.getLastContributor() + "]";
}
MetaData meta = asset.metaData;
MetaDataMapper metaDataMapper = MetaDataMapper.getInstance();
metaDataMapper.copyFromMetaData( meta,
repoAsset );
repoAsset.updateDateEffective( dateToCalendar( meta.dateEffective ) );
repoAsset.updateDateExpired( dateToCalendar( meta.dateExpired ) );
repoAsset.updateCategoryList( meta.categories );
repoAsset.updateDescription(asset.description);
ContentHandler handler = ContentManager.getHandler(repoAsset.getFormat());
handler.storeAssetContent(asset,
repoAsset);
if ( !(asset.metaData.format.equals( AssetFormats.TEST_SCENARIO )) || asset.metaData.format.equals( AssetFormats.ENUMERATION ) ) {
PackageItem pkg = repoAsset.getPackage();
pkg.updateBinaryUpToDate(false);
RuleBaseCache.getInstance().remove(pkg.getUUID());
}
repoAsset.checkin( asset.checkinComment );
return repoAsset.getUUID();
}
private Calendar dateToCalendar(Date date) {
if (date == null) {
return null;
}
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal;
}
private boolean isAssetUpdatedInRepository(RuleAsset asset,
AssetItem repoAsset) {
return asset.lastModified.before( repoAsset.getLastModified().getTime() );
}
public void restoreVersion(String versionUUID,
String assetUUID,
String comment) {
AssetItem old = getRulesRepository().loadAssetByUUID(versionUUID);
AssetItem head = getRulesRepository().loadAssetByUUID(assetUUID);
log.info("USER:" + getCurrentUserName() + " RESTORE of asset: [" + head.getName() + "] UUID: [" + head.getUUID() + "] with historical version number: [" + old.getVersionNumber());
getRulesRepository().restoreHistoricalAsset(old,
head,
comment);
}
protected TableDataResult loadItemHistory(final VersionableItem item)
{
Iterator<VersionableItem> it = item.getHistory();
//AssetHistoryIterator it = assetItem.getHistory();
// MN Note: this uses the lazy iterator, but then loads the whole lot
// up, and returns it.
// The reason for this is that the GUI needs to show things in numeric
// order by the version number.
// When a version is restored, its previous version is NOT what you
// thought it was - due to how JCR works
// (its more like CVS then SVN). So to get a linear progression of
// versions, we use the incrementing version number,
// and load it all up and sort it. This is not ideal.
// In future, we may do a "restore" instead just by copying content into
// a new version, not restoring a node,
// in which case the iterator will be in order (or you can just walk all
// the way back).
// So if there are performance problems with looking at lots of
// historical versions, look at this nasty bit of code.
List<TableDataRow> result = new ArrayList<TableDataRow>();
while (it.hasNext()) {
VersionableItem historical = (VersionableItem) it.next();
long versionNumber = historical.getVersionNumber();
if (isHistory(item,
versionNumber)) {
result.add(createHistoricalRow(
historical));
}
}
TableDataResult table = new TableDataResult();
table.data = result.toArray(new TableDataRow[result.size()]);
return table;
}
private boolean isHistory(VersionableItem item,
long versionNumber) {
//return versionNumber != 0 && versionNumber != item.getVersionNumber();
//we do return the LATEST version as part of the history.
return versionNumber != 0;
}
private TableDataRow createHistoricalRow(VersionableItem historical) {
final DateFormat dateFormatter = DateFormat.getInstance();
TableDataRow tableDataRow = new TableDataRow();
tableDataRow.id = historical.getVersionSnapshotUUID();
tableDataRow.values = new String[4];
tableDataRow.values[0] = Long.toString(historical.getVersionNumber());
tableDataRow.values[1] = historical.getCheckinComment();
tableDataRow.values[2] = dateFormatter.format(historical
.getLastModified().getTime());
tableDataRow.values[3] = historical.getStateDescription();
return tableDataRow;
}
/**
* @param skip
* @param numRows
* @return
* @throws SerializationException
* @deprecated in favour of {@link loadArchivedAssets(PageRequest)}
*/
protected TableDataResult loadArchivedAssets(int skip,
int numRows)
{
List<TableDataRow> result = new ArrayList<TableDataRow>();
RepositoryFilter filter = new AssetItemFilter();
AssetItemIterator it = getRulesRepository().findArchivedAssets();
it.skip(skip);
int count = 0;
while (it.hasNext()) {
AssetItem archived = (AssetItem) it.next();
if (filter.accept(archived,
"read")) {
result.add(createArchivedRow(archived));
count++;
}
if (count == numRows) {
break;
}
}
return createArchivedTable(result,
it);
}
private TableDataRow createArchivedRow(AssetItem archived) {
TableDataRow row = new TableDataRow();
row.id = archived.getUUID();
row.values = new String[5];
row.values[0] = archived.getName();
row.values[1] = archived.getFormat();
row.values[2] = archived.getPackageName();
row.values[3] = archived.getLastContributor();
row.values[4] = Long.toString(archived.getLastModified().getTime()
.getTime());
return row;
}
private TableDataResult createArchivedTable(List<TableDataRow> result,
AssetItemIterator it) {
TableDataResult table = new TableDataResult();
table.data = result.toArray(new TableDataRow[result.size()]);
table.currentPosition = it.getPosition();
table.total = it.getSize();
table.hasNext = it.hasNext();
return table;
}
protected PageResponse<AdminArchivedPageRow> loadArchivedAssets(PageRequest request) {
// Do query
long start = System.currentTimeMillis();
AssetItemIterator iterator = getRulesRepository().findArchivedAssets();
log.debug("Search time: " + (System.currentTimeMillis() - start));
// Populate response
long totalRowsCount = iterator.getSize();
PageResponse<AdminArchivedPageRow> response = new PageResponse<AdminArchivedPageRow>();
ArchivedAssetPageRowBuilder archivedAssetPageRowBuilder = new ArchivedAssetPageRowBuilder();
List<AdminArchivedPageRow> rowList = archivedAssetPageRowBuilder.createRows( request,
iterator );
boolean bHasMoreRows = iterator.hasNext();
response.setStartRowIndex( request.getStartRowIndex() );
response.setPageRowList( rowList );
response.setLastPage( !bHasMoreRows );
ServiceRowSizeHelper serviceRowSizeHelper = new ServiceRowSizeHelper();
serviceRowSizeHelper.fixTotalRowSize( request,
response,
totalRowsCount,
rowList.size(),
bHasMoreRows );
long methodDuration = System.currentTimeMillis() - start;
log.debug("Searched for Archived Assests in " + methodDuration + " ms.");
return response;
}
/**
* @param packageUuid
* @param formats
* @param skip
* @param numRows
* @param tableConfig
* @return
* @throws SerializationException
* @deprecated in favour of {@link findAssetPage(AssetPageRequest)}
*/
protected TableDataResult listAssets(String packageUuid,
String formats[],
int skip,
int numRows,
String tableConfig)
{
long start = System.currentTimeMillis();
PackageItem pkg = getRulesRepository().loadPackageByUUID(packageUuid);
AssetItemIterator it;
if (formats.length > 0) {
it = pkg.listAssetsByFormat(formats);
} else {
it = pkg.listAssetsNotOfFormat(AssetFormatHelper
.listRegisteredTypes());
}
TableDisplayHandler handler = new TableDisplayHandler(tableConfig);
log.debug("time for asset list load: "
+ (System.currentTimeMillis() - start));
return handler.loadRuleListTable(it,
skip,
numRows);
}
/**
* @param searchText
* @param searchArchived
* @param skip
* @param numRows
* @return
* @throws SerializationException
* @deprecated in favour of {@link quickFindAsset(QueryPageRequest)}
*/
protected TableDataResult quickFindAsset(String searchText,
boolean searchArchived,
int skip,
int numRows)
throws SerializationException {
String search = searchText.replace('*',
'%');
if (!search.endsWith("%")) {
search += "%";
}
List<AssetItem> resultList = new ArrayList<AssetItem>();
long start = System.currentTimeMillis();
AssetItemIterator it = getRulesRepository().findAssetsByName(search,
searchArchived);
log.debug("Search time: " + (System.currentTimeMillis() - start));
RepositoryFilter filter = new AssetItemFilter();
while (it.hasNext()) {
AssetItem ai = it.next();
if (filter.accept(ai,
RoleTypes.PACKAGE_READONLY)) {
resultList.add(ai);
}
}
TableDisplayHandler handler = new TableDisplayHandler("searchresults");
return handler.loadRuleListTable(resultList,
skip,
numRows);
}
/**
* @param text
* @param seekArchived
* @param skip
* @param numRows
* @return
* @throws SerializationException
* @deprecated in favour of {@link queryFullText(QueryPageRequest)}
*/
protected TableDataResult queryFullText(String text,
boolean seekArchived,
int skip,
int numRows) throws SerializationException {
AssetItemIterator it = getRulesRepository().queryFullText( text,
seekArchived );
// Add filter for READONLY permission
List<AssetItem> resultList = new ArrayList<AssetItem>();
RepositoryFilter filter = new PackageFilter();
while ( it.hasNext() ) {
AssetItem ai = it.next();
PackageConfigData data = new PackageConfigData();
data.uuid = ai.getPackage().getUUID();
if ( filter.accept( data,
RoleTypes.PACKAGE_READONLY ) ) {
resultList.add( ai );
}
}
TableDisplayHandler handler = new TableDisplayHandler("searchresults");
return handler.loadRuleListTable(resultList,
skip,
numRows);
}
// TODO: Very hard to unit test -> needs refactoring
protected String buildAssetSource(RuleAsset asset)
throws SerializationException {
ContentHandler handler = ContentManager
.getHandler( asset.metaData.format );
StringBuilder stringBuilder = new StringBuilder();
if (handler.isRuleAsset()) {
BRMSPackageBuilder builder = new BRMSPackageBuilder();
// now we load up the DSL files
PackageItem packageItem = getRulesRepository().loadPackage(
asset.metaData.packageName );
builder.setDSLFiles( DSLLoader.loadDSLMappingFiles( packageItem ) );
if ( asset.metaData.isBinary() ) {
AssetItem item = getRulesRepository().loadAssetByUUID(
asset.uuid );
handler.storeAssetContent(asset,
item);
((IRuleAsset) handler).assembleDRL(builder,
item,
stringBuilder);
} else {
((IRuleAsset) handler).assembleDRL(builder,
asset,
stringBuilder);
}
} else {
if (handler
.getClass()
.getName()
.equals("org.drools.guvnor.server.contenthandler.BPMN2ProcessHandler")) {
BPMN2ProcessHandler bpmn2handler = ((BPMN2ProcessHandler) handler);
bpmn2handler.assembleProcessSource( asset.content,
stringBuilder );
}
}
return stringBuilder.toString();
}
protected PageResponse<AssetPageRow> findAssetPage(AssetPageRequest request)
{
log.debug( "Finding asset page of packageUuid ("
+ request.getPackageUuid() + ")" );
long start = System.currentTimeMillis();
PackageItem packageItem = getRulesRepository().loadPackageByUUID(
request.getPackageUuid() );
AssetItemIterator it;
if ( request.getFormatInList() != null ) {
if ( request.getFormatIsRegistered() != null ) {
throw new IllegalArgumentException(
"Combining formatInList and formatIsRegistered is not yet supported." );
} else {
it = packageItem.listAssetsByFormat( request.getFormatInList() );
}
} else {
if ( request.getFormatIsRegistered() != null ) {
it = packageItem.listAssetsNotOfFormat( AssetFormatHelper
.listRegisteredTypes() );
} else {
it = packageItem.queryAssets( "" );
}
}
// Populate response
long totalRowsCount = it.getSize();
PageResponse<AssetPageRow> response = new PageResponse<AssetPageRow>();
AssetPageRowBuilder assetPageRowBuilder = new AssetPageRowBuilder();
List<AssetPageRow> rowList = assetPageRowBuilder.createRows( request,
it );
boolean bHasMoreRows = it.hasNext();
response.setStartRowIndex( request.getStartRowIndex() );
response.setPageRowList( rowList );
response.setLastPage( !bHasMoreRows );
// Fix Total Row Size
ServiceRowSizeHelper serviceRowSizeHelper = new ServiceRowSizeHelper();
serviceRowSizeHelper.fixTotalRowSize( request,
response,
totalRowsCount,
rowList.size(),
bHasMoreRows );
long methodDuration = System.currentTimeMillis() - start;
log.debug("Found asset page of packageUuid ("
+ request.getPackageUuid() + ") in " + methodDuration + " ms.");
return response;
}
protected PageResponse<QueryPageRow> quickFindAsset(QueryPageRequest request) {
// Setup parameters
String search = request.getSearchText().replace('*',
'%');
if (!search.startsWith("%")) {
search = "%" + search;
}
if (!search.endsWith("%")) {
search += "%";
}
// Do query
long start = System.currentTimeMillis();
AssetItemIterator it = getRulesRepository().findAssetsByName( search,
request.isSearchArchived() );
log.debug( "Search time: " + (System.currentTimeMillis() - start) );
// Populate response
long totalRowsCount = it.getSize();
PageResponse<QueryPageRow> response = new PageResponse<QueryPageRow>();
QuickFindPageRowBuilder quickFindPageRowBuilder = new QuickFindPageRowBuilder();
List<QueryPageRow> rowList = quickFindPageRowBuilder.createRows( request,
it );
boolean bHasMoreRows = it.hasNext();
response.setStartRowIndex( request.getStartRowIndex() );
response.setPageRowList( rowList );
response.setLastPage( !bHasMoreRows );
// Fix Total Row Size
ServiceRowSizeHelper serviceRowSizeHelper = new ServiceRowSizeHelper();
serviceRowSizeHelper.fixTotalRowSize( request,
response,
totalRowsCount,
rowList.size(),
bHasMoreRows );
long methodDuration = System.currentTimeMillis() - start;
log.debug("Queried repository (Quick Find) for (" + search + ") in " + methodDuration + " ms.");
return response;
}
protected void lockAsset(String uuid) {
AssetLockManager alm = AssetLockManager.instance();
String userName;
if (Contexts.isApplicationContextActive()) {
userName = Identity.instance().getCredentials().getUsername();
} else {
userName = "anonymous";
}
log.info("Locking asset uuid=" + uuid + " for user [" + userName + "]");
alm.lockAsset( uuid,
userName );
}
protected void unLockAsset(String uuid) {
AssetLockManager alm = AssetLockManager.instance();
log.info("Unlocking asset [" + uuid + "]");
alm.unLockAsset(uuid);
}
protected String getAssetLockerUserName(String uuid) {
AssetLockManager alm = AssetLockManager.instance();
String userName = alm.getAssetLockerUserName(uuid);
log.info("Asset locked by [" + userName + "]");
return userName;
}
protected RuleAsset loadAsset(AssetItem item) throws SerializationException {
RuleAsset asset = new RuleAsset();
asset.uuid = item.getUUID();
asset.name = item.getName();
asset.description = item.getDescription();
asset.lastModified = item.getLastModified().getTime();
asset.lastContributor = item.getLastContributor();
asset.state = (item.getState() != null) ? item.getState().getName() : "";
asset.dateCreated = item.getCreatedDate().getTime();
asset.checkinComment = item.getCheckinComment();
asset.versionNumber = item.getVersionNumber();
asset.metaData = populateMetaData( item );
ContentHandler handler = ContentManager.getHandler( asset.metaData.format );
handler.retrieveAssetContent( asset,
item );
return asset;
}
/**
* Populate meta data with asset specific info.
*/
MetaData populateMetaData(AssetItem item) {
MetaData meta = populateMetaData((VersionableItem) item);
meta.packageName = item.getPackageName();
meta.packageUUID = item.getPackage().getUUID();
meta.setBinary( item.isBinary() );
List<CategoryItem> categories = item.getCategories();
fillMetaCategories( meta,
categories );
meta.dateEffective = calendarToDate( item.getDateEffective() );
meta.dateExpired = calendarToDate( item.getDateExpired() );
return meta;
}
/**
* read in the meta data, populating all dublin core and versioning stuff.
*/
MetaData populateMetaData(VersionableItem item) {
MetaData meta = new MetaData();
MetaDataMapper metaDataMapper = MetaDataMapper.getInstance();
metaDataMapper.copyToMetaData( meta,
item );
//problematic implementation of getPrecedingVersion and getPrecedingVersion().
//commented out temporarily as this is used by the front end.
//meta.hasPreceedingVersion = item.getPrecedingVersion() != null;
//meta.hasSucceedingVersion = item.getPrecedingVersion() != null;
return meta;
}
private void fillMetaCategories(MetaData meta,
List<CategoryItem> categories) {
meta.categories = new String[categories.size()];
for ( int i = 0; i < meta.categories.length; i++ ) {
CategoryItem cat = (CategoryItem) categories.get( i );
meta.categories[i] = cat.getFullPath();
}
}
private Date calendarToDate(Calendar createdDate) {
if (createdDate == null) {
return null;
}
return createdDate.getTime();
}
protected void clearAllDiscussionsForAsset(final String assetId) {
RulesRepository repo = getRulesRepository();
AssetItem asset = repo.loadAssetByUUID(assetId);
asset.updateStringProperty("",
"discussion");
repo.save();
push("discussion",
assetId);
}
protected List<DiscussionRecord> addToDiscussionForAsset(String assetId,
String comment) {
RulesRepository repo = getRulesRepository();
AssetItem asset = repo.loadAssetByUUID( assetId );
Discussion dp = new Discussion();
List<DiscussionRecord> discussion = dp.fromString( asset.getStringProperty( Discussion.DISCUSSION_PROPERTY_KEY ) );
discussion.add( new DiscussionRecord( repo.getSession().getUserID(),
StringEscapeUtils.escapeXml( comment ) ) );
asset.updateStringProperty( dp.toString( discussion ),
Discussion.DISCUSSION_PROPERTY_KEY,
false );
repo.save();
push("discussion",
assetId);
MailboxService.getInstance().recordItemUpdated(asset);
return discussion;
}
private void push(String messageType,
String message) {
Backchannel.getInstance().publish(new PushResponse(messageType,
message));
}
private String getCurrentUserName() {
return getRulesRepository().getSession().getUserID();
}
}